From 2137f4b3f21b63cf98c7a2091e2f9bc23792d0b9 Mon Sep 17 00:00:00 2001 From: csf Date: Tue, 8 Feb 2022 22:45:48 +0800 Subject: [PATCH] update ui --- android_doc.md | 44 ++++++++++++++++++++++++++++++++- lib/common.dart | 8 +++--- lib/model.dart | 59 +++++++++++++++++++++++++++++++++++++++----- lib/server_page.dart | 41 +++++++++++++++++++----------- pubspec.lock | 7 ++++++ pubspec.yaml | 3 ++- 6 files changed, 136 insertions(+), 26 deletions(-) diff --git a/android_doc.md b/android_doc.md index 4b0eef445..3cc280f3b 100644 --- a/android_doc.md +++ b/android_doc.md @@ -51,7 +51,7 @@ MediaProjectionManager -> MediaProjection ... android:foregroundServiceType="mediaProjection"/> ``` -- API大于O(26)时需要startForegroundService,且需要正确设置通知栏, +- API大于O(26/Android8.0)时需要startForegroundService,且需要正确设置通知栏, 新特性中使用ForegroundService不会被系统杀掉 @@ -247,6 +247,48 @@ Config::set_option("stop_service","") 首次登录不显示id密码 安卓前后分离的问题 通过IPC或者广播解耦 +### 关于安卓的service和进程 +实际测试 安卓7和安卓11表现不同 同一个apk下若有多个activity或service +安卓7 关闭activity后所有的服务都会强制关闭 可能是锤子手机特有 + +安卓8.1 和7类似 且安卓8.1和7录屏权限比较宽松 只需要获取一次 不需要每次播放都要获取录屏权限 + +*安卓7/8.1关闭activity 后就关闭service 可能是锤子OS的特质 + +理论上 非bind启动的service可以脱离activity运行 就像三星安卓11上测试的情况 + +安卓11 关闭activity后service可以单独运行 可能由于前台应用可以持续维持 + 再次进入程序新的activity会共用之前在内存中的so程序 + +安卓Service运行在主线程!才能实现脱离activity独立运行 + +>只有在内存过低且必须回收系统资源以供拥有用户焦点的 Activity 使用时,Android 系统才会停止服务。如果将服务绑定到拥有用户焦点的 Activity,则它其不太可能会终止;如果将服务声明为在前台运行,则其几乎永远不会终止。如果服务已启动并长时间运行,则系统逐渐降低其在后台任务列表中的位置,而服务被终止的概率也会大幅提升—如果服务是启动服务,则您必须将其设计为能够妥善处理系统执行的重启。如果系统终止服务,则其会在资源可用时立即重启服务,但这还取决于您从 onStartCommand() 返回的值。 + +>如服务文档中所述,您可以创建同时具有已启动和已绑定两种状态的服务。换言之,您可以通过调用 startService() 来启动服务,让服务无限期运行,您也可以通过调用 bindService() 让客户端绑定到该服务。 +如果您确实允许服务同时具有已启动和已绑定状态,那么服务启动后,系统不会在所有客户端均与服务取消绑定后销毁服务,而必须由您通过调用 stopSelf() 或 stopService() 显式停止服务。 +尽管您通常应实现 onBind() 或 onStartCommand(),但有时也需要同时实现这两种方法。例如,音乐播放器可能认为,让其服务无限期运行并同时提供绑定很有用处。如此一来,Activity 便可启动服务来播放音乐,并且即使用户离开应用,音乐播放也不会停止。然后,当用户返回应用时,Activity 便能绑定到服务,重新获得播放控制权。 + +onRebind() + +[进程和应用生命周期](https://developer.android.com/guide/components/activities/process-lifecycle?hl=zh-cn) + +[进程和线程概览](https://developer.android.com/guide/components/processes-and-threads?hl=zh-cn) + +[绑定服务概览](https://developer.android.com/guide/components/bound-services?hl=zh-cn) + +Service持久化与绑定具体操作 [已测试安卓7.1以上系统特性相同] +1.前台服务 service中调用startForeground,启用持久化Service需要保证至少一次通过startForegroundService/startService 启动了Service且在Service中主动startForeground,可以通过intent传参指定一次init操作,在init的过程中startForeground,最关键的操作就是startForeground +即 通过startService 调用过的Service并且Service中调用过startForeground的Service就是持久化的前台服务,服务不会被系统kill +2.通过使用bindService将Activity与Service绑定 使用unbindService在onDestroy中解绑,如果不解绑会造成对Service的引用泄漏引发错误。可以在Activity中的onCreate中进行绑定,也可以根据需求按需手动进行绑定,bindService startService的先后顺序无所谓 +*只要注意至少一次对Service调用过startForegroundService/startService* + +关于startForegroundService +https://developer.android.com/about/versions/oreo/background?hl=zh-cn#services +尽量使用startForegroundService传递start命令 注意首次使用的时候需要在5秒内在服务中主动调用startService + +改成bindService逻辑 +直接在activity onCreate时候进行绑定 onDestroy时解绑(注意判空),绑定的时候进行一些判断。如果已存在服务会话则恢复之前的情况。如果不存在不服务会话则等待需要的时候再启动前台服务 +
### 其他 diff --git a/lib/common.dart b/lib/common.dart index 9549c8b93..f425ddfed 100644 --- a/lib/common.dart +++ b/lib/common.dart @@ -219,10 +219,10 @@ toAndroidChannelInit() { switch (call.method) { case "try_start_without_auth": { - var peerID = call.arguments["peerID"] as String; - var name = call.arguments["name"] as String; - ServerPage.serverModel.setPeer(false, name: name, id: peerID); - showLoginReqAlert(nowCtx, peerID, name); + // 可以不需要传递 通过FFI直接去获取 serverModel里面直接封装一个update通过FFI从rust端获取 + ServerPage.serverModel.updateClientState(); + debugPrint("pre show loginAlert:${ServerPage.serverModel.isFileTransfer.toString()}"); + showLoginReqAlert(nowCtx, ServerPage.serverModel.peerID, ServerPage.serverModel.peerName); debugPrint("from jvm:try_start_without_auth done"); break; } diff --git a/lib/model.dart b/lib/model.dart index 83ed08238..e4f2ebd0c 100644 --- a/lib/model.dart +++ b/lib/model.dart @@ -4,6 +4,7 @@ import 'package:flutter/gestures.dart'; import 'package:path_provider/path_provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:device_info/device_info.dart'; +import 'package:external_path/external_path.dart'; import 'dart:io'; import 'dart:math'; import 'dart:ffi'; @@ -518,11 +519,37 @@ class CursorModel with ChangeNotifier { } } +class ClientState { + bool isStart; + bool isFileTransfer; + String name; + String peerId; + + ClientState({this.isStart, this.isFileTransfer, this.name, this.peerId}); + + ClientState.fromJson(Map json) { + isStart = json['is_start']; + isFileTransfer = json['is_file_transfer']; + name = json['name']; + peerId = json['peer_id']; + } + + Map toJson() { + final Map data = new Map(); + data['is_start'] = this.isStart; + data['is_file_transfer'] = this.isFileTransfer; + data['name'] = this.name; + data['peer_id'] = this.peerId; + return data; + } +} + class ServerModel with ChangeNotifier { bool _mediaOk; bool _inputOk; - bool _peerEnabled; + bool _isStart; + bool _isFileTransfer; String _peerName; String _peerID; @@ -530,7 +557,9 @@ class ServerModel with ChangeNotifier { bool get inputOk => _inputOk; - bool get peerEnabled => _peerEnabled; + bool get isStart => _isStart; + + bool get isFileTransfer => _isFileTransfer; String get peerName => _peerName; @@ -539,7 +568,7 @@ class ServerModel with ChangeNotifier { ServerModel() { _mediaOk = false; _inputOk = false; - _peerEnabled = false; + _isStart = false; _peerName = ""; _peerID = ""; } @@ -559,14 +588,28 @@ class ServerModel with ChangeNotifier { } setPeer(bool enabled, {String name = "", String id = ""}) { - _peerEnabled = enabled; + _isStart = enabled; if (name != "") _peerName = name; if (id != "") _peerID = id; notifyListeners(); } + updateClientState() { + var res = FFI.getByName("client_state"); + debugPrint("getByName client_state string:$res"); + try { + var clientState = ClientState.fromJson(jsonDecode(res)); + _isStart = clientState.isStart; + _isFileTransfer = clientState.isFileTransfer; + _peerName = clientState.name; + _peerID = clientState.peerId; + debugPrint("updateClientState:${clientState.toJson()}"); + } catch (e) {} + notifyListeners(); + } + clearPeer() { - _peerEnabled = false; + _isStart = false; _peerName = ""; _peerID = ""; notifyListeners(); @@ -575,7 +618,8 @@ class ServerModel with ChangeNotifier { class FFI { static String id = ""; - static String _dir = ''; + static String _dir = ""; + static String _homeDir = ""; static F2 _getByName; static F3 _setByName; static F4 _freeRgba; @@ -742,6 +786,7 @@ class FFI { .lookupFunction), F4>('free_rgba'); _getRgba = dylib.lookupFunction('get_rgba'); _dir = (await getApplicationDocumentsDirectory()).path; + _homeDir = (await ExternalPath.getExternalStorageDirectories())[0]; String id = 'NA'; String name = 'Flutter'; DeviceInfoPlugin deviceInfo = DeviceInfoPlugin(); @@ -754,8 +799,10 @@ class FFI { name = iosInfo.utsname.machine; id = iosInfo.identifierForVendor.hashCode.toString(); } + debugPrint("info1-id:$id,info2-name:$name,dir:$_dir,homeDir:$_homeDir"); setByName('info1', id); setByName('info2', name); + setByName('home_dir',_homeDir); setByName('init', _dir); } catch (e) { print(e); diff --git a/lib/server_page.dart b/lib/server_page.dart index 0e83ca801..f9e58c972 100644 --- a/lib/server_page.dart +++ b/lib/server_page.dart @@ -11,7 +11,7 @@ class ServerPage extends StatelessWidget { @override Widget build(BuildContext context) { - // TODO: implement build + checkService(); return ChangeNotifierProvider.value( value: serverModel, child: Scaffold( @@ -55,13 +55,24 @@ class ServerPage extends StatelessWidget { } } +void checkService() { + // 检测当前服务状态,若已存在服务则异步更新数据回来 + toAndroidChannel.invokeMethod("check_service"); // jvm + ServerPage.serverModel.updateClientState(); + // var state = FFI.getByName("client_state").split(":"); // rust + // var isStart = FFI.getByName("client_is_start") !="";// 使用JSON + // if(state.length == 2){ + // ServerPage.serverModel.setPeer(isStart,name:state[0],id:state[1]); + // } +} + class ServerInfo extends StatefulWidget { @override _ServerInfoState createState() => _ServerInfoState(); } class _ServerInfoState extends State { - var _passwdShow = true; + var _passwdShow = false; // TODO set ID / PASSWORD var _serverId = ""; @@ -96,7 +107,7 @@ class _ServerInfoState extends State { ), TextFormField( readOnly: true, - obscureText: _passwdShow, + obscureText: !_passwdShow, style: TextStyle( fontSize: 25.0, fontWeight: FontWeight.bold, @@ -144,7 +155,7 @@ class _PermissionCheckerState extends State { cardTitle("权限列表"), PermissionRow("媒体权限", serverModel.mediaOk, _toAndroidInitService), const Divider(height: 0), - PermissionRow("输入权限", serverModel.inputOk, _toAndroidCheckInput), + PermissionRow("输入权限", serverModel.inputOk, _toAndroidInitInput), const Divider(), serverModel.mediaOk ? ElevatedButton.icon( @@ -172,7 +183,9 @@ void showLoginReqAlert(BuildContext context, String peerID, String name) { child: Text("接受"), onPressed: () { FFI.setByName("login_res", "true"); - _toAndroidStartCapture(); + if(!ServerPage.serverModel.isFileTransfer){ + _toAndroidStartCapture(); + } ServerPage.serverModel.setPeer(true); Navigator.of(context).pop(); }), @@ -224,7 +237,7 @@ class ConnectionManager extends StatelessWidget { final serverModel = Provider.of(context); var info = "${serverModel.peerName != "" ? serverModel.peerName : "NA"}-${serverModel.peerID != "" ? serverModel.peerID : "NA"}"; - return serverModel.peerEnabled + return serverModel.isStart ? myCard(Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -239,7 +252,7 @@ class ConnectionManager extends StatelessWidget { icon: Icon(Icons.close), onPressed: () { FFI.setByName("close_conn"); - _toAndroidStopCapture(); + // _toAndroidStopCapture(); serverModel.setPeer(false); }, label: Text("断开连接")) @@ -286,10 +299,10 @@ Future _toAndroidStartCapture() async { debugPrint("_toAndroidStartCapture:$res"); } -Future _toAndroidStopCapture() async { - bool res = await toAndroidChannel.invokeMethod("stop_capture"); - debugPrint("_toAndroidStopCapture:$res"); -} +// Future _toAndroidStopCapture() async { +// bool res = await toAndroidChannel.invokeMethod("stop_capture"); +// debugPrint("_toAndroidStopCapture:$res"); +// } Future _toAndroidStopService() async { FFI.setByName("stop_service"); @@ -297,7 +310,7 @@ Future _toAndroidStopService() async { debugPrint("_toAndroidStopSer:$res"); } -Future _toAndroidCheckInput() async { - bool res = await toAndroidChannel.invokeMethod("check_input"); - debugPrint("_toAndroidStopSer:$res"); +Future _toAndroidInitInput() async { + bool res = await toAndroidChannel.invokeMethod("init_input"); + debugPrint("_toAndroidInitInput:$res"); } diff --git a/pubspec.lock b/pubspec.lock index 4ec2db940..6ec8f7ba5 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -85,6 +85,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.1" + external_path: + dependency: "direct main" + description: + name: external_path + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" fake_async: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index a6fa95449..9e0366015 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -30,8 +30,9 @@ dependencies: cupertino_icons: ^1.0.3 ffi: ^1.1.2 path_provider: ^2.0.2 + external_path: ^1.0.1 provider: ^5.0.0 - flutter_easyloading: + flutter_easyloading: # not Null safety 2.2.0 git: url: git://github.com/open-trade/flutter_easyloading #path: flutter_easyloading