聲明:本篇網誌沒有任何人被罵或受傷,請勿獵巫
正文開始
那個無限遞迴重整果然被開bug了,然後pm果然希望flutter背景執行要把vpn斷開,再來我也想把程式整理一下,我們開始以下故事吧
首先要監測app是否被移到背景執行需要有lifecycle,但flutter好像沒有相應pause的方法可以複寫,上網查好像也查不太到資料,幸好大神水哥又上演了神助攻
他推薦我一個plugin,看圖片就知道是他本人寫的,雖然沒上到官方hub,但以他技術應該是無虞,就直接git引用
# pubspec.yaml
dependencies:
...
mx_lifecycle:
git:
url: git@github.com:MagicalWater/mx_lifecycle.git
然後正常情況應該是會報 git@github.com: Permission denied
不要急,水哥繼續說,git套件安裝要走ssh,所以必須有ssh key,新增方法在這裡,產生pub key的指令就上網查,我就不贅述自己去看,完成就可以順利安裝完畢,至於使用範例,等等會在程式碼中
接著就上我們主菜吧
首先把V2ray連線拆成service
// lib/v2ray_service.dart
import 'package:flutter_v2ray/flutter_v2ray.dart';
import 'package:flutter/material.dart';
class V2rayService {
late final V2RayURL parser;
late final FlutterV2ray connection;
var status = ValueNotifier<V2RayStatus>(V2RayStatus());
var prevStatus = ValueNotifier<V2RayStatus>(V2RayStatus());
var hasReload = ValueNotifier<bool>(false);
bool get isConnected => status.value.state == "CONNECTED";
V2rayService(String connectText, Function updateView) {
parser = FlutterV2ray.parseFromURL(connectText);
connection = FlutterV2ray(
onStatusChanged: (_status) {
status.value = _status;
// 觀察狀態變化
if (status.value != prevStatus.value) {
prevStatus.value = status.value;
// 把可重整的值還原
hasReload.value = false;
}
updateView();
print("my status: ${status.value.state}");
},
);
}
init() {
return connection.initializeV2Ray();
}
connect() async {
if (await connection.requestPermission() && !isConnected) {
return connection.startV2Ray(
remark: parser.remark, config: parser.getFullConfiguration());
}
}
disconnect() {
if (isConnected) {
connection.stopV2Ray();
}
}
getVersion() {
return connection.getCoreVersion();
}
}
然後是主程式
// lib/main.dart
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:mx_lifecycle/mx_lifecycle.dart';
import "./v2ray_service.dart";
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter V2Ray',
theme: ThemeData(
useMaterial3: true,
brightness: Brightness.dark,
inputDecorationTheme: const InputDecorationTheme(
border: OutlineInputBorder(),
),
),
home: SafeArea(
top: true,
right: true,
bottom: true,
left: true,
child: V2rayWebPage(),
),
);
}
}
class V2rayWebPage extends StatefulWidget {
const V2rayWebPage({super.key});
@override
State<V2rayWebPage> createState() => V2rayWebPageState();
}
class V2rayWebPageState extends State<V2rayWebPage> {
late final V2rayService v2rayService = new V2rayService(
"vmess://......",
() => setState(() {}));
late InAppWebViewController webViewController;
String url = "https://www.xxx.com/";
@override
void initState() {
super.initState();
v2rayService.init();
Future.delayed(const Duration(milliseconds: 500), () async {
print("version: ${await v2rayService.getVersion()}");
v2rayService.connect();
});
listenLifecycle();
}
// 水哥套件,lifecycle監聽
void listenLifecycle() {
final lifecycle = MxLifecycle();
lifecycle.lifecycleStateStream.listen((event) async {
if (defaultTargetPlatform == TargetPlatform.android) {
final state = lifecycle.toAndroid(event);
switch (state) {
case AndroidLifecycleState.paused:
v2rayService.disconnect();
// 這裏暫存目前網址,以免到時重新連線又回到首頁
url = (await webViewController.getUrl()).toString();
break;
case AndroidLifecycleState.started:
v2rayService.connect();
break;
default:
break;
}
print('android狀態: $state');
}
// else if (defaultTargetPlatform == TargetPlatform.iOS) {
// final state = lifecycle.toIos(event);
// print('ios狀態: $state');
// }
});
}
@override
Widget build(BuildContext context) {
return PopScope(
canPop: false,
onPopInvoked: (bool didPop) async {
if (await webViewController?.canGoBack() ?? false) {
webViewController?.goBack();
} else {
SystemNavigator.pop();
}
},
child: Scaffold(
body: !v2rayService.isConnected
? Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
ElevatedButton(
child: new Text("Connect"),
onPressed: () => v2rayService.connect(),
),
Container(height: 20.0), //SizedBox(height: 20.0),
],
)
: InAppWebView(
initialUrlRequest: URLRequest(url: WebUri(url)),
onWebViewCreated: (controller) {
webViewController = controller;
},
onReceivedError: (controller, req, error) {
// 這裡只給一次重整機會,防止遞迴
if (!v2rayService.hasReload.value) {
controller.reload();
v2rayService.hasReload.value = true;
print("req: ${req}");
print("error: ${error}");
}
},
onConsoleMessage: (controller, consoleMessage) {
print('Console message: ${consoleMessage.message}');
},
initialSettings: InAppWebViewSettings(
mixedContentMode:
MixedContentMode.MIXED_CONTENT_ALWAYS_ALLOW),
)));
}
}
最後也是最重要的步驟,就是要耐心的等,等到老闆沒耐心的時候,再把apk交出去,然後領績效獎金!!(咦!!