Flutter跨应用更改状态在安卓端的实现

如何让 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//重写ContentObserver的onChange方法
public void onChange(boolean z) {
//此方法当监听的值改变后会触发,这里将消息发送给一个Handler处理
Message obtainMessage=mHandler.obainMessage();//
obtainMessage.obj=System.getString(mContext.getContentResolver(),"Nightmare_Test_Key"));
//拿到Nightmare_Text_Key的新值并将它发送给Handler处理
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;//ContentObserver那边传过来的值
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");
//使用bundle传递参数
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 原生的双向调用而已

看完估计会想,谁**会有这种需求啊?

个人项目实际需求

smali1.gif

上图是 Flutter App(MToolkit)控制原生 App(SystemUI)的状态,当然这个原生应用被我反编译植入布局,下篇可能会详细说哦,才开始写帖子,哪儿有不对的地方希望大家多多指出哦 😬
最后Mtoolkit,也是本人的 Flutter 与原生的混合项目
作者

梦魇兽

发布于

2019-10-28

更新于

2023-03-11

许可协议

评论