Flutter跨應用更改狀態在安卓端的實現

Nightmare_夢魘獸發表於2019-10-28

如何讓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這個許可權

所以我們實現內容觀察者的監聽需要啥?

Flutter跨應用更改狀態在安卓端的實現

一個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嗎?

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了?

Flutter跨應用更改狀態在安卓端的實現
我也不想半路換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,
        ),
      ),
    );
  }
}
複製程式碼

看一下預覽圖

Flutter跨應用更改狀態在安卓端的實現

其實就是用MethodChannel實現了Flutter到Android原生的雙向呼叫而已

看完估計會想,誰**會有這種需求啊?

Flutter跨應用更改狀態在安卓端的實現

個人專案實際需求

smali1.gif

上圖是Flutter App(MToolkit)控制原生App(SystemUI)的狀態,當然這個原生應用被我反編譯植入佈局,下篇可能會詳細說哦,才開始寫帖子,哪兒有不對的地方希望大家多多指出哦?
最後Mtoolkit,也是本人的Flutter與原生的混合專案

相關文章