如何让 Flutter 程序可以跨应用的更改其他应用的状态呢,先看原生如何实现,其次再用 MethodChannel 对接 Flutter 就行了,所以此篇更多的是安卓原生开发的知识,提到原生开发跨应用发送消息或者更改状态,有两个东西能够实现这样的需求,一个是 ContentObserver,另一个就是 Broadcast
ContentOberver
ContentObserver 被称为安卓的内容观察者,目的是观察特定 Uri 引起的数据变化,例如通过一个 key(String)来监听这个 key 对应值的变化,如果该值发生了变化,那么整个系统中所有对这个 key 做了监听的地方都能知道它被更改了,从而来根据这个 key 对应的新的值来刷新相关 Widget 的状态,这些值都被保存在了/data/system/users/0/下的 settings_global.xml,settings_system.xml,settings_secure.xml,分别对应三种不同类型的 key,
global:所有应用都能够访问并且更改
system:只有系统级别的应用能进行监听及更改,或者用 su 权限进行更改,亦或者降低 app 的编译版本然后导入 android.permission.WRITE_SETTINGS 这个权限即可
secure:安全级别最高,用来保存整个安卓系统的一些安全设置,更改方式同上,不过需要 android.permission.WRITE_SECURE_SETTINGS 这个权限
所以我们实现内容观察者的监听需要啥?
一个 ContentObserver,一个 Handler(可省略),比如我们对”Nightmare_Test_Key”进行监听
首先自定义一个 ContentObserver
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| class MyObserver extends ContentObserver { final Handler mHandler; final Context mContext; public MyObserver(Context context,Handler handler) { super(handler); this.mHandler=handler; this.mContext=context; } @Override public void onChange(boolean z) { Message obtainMessage=mHandler.obainMessage(); obtainMessage.obj=System.getString(mContext.getContentResolver(),"Nightmare_Test_Key")); mHandler.sedMessage(obtainMessage); } }
|
再自定义一个 Handler
1 2 3 4 5 6 7 8 9 10 11
| class MyHandler extends Handler { final TextView mTextView; MyHandler(TextView view) { this.mTextView = view; } @Override public void handleMessage(Message message) { String str = (String) message.obj; this.mTextView.setText(TextUtils.isEmpty(str) ? "未获取到数据" : str); } }
|
贴上关键代码,以下代码需要放下 Activty 的生命周期中,如果是在一个 View 的生命周期中实现这样的监听,需要将所有的 this 更改成 this.getContext(),亦或者通过其他的方式拿到安卓的 Context(上下文)
1 2 3 4 5
| TextView mTextView=new TextView(this); Handler mHandler = new MyHandler(mTextView); ContentObserver mContextObserser=new MyObserver(this,mHandler); this.getContentResolver().registerContentObserver(System.getUriFor("Nightmare_Test_Key"),false,mContextObserser); //第二个参数false表示精准匹配,即值匹配该Uri
|
这样一个完整的监听就写好了,我们只需要在任意 App 内调用(Activity 内)
1
| System.putString(this.getContentReslover(),"Nightmare_Test_Key","我想把Text的内容改成这个");
|
只要注册了监听的那个 App 还在运行,其中的那个 TextView 的内容就会被更改
#Broadcast
Broadcast 作为安卓四大组件之一,其作用也是相当的强大,具体就不详细阐述,有点类似于 EventBus,但安卓的 Broadcast 可以贯穿安卓所有在运行的 App,那么我们怎么用 Broadcast 来实现跨应用更新状态呢?
自定义一个 Broadcast
1 2 3 4 5 6 7 8 9 10 11
| class MyBroadcastReceiver extends BroadcastReceiver{ final TextView mView; public MyBroadcastReceiver(TextView v){ this.mView=v; } @Override public void onReceive(Context context, Intent intent) { String result = intent.getStringExtra("Test_Key") ; this.mView.setText(result); } }
|
我们这里注册”test.android.intent.action.TEST”这个自定义广播,整个广播注册 方式为动态注册,不涉及 xml 文件
1 2 3 4 5
| TextView mTextView=new TextView(this); BroadcastReceiver mBroadcastReceiver = new MyBroadcastReceiver(mTextView); IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction("test.android.intent.action.TEST"); this.registerReceiver(broadcastReceiver,intentFilter);
|
整个广播注册完成,接下来我们来发送一个广播,以下代码可在另外的 App 中执行
1 2 3 4 5
| Intent intent = new Intent(); intent.putExtra("Test_Key","来自其他应用的消息"); intent.setAction("test.android.intent.action.TEST");
sendBroadcast(intent);
|
不是说 Flutter 吗?
以下的 Example 就不用 ContentObserver 了,使用 Broadcast,直接上代码,嘿嘿嘿 😜
继续上代码,首先是发送端的安卓部分
1 2 3 4 5 6 7 8 9 10 11 12 13
| class MainActivity: FlutterActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) GeneratedPluginRegistrant.registerWith(this) MethodChannel(flutterView, "Nightmare").setMethodCallHandler { call, _ -> val intent = Intent() intent.putExtra("Test_Key",call.method) intent.action = "test.android.intent.action.TEST" sendBroadcast(intent) } } }
|
怎么又是 Kotlin 了?
我也不想半路换 Kotlin,新建这个 Example 就是了,不过看起来比 Java 要简洁多了
####Dart 部分
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| class MyHomePage extends StatefulWidget { MyHomePage({Key key, this.title}) : super(key: key); final String title; @override _MyHomePageState createState() => _MyHomePageState(); }
class _MyHomePageState extends State<MyHomePage> { MethodChannel _channel=MethodChannel("Nightmare"); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( centerTitle: true, title: Text(widget.title), ), body: Center( // Center is a layout widget. It takes a single child and positions it // in the middle of the parent. child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ RaisedButton( child: Text("添加一个按钮"), onPressed: (){ _channel.invokeMethod("Button"); }, ), RaisedButton( child: Text("添加一个Card"), onPressed: (){ _channel.invokeMethod("Card"); }, ), TextField( onSubmitted: (str){ _channel.invokeMethod(str); }, ) ], ), ), ); } }
|
接收端的安卓部分
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| class MainActivity: FlutterActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) GeneratedPluginRegistrant.registerWith(this) val methodChannel = MethodChannel(flutterView, "Nightmare") class MyBroadcastReceiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { val result = intent.getStringExtra("Test_Key") methodChannel.invokeMethod(result,"") } } val mBroadcastReceiver = MyBroadcastReceiver() val intentFilter = IntentFilter() intentFilter.addAction("test.android.intent.action.TEST") this.registerReceiver(mBroadcastReceiver, intentFilter) } }
|
Dart 部分
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| class MyHomePage extends StatefulWidget { MyHomePage({Key key, this.title}) : super(key: key); final String title; @override _MyHomePageState createState() => _MyHomePageState(); }
class _MyHomePageState extends State<MyHomePage> { List<Widget> _list = [];
MethodChannel platform = MethodChannel("Nightmare");
@override void initState() { super.initState(); platform.setMethodCallHandler(platformCallHandler); }
Future<dynamic> platformCallHandler(MethodCall call) async { print(call.method); switch (call.method) { case "Button": _list.add( RaisedButton( onPressed: () {}, child: Text("按钮"), ), ); setState(() {}); break; case "Card": _list.add( Card( child: Text("Card"), ) ); setState(() {}); break; default: _list.add( Text(call.method) ); setState(() {}); break; } }
@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.title), centerTitle: true, ), body: Center( child: Column( children: _list, ), ), ); } }
|
看一下预览图
其实就是用 MethodChannel 实现了 Flutter 到 Android 原生的双向调用而已
看完估计会想,谁**会有这种需求啊?
个人项目实际需求