singbox_mm
A Flutter VPN plugin that drives a sing-box runtime with a high-level Dart API.
The package focuses on:
- GFW-resistant routing presets.
- Traffic-throttling countermeasures (uTLS, multiplex, optional TCP brutal).
- A typed profile/config builder instead of raw JSON strings.
- Runtime controls (
initialize,setConfig,start/stop/restart) with state + stats streaming.
Supported protocols in the current model:
vlessvmesstrojanshadowsocks(ss://)hysteria2(hysteria:///hysteria2:///hy2://)tuicwireguard(wireguard:///wg://)ssh(ssh://)
WireGuard outbound in sing-box is deprecated in 1.11.x and removed in 1.13.x; keep this in mind when choosing core versions.
Runtime capability guard is enabled: when core version is >= 1.13.0, WireGuard profiles are rejected (or filtered out from endpoint pools) before config apply.
You can inspect runtime capabilities directly:
final VpnCoreCapabilities core = await vpn.getCoreCapabilities();
print(core.displayVersion);
print(core.supportsProtocol(VpnProtocol.vless)); // true
print(core.supportsProtocol(VpnProtocol.wireguard)); // false on >= 1.13.0
final bool canUseWireGuard = await vpn.isProtocolSupportedByCore(
VpnProtocol.wireguard,
);
final List<VpnProfile> supportedOnly = await vpn.filterProfilesByCoreSupport(
profiles: subscriptionProfiles,
);
Install
Add to your pubspec.yaml:
dependencies:
singbox_mm: ^0.1.6
For local development in a monorepo, you can still use a path dependency:
dependencies:
singbox_mm:
path: ../singbox_mm
Platform Support
- Android: fully supported runtime through bundled
libboxJNI bridge. - iOS: API surface is available, but production tunnel requires host-side
NetworkExtension(PacketTunnelProvider) integration.
Dart API
import 'package:singbox_mm/singbox_mm.dart';
final vpn = SignboxVpn();
await vpn.initialize(
const SingboxRuntimeOptions(
logLevel: 'info',
tunInterfaceName: 'sb-tun',
androidBinaryAssetByAbi: <String, String>{
'arm64-v8a': 'assets/singbox/android/arm64-v8a/sing-box',
'armeabi-v7a': 'assets/singbox/android/armeabi-v7a/sing-box',
'x86_64': 'assets/singbox/android/x86_64/sing-box',
},
),
);
final granted = await vpn.requestVpnPermission();
if (!granted) {
throw Exception('VPN permission denied');
}
final notificationGranted = await vpn.requestNotificationPermission();
if (!notificationGranted) {
throw Exception('Notification permission denied');
}
final profile = VpnProfile.vless(
tag: 'proxy-main',
server: 'example.com',
serverPort: 443,
uuid: '00000000-0000-0000-0000-000000000000',
transport: VpnTransport.ws,
websocketPath: '/ws',
tls: const TlsOptions(
enabled: true,
serverName: 'example.com',
utlsFingerprint: 'chrome',
),
);
await vpn.applyProfile(
profile: profile,
bypassPolicy: const BypassPolicy(
preset: BypassPolicyPreset.aggressive,
directDomains: <String>['lan', 'local'],
),
throttlePolicy: const TrafficThrottlePolicy(
enableMultiplex: true,
enableTcpBrutal: true,
),
);
await vpn.start();
Advanced Settings Surface (Dashboard-style)
The package now exposes a typed settings model that matches modern VPN settings screens (advanced, route, DNS, inbound, TLS tricks, WARP, misc):
final settings = SingboxFeatureSettings(
advanced: const AdvancedOptions(
memoryLimit: true,
debugMode: false,
logLevel: 'warn',
),
route: const RouteOptions(
region: 'other',
blockAdvertisements: true,
bypassLan: true,
resolveDestination: true,
ipv6RouteMode: SingboxIpv6RouteMode.disable,
),
dns: const DnsOptions(
providerPreset: DnsProviderPreset.cloudflare,
remoteDns: 'udp://1.1.1.1',
directDns: '1.1.1.1',
enableDnsRouting: true,
),
inbound: const InboundOptions(
serviceMode: SingboxServiceMode.vpn,
strictRoute: true,
tunImplementation: SingboxTunImplementation.gvisor,
mixedPort: 12334,
transparentProxyPort: 12335,
shareVpnInLocalNetwork: false,
includePackages: <String>['com.android.chrome'],
excludePackages: <String>['com.example.bank'],
),
tlsTricks: const TlsTricksOptions(
enableTlsFragment: true,
tlsFragmentSize: IntRange(10, 30),
tlsFragmentSleep: IntRange(2, 8),
),
warp: const WarpOptions(
enableWarp: false,
detourMode: WarpDetourMode.detourProxiesThroughWarp,
),
misc: const MiscOptions(
connectionTestUrl: 'http://cp.cloudflare.com',
urlTestInterval: Duration(minutes: 10),
clashApiPort: 16756,
),
);
await vpn.applyProfile(
profile: profile,
featureSettings: settings,
);
Custom DNS provider presets and fully custom DNS are both supported:
final googleDns = DnsOptions.fromProvider(
preset: DnsProviderPreset.google,
);
const customDns = DnsOptions(
providerPreset: DnsProviderPreset.custom,
remoteDns: 'https://dns.nextdns.io/your-id',
directDns: '45.90.28.0',
enableDnsRouting: true,
);
InboundOptions.includePackages and InboundOptions.excludePackages map to
sing-box TUN include_package / exclude_package.
Use InboundOptions.splitTunnelingEnabled to explicitly control behavior:
true: apply package filters.false: ignore package filters (full tunnel).null(default): backward-compatible mode, apply filters only when lists are non-empty.
State + Traffic Stream
You can consume live stats directly instead of polling:
final stateSub = vpn.stateStream.listen((state) {
print('state=${state.wireValue}');
});
final statsSub = vpn.statsStream.listen((stats) {
print('downloadSpeed=${stats.downloadSpeed}');
print('uploadSpeed=${stats.uploadSpeed}');
print('totalDownloaded=${stats.totalDownloaded}');
print('totalUploaded=${stats.totalUploaded}');
});
Detailed connection state (validation, private DNS status, interface, transport) is also available:
final detailSub = vpn.stateDetailsStream.listen((snapshot) {
print('state=${snapshot.state.wireValue} detail=${snapshot.detailCode}');
print('validated=${snapshot.networkValidated} privateDns=${snapshot.privateDnsServerName}');
});
final current = await vpn.getStateDetails();
print(current.toMap());
If your fork/custom core needs non-standard experimental fields, use:
TlsTricksOptions.rawOutboundPatchSingboxFeatureSettings.rawConfigPatch
This allows custom patching without losing typed settings for UI rendering.
Official core compatibility notes:
tls.fragmentis emitted in official-compatible form (boolean).- Non-standard TLS keys like
mixed_sni_caseandpaddingare removed for officialSagerNet/sing-boxcore compatibility. - TLS tricks are applied only to TLS-capable outbound types (
vless,vmess,trojan,anytls).
Parse Share Links
You can parse common VPN links directly and apply them:
final parsed = vpn.parseConfigLink(
'vless://uuid@host:443?type=ws&security=tls#node-1',
);
await vpn.applyConfigLink(
configLink: 'ss://base64-userinfo@host:8388#ss-node',
bypassPolicy: const BypassPolicy(
preset: BypassPolicyPreset.aggressive,
),
throttlePolicy: const TrafficThrottlePolicy(
enableMultiplex: false,
),
);
WebSocket compatibility notes:
- Early Data is disabled by default in generated WS transport (
max_early_data: 0). pathquery hints used by some share links (ed/eh) are sanitized out to avoid strict CDN404upgrades.- For strict TLS endpoints,
alpn=nonecan be used in links to produce empty ALPN (tls.alpn=[]).
Parser coverage includes:
sbmm://(encrypted wrapper for a supported inner link)vless://vmess://trojan://ss:///shadowsocks://hysteria:///hysteria2:///hy2://tuic://wireguard:///wg://- WireGuard
wg-quicktext blocks ([Interface]+[Peer]) ssh://
You can paste wg-quick text directly:
const String wgQuick = '''
[Interface]
PrivateKey = ...
Address = 10.0.0.2/32
[Peer]
PublicKey = ...
Endpoint = 203.0.113.20:31543
''';
final ParsedVpnConfig parsed = vpn.parseConfigLink(wgQuick);
await vpn.connectManualProfile(profile: parsed.profile);
For UI rendering, you can extract endpoint summaries directly from links or subscriptions:
final VpnProfileSummary summary = vpn.extractConfigLinkSummary(
'vless://uuid@host:443?type=ws&security=tls#node-1',
);
print('remark=${summary.remark} host=${summary.host} port=${summary.port}');
final List<VpnProfileSummary> list = vpn.extractSubscriptionSummaries(
subscriptionTextOrBase64,
);
Auto + Manual Connect Methods
Use explicit UX-focused methods for common app flows:
// Manual: user pastes one link and taps Connect.
final manual = await vpn.connectManualConfigLink(
configLink: 'vless://uuid@host:443?security=tls#my-node',
);
print(manual.profile.tag);
// Auto: import subscription, optionally pick lowest latency, then connect.
final auto = await vpn.connectAutoSubscription(
rawSubscription: subscriptionTextOrBase64,
preferLowestLatency: true,
pingTimeout: const Duration(seconds: 2),
);
print(auto.selectedProfile?.tag);
If you expose endpoint list UI, you can combine manual and automatic switching:
await vpn.selectEndpoint(index: 2, reconnect: true); // manual select
await vpn.selectBestEndpointByPing(reconnect: true); // automatic select
SBMM Secure Protocol Layer (sbmm://)
Use sbmm:// to wrap any supported config link (vless, vmess, trojan, ss, hysteria2, tuic, wireguard, ssh) in an encrypted envelope:
const raw = 'vless://uuid@host:443?security=tls#edge-1';
const passphrase = 'your-strong-passphrase';
final sbmm = vpn.wrapSecureConfigLink(
configLink: raw,
passphrase: passphrase,
);
// Parse / connect directly using the same passphrase.
final parsed = vpn.parseConfigLink(
sbmm,
sbmmPassphrase: passphrase,
);
await vpn.connectManualConfigLink(
configLink: sbmm,
sbmmPassphrase: passphrase,
);
Crypto profile for sbmm:// envelopes:
AES-256-GCMPBKDF2-HMAC-SHA256- random per-link salt + nonce
Security notes:
- Keep passphrases out of logs and analytics.
- Prefer device-secure storage (Keystore/Keychain) for passphrase persistence.
sbmm://protects config confidentiality; it does not replace TLS/Reality transport security on the tunnel itself.
GFW Hardened Preset Pack
The package now includes a ready-to-use hardened preset pack:
GfwPresetMode.compatibilityGfwPresetMode.balanced(recommended default)GfwPresetMode.aggressiveGfwPresetMode.extreme
GfwPresetMode.extreme is intentionally strict and only allows:
vlesswith Reality (security=reality/ Reality public key)hysteria2tuic
When a profile is incompatible with extreme, preset connect APIs reject it
with SignboxVpnException(code: 'EXTREME_PRESET_PROTOCOL_BLOCKED').
final preset = GfwPresetPack.balanced();
await vpn.connectManualConfigLinkWithPreset(
configLink: 'vless://uuid@host:443?security=tls#edge-1',
preset: preset,
);
await vpn.connectAutoWithPreset(
rawSubscription: subscriptionTextOrBase64,
preset: GfwPresetPack.aggressive(),
preferLowestLatency: true,
);
You can list all preset packs for UI:
final presets = vpn.listGfwPresetPacks();
Extract + Reconfigure From Config Files
When you already have a Sing-box JSON config file, parse it and extract
UI-friendly endpoint fields (ip/server, port, remark):
final document = vpn.parseConfigDocument(configJsonString);
final endpoints = document.endpointSummaries();
for (final endpoint in endpoints) {
print(
'remark=${endpoint.remark} server=${endpoint.server} port=${endpoint.serverPort}',
);
}
document.updateEndpoint(
outboundIndex: 0,
server: '8.8.8.8',
serverPort: 10443,
remark: 'new-node',
advancedPatch: {
'transport': {'type': 'grpc', 'service_name': 'vpn'},
},
);
document.applyAdvancedOverride({
'experimental': {
'clash_api': {'external_controller': '127.0.0.1:9090'},
},
});
await vpn.applyConfigDocument(document);
Multi-Endpoint Rotation + Auto Failover
Build an endpoint pool and let the SDK rotate/fail over automatically:
final endpoints = <VpnProfile>[
vpn.parseConfigLink('vless://...#edge-a').profile,
vpn.parseConfigLink('hysteria2://...#edge-b').profile,
vpn.parseConfigLink('ss://...#edge-c').profile,
];
await vpn.applyEndpointPool(
profiles: endpoints,
options: const EndpointPoolOptions(
autoFailover: true,
rotationStrategy: EndpointRotationStrategy.healthiest,
healthCheck: VpnHealthCheckOptions(
checkInterval: Duration(seconds: 8),
noTrafficTimeout: Duration(seconds: 45),
maxConsecutiveFailures: 2,
failoverOnNoTraffic: true,
failoverOnError: true,
failoverOnDisconnect: true,
),
),
);
await vpn.startManaged();
Manual rotation:
await vpn.rotateEndpoint(reconnect: true);
Ping Check / URL Test Support
You can now run reachability-latency checks from Dart:
final ping = await vpn.pingProfile(profile: profile);
print('ok=${ping.success} latencyMs=${ping.latencyMs} error=${ping.error}');
final poolResults = await vpn.pingEndpointPool();
For managed failover, enable ping in health options:
await vpn.applyEndpointPool(
profiles: endpoints,
options: const EndpointPoolOptions(
autoFailover: true,
healthCheck: VpnHealthCheckOptions(
pingEnabled: true,
pingTimeout: Duration(seconds: 3),
failoverOnPingFailure: true,
),
),
);
You can also fail over when HTTP connectivity probe fails (useful when tunnel is "connected" but internet is blocked):
await vpn.applyEndpointPool(
profiles: endpoints,
options: const EndpointPoolOptions(
autoFailover: true,
healthCheck: VpnHealthCheckOptions(
connectivityProbeEnabled: true,
connectivityProbeUrl: 'http://cp.cloudflare.com',
connectivityProbeTimeout: Duration(seconds: 8),
failoverOnConnectivityFailure: true,
maxConsecutiveFailures: 1,
),
),
);
Subscription Import Pipeline
Import plain-text or base64-encoded subscription content:
final result = await vpn.importSubscription(
rawSubscription: subscriptionTextOrBase64,
source: 'my-provider',
connect: true,
options: const EndpointPoolOptions(
autoFailover: true,
rotationStrategy: EndpointRotationStrategy.healthiest,
),
);
print('Imported: ${result.importedCount}');
print('Invalid: ${result.invalidCount}');
print('Active: ${result.appliedProfile?.tag}');
Android notes
- Android runtime uses embedded libbox JNI artifacts (
android/libs/libbox.jar+android/src/main/jniLibs/<abi>/libbox.so). - The
androidBinaryAssetByAbioption remains available for compatibility, but Android VPN runtime is driven by libbox JNI lifecycle. - Example assets still use
sing-boxfilenames underassets/singbox/android/<abi>/sing-box. - Call
requestVpnPermission()beforestart(). - On Android 13+, also call
requestNotificationPermission()beforestart(). - The VPN notification now uses a dedicated monochrome small icon (
ic_stat_singbox_mm).- If you want a custom status-bar icon, provide your own
ic_stat_singbox_mmdrawable in the host app.
- If you want a custom status-bar icon, provide your own
- While connected, Android foreground notification now shows:
- live
Up/Downspeed only - session duration (chronometer)
- live
- Android service recovery is sticky and stateful:
- uses
START_REDELIVER_INTENTfor start/restart actions. - restores last config/state after process death (when possible).
- uses
- You can reduce Flutter-side stats overhead by increasing runtime stats interval:
SingboxRuntimeOptions(statsEmitIntervalMs: 1500)(range250..10000).
- Android excludes the VPN app package from TUN to avoid self-capture loops.
probeConnectivity()and UID-basedgetStats()can still show activity even when user-app traffic is failing.- For real tunnel health, prefer
stateDetailsStream(networkValidated,hasInternetCapability,detailCode) plus a real external app test (browser/Telegram/etc).
- DNS requests to the TUN gateway (
172.19.0.2:53) are explicitly routed todns-outbefore private-network bypass rules. - In strict-route VPN mode, the TUN inbound keeps an IPv6 address even when
ipv6RouteMode=disableto prevent app-level IPv6No route to hostfailures during mixed IPv4/IPv6 app traffic.- This prevents
ip_is_private -> directfrom blackholing app DNS on Android.
- This prevents
- The service applies a strict-Private-DNS compatibility patch on Android:
- Detects active strict Private DNS hostname.
- Adds a direct bootstrap DNS server (
1.1.1.1) for that hostname lookup. - Adds direct route exceptions for the strict Private DNS host and TCP/853.
- If Android still shows
PRIVATE_DNS_BROKEN, your strict DNS provider may block validation probe domains (*.dnsotls-ds.metric.gstatic.com). In that case, switch Private DNS toAutomatic/Offor use a less filtering strict DNS provider. - Ensure your final app architecture includes a compliant VPN service strategy for production distribution.
- Release builds must keep gomobile bridge classes (
go.Seq,go.*) for libbox JNI.- This package now ships Android consumer keep rules (
android/consumer-rules.pro) for host apps enabling shrink/obfuscation.
- This package now ships Android consumer keep rules (
Android Build Size Notes
Large release APK size is expected when shipping all native ABIs because each ABI includes its own libbox.so.
For pub.dev distribution, this package includes mobile ABIs only:
arm64-v8aarmeabi-v7a
Emulator ABIs (x86, x86_64) are excluded from the published tarball to stay under pub.dev extracted-size limits. If you need emulator ABI support, use a local/forked copy and include those JNI libs.
Recommended distribution strategy:
- Play Store: build
AAB(flutter build appbundle) so users receive only their device ABI split. - Direct APK distribution: build per-ABI artifacts (
flutter build apk --release --split-per-abi). - If your distribution never targets emulators, you can exclude
x86/x86_64ABIs in the host app release config.
Android 16 KB Page-Size Support
This package includes native alignment verification for Android page-size compatibility:
- 64-bit ABIs (
arm64-v8a,x86_64) require minimumLOADalignment2**14(16 KB). - 32-bit ABIs (
armeabi-v7a,x86) require minimumLOADalignment2**12(4 KB baseline).
Run:
./tool/check_android_page_size.sh
This check is also integrated into:
./tool/quality_gate.sh./tool/fetch_singbox_libbox_android.sh
Android Manifest Requirements
Your host app (or plugin merged manifest) must include:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<application>
<service
android:name=".SignboxLibboxVpnService"
android:exported="false"
android:permission="android.permission.BIND_VPN_SERVICE"
android:foregroundServiceType="specialUse">
<intent-filter>
<action android:name="android.net.VpnService" />
</intent-filter>
<property
android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
android:value="vpn" />
</service>
</application>
Runtime requirement (Android 13+):
- Request
POST_NOTIFICATIONSat runtime (vpn.requestNotificationPermission()).
PRIVATE_DNS_BROKEN Notes
When Android marks VPN as PRIVATE_DNS_BROKEN, the tunnel can still be connected.
Use detailed state diagnostics and logs together:
stateDetailsStream.detailCode == PRIVATE_DNS_BROKENstateDetailsStream.networkValidated == false- logcat
PROBE_PRIVDNS ... dnsotls-ds.metric.gstatic.com ... No address associated
If this happens:
- Try Private DNS
Automaticand retest. - Or switch strict provider to one that resolves Android validation probe domains.
- Keep VPN DNS bootstrap (
1.1.1.1) and direct TCP/853 exception enabled (already auto-patched by this package).
Network Handover Stress Test
You can run a stress test that repeatedly toggles Wi-Fi/Data while the VPN stays connected:
./tool/run_android_handover_stress.sh \
<device_id> \
'vless://uuid@host:443?security=tls#edge-1' \
120 \
8
For sbmm:// links:
./tool/run_android_handover_stress.sh \
<device_id> \
'sbmm://secure?data=...' \
120 \
8 \
'your-strong-passphrase'
The script runs example/integration_test/network_handover_stress_test.dart and enforces handover signal detection (NETWORK_HANDOVER).
Release Reliability Suite (App Reachability Matrix)
Run release-mode reliability checks with a probe matrix that targets browser + common app endpoints:
./tool/run_android_release_reliability_suite.sh \
<device_id> \
'vless://uuid@host:443?security=tls#edge-1' \
balanced \
75 \
10
For sbmm:// links:
./tool/run_android_release_reliability_suite.sh \
<device_id> \
'sbmm://secure?data=...' \
balanced \
90 \
10 \
'your-strong-passphrase'
Custom probe matrix and threshold:
./tool/run_android_release_reliability_suite.sh \
<device_id> \
'vless://uuid@host:443?security=tls#edge-1' \
aggressive \
120 \
10 \
'' \
'http://cp.cloudflare.com||https://play.google.com/generate_204||https://www.facebook.com||https://telegram.org||https://www.viber.com' \
0.75 \
false
The suite performs:
- release APK build/install/launch smoke check (crash detection)
- profile-mode automated instrumentation (
flutter drive --profile) for reliability sampling
Note: Flutter Driver does not support true non-web --release test execution.
It reports:
- VPN stability (
disconnected/errortransitions) - ping success ratio
- URL probe success ratio
- traffic progression (
maxTotalBytes) - optional validated network requirement
Quality Gate (9.5 Target)
Use the built-in quality gate before release:
./tool/quality_gate.sh
It enforces:
flutter analyzeflutter test- direct dependency freshness (
flutter pub outdated)
Package Structure
lib/src/config/
singbox_config_builder.dart # typed settings -> sing-box JSON
internal/ # builder + parser split modules
singbox_*.dart # dns/inbound/route builders
vpn_config_parser_*.dart # protocol-specific link parsers
vpn_subscription_parser_*.dart # subscription payload + entry parsing
vpn_config_parser.dart # sbmm + vmess/vless/ss/trojan/hysteria/tuic links
sbmm_secure_link_codec.dart # sbmm secure envelope codec
vpn_subscription_parser.dart # subscription decode + dedupe
singbox_config_document.dart # read/update raw sing-box JSON
lib/src/core/
singbox_mm_client.dart # core state holder + lightweight class shell
internal/
singbox_mm_client_api_*.dart # public API wrappers (config/runtime/subscription/diagnostics)
singbox_mm_client_orchestration_*.dart # apply/connect/import execution flows
singbox_mm_client_diagnostics_*.dart # profile validation + probe + report assembly
singbox_mm_client_health_*.dart # health tick/monitor/failover logic
singbox_mm_client_lifecycle_*.dart # init/cleanup/managed-state handlers
singbox_mm_client_endpoint_*.dart # endpoint rotation/selection/health scoring
singbox_mm_client_{platform,network,document,utils,foundation}.dart
singbox_mm_exception.dart
lib/src/models/
vpn_profile.dart # protocol profile model
singbox_feature_settings.dart # advanced settings surface
vpn_connection_snapshot.dart # detailed state diagnostics
vpn_runtime_stats.dart # traffic stats model
gfw_preset_pack.dart # hardened presets
...
android/src/main/kotlin/com/signbox/singbox_mm/
SingboxMmPlugin.kt # Flutter method/event bridge
SignboxLibboxVpnService.kt # thin Android VpnService shell
VpnServiceRuntimeGraph.kt # top-level runtime composition root
VpnServiceNotificationGraph.kt # notification runtime + live ticker wiring
VpnServicePlatformGraph.kt # platform/tun/default-network monitor wiring
VpnServiceControlGraph.kt # runtime state/action/lifecycle orchestration
Core Internals Map
singbox_mm_client_api_*.dart: PublicSignboxVpnAPI wrappers grouped by domain (config, runtime, subscription, diagnostics).singbox_mm_client_orchestration_*.dart: High-level connect/apply/import flows and shared orchestration helpers.singbox_mm_client_diagnostics_*.dart: Profile validation, connectivity probing, and diagnostics report collection/assembly.singbox_mm_client_health_*.dart: Health monitor scheduling, tick evaluation, and endpoint failover logic.singbox_mm_client_lifecycle_*.dart: Initialization, cleanup/reset, and managed state-stream handlers.singbox_mm_client_endpoint_*.dart: Endpoint selection/rotation strategy, health scoring, and throttle/MTU candidate policy.singbox_mm_client_platform.dart: Method-channel calls for config/control/permission operations.singbox_mm_client_network.dart: Ping implementations and endpoint pool latency checks.singbox_mm_client_document.dart: Raw sing-box JSON document parse/extract/apply helpers.singbox_mm_client_utils.dart: Shared stateless parsing/permission helpers.singbox_mm_client_foundation.dart: Small foundational types and guard wrappers used across modules.
Performance Notes
- Keep
statsEmitIntervalMsat1000-2000for UI updates without extra battery use. - Endpoint-pool ping checks are parallelized (bounded worker pool) for faster large-subscription health scans.
- Android stats stream now de-duplicates unchanged payloads and emits periodic heartbeat updates to reduce channel overhead while keeping UI live.
- Default TUN stack is
gvisorfor better app compatibility on restrictive networks/devices. - If your target devices are stable with
system, switching tosystemcan reduce overhead. - Prefer
balancedpreset for long sessions;aggressive/extremeincrease CPU/battery. - Avoid excessive endpoint pool size on low-memory devices; keep health checks focused.
Runtime Stats Formatting
VpnRuntimeStats includes built-in helpers for UI rendering:
final stats = await vpn.getStats();
print(stats.formattedDownloadSpeed); // e.g. 52.10 KB/s
print(stats.formattedUploadSpeed); // e.g. 4.22 KB/s
print(stats.formattedTotalDownloaded); // e.g. 390.10 MB
print(stats.formattedTotalUploaded); // e.g. 12.45 MB
print(stats.formattedDuration); // e.g. 01:24:53
Background/Resume Stability
Call syncRuntimeState() when the app returns to foreground so UI state and
stats baseline rehydrate even after process recreation:
class _MyState extends State<MyPage> with WidgetsBindingObserver {
final SignboxVpn vpn = SignboxVpn();
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
unawaited(vpn.syncRuntimeState());
}
}
}
Official libbox reference
This repository includes a helper script to sync official SagerNet/sing-box Android libbox artifacts:
./tool/fetch_singbox_libbox_android.sh
The script builds and syncs:
android/libs/libbox.jarandroid/src/main/jniLibs/<abi>/libbox.soexample/assets/singbox/android/<abi>/sing-box
Important:
- Official
sing-boxreleases ship CLI binaries; Androidlibboxintegration is built from source viagomobile. - JNI Android integration requires matching Java package + native symbols (
io.nekohasekai.libbox+libbox.so). - Running only a standalone CLI via
ProcessBuilderis not enough for a full VPN lifecycle on Android. - Third-party attribution and license notes for bundled binaries are documented in
THIRD_PARTY_NOTICES.md.
iOS notes
iOS does not allow launching VPN binaries directly from app sandbox code.
You must integrate a Packet Tunnel Network Extension and bind it to a sing-box core strategy.
This plugin keeps the API consistent on iOS but returns IOS_EXTENSION_REQUIRED from startVpn until extension wiring is provided by the host app.
What this package gives you
- Strongly typed config model and generator for
sing-boxJSON. - Stable Flutter method/event channel contract.
- Android runtime libbox (JNI) bridge.
- iOS-compatible API surface with explicit extension requirement.
Support
Tron (TRC20): TLbwVrZyaZujcTCXAb94t6k7BrvChVfxzi
License
Project license: MIT-style license in LICENSE.
Third-party runtime binaries and notices: THIRD_PARTY_NOTICES.md.