2024年7月24日 星期三

flutter第五彈,最後一哩路,v2ray專案,加上生命週期,遞迴bug修正,程式碼優化

聲明:本篇網誌沒有任何人被罵或受傷,請勿獵巫

正文開始

那個無限遞迴重整果然被開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交出去,然後領績效獎金!!(咦!!

沒有留言:

張貼留言