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

1,698 阅读5分钟

如何让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

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

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(上下文)

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内)

System.putString(this.getContentReslover(),"Nightmare_Test_Key","我想把Text的内容改成这个");

只要注册了监听的那个App还在运行,其中的那个TextView的内容就会被更改 #Broadcast Broadcast作为安卓四大组件之一,其作用也是相当的强大,具体就不详细阐述,有点类似于EventBus,但安卓的Broadcast可以贯穿安卓所有在运行的App,那么我们怎么用Broadcast来实现跨应用更新状态呢?

自定义一个Broadcast

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文件

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中执行

Intent intent = new Intent();
intent.putExtra("Test_Key","来自其他应用的消息");
intent.setAction("test.android.intent.action.TEST");
//使用bundle传递参数
sendBroadcast(intent);

不是说Flutter吗?

以下的Example就不用ContentObserver了,使用Broadcast,直接上代码,嘿嘿嘿😜

继续上代码,首先是发送端的安卓部分

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部分


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);
              },
            )
          ],
        ),
      ),
    );
  }
}

接收端的安卓部分

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部分


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与原生的混合项目