[Android]你不知道的Android程式化(5)--程式通訊Messenger框架

Cang_Wang發表於2018-04-16

大家好,我係蒼王。
以下是我這個系列的相關文章,有興趣可以參考一下,可以給個喜歡或者關注我的文章。

[Android]如何做一個崩潰率少於千分之三噶應用app--章節列表

宣傳一波新書,裡面介紹了眾多元件化程式設計技術,以及讓你對工程架構認識和理解有新的提升。

                    [Android]你不知道的Android程式化(5)--程式通訊Messenger框架
                                  Android元件化架構熱賣中

元件化群1已經滿員,可以加群2 763094035



上一節,介紹了使用AIDL的程式通訊框架。
這一節給大家介紹Messenger的通訊框架,而Messenger其意思是“信使”的意思
使用Messenger的優勢在於
1.實際傳遞的是Message,可以複用資訊池
2.支援資訊回撥
3.不需要編寫aidl

[Android]你不知道的Android程式化(5)--程式通訊Messenger框架
Messenger通訊原理圖


Messenger繼承了Parcelable介面,可以作為序列化物件用於傳輸。
這裡可以傳入Handler,Handler裡面有包含IMessenger物件

     /**
     * Create a new Messenger pointing to the given Handler.  Any Message
     * objects sent through this Messenger will appear in the Handler as if
     * {@link Handler#sendMessage(Message) Handler.sendMessage(Message)} had
     * been called directly.
     * 
     * @param target The Handler that will receive sent messages.
     */
    public Messenger(Handler target) {
        mTarget = target.getIMessenger();
    }
複製程式碼

或者傳入IBinder物件,Stub當中存在IMessenger物件

    /**
     * Create a Messenger from a raw IBinder, which had previously been
     * retrieved with {@link #getBinder}.
     * 
     * @param target The IBinder this Messenger should communicate with.
     */
    public Messenger(IBinder target) {
        mTarget = IMessenger.Stub.asInterface(target);
    }
複製程式碼

實際上Handler中IMessager實現物件是MessengerImpl

    final IMessenger getIMessenger() {
        synchronized (mQueue) {
            if (mMessenger != null) {
                return mMessenger;
            }
            mMessenger = new MessengerImpl();
            return mMessenger;
        }
    }
複製程式碼

這裡IMessenger是呼叫Handler的send方法來傳送訊息的。

    private final class MessengerImpl extends IMessenger.Stub {
        public void send(Message msg) {
            msg.sendingUid = Binder.getCallingUid();
            Handler.this.sendMessage(msg);
        }
    }
複製程式碼

每個Message當中也包含了一個replyTo的變數使用者回撥

    /**
     * Optional Messenger where replies to this message can be sent.  The
     * semantics of exactly how this is used are up to the sender and
     * receiver.
     */
    public Messenger replyTo;
複製程式碼

就這幾個步驟Messenger獨立的實現了Parcelable和使用aidl的通訊方式

接下來我們介紹一下Modular框架是用Messenger通訊設計。
不同於上一節介紹的ModularArchitecture的aidl中通訊是1對1的通訊,Modular提供的通訊框架是通過1對多的傳送方式來傳遞的。

以下是Messenger的註冊流程圖


[Android]你不知道的Android程式化(5)--程式通訊Messenger框架
Messenger註冊流程圖

1.每個模組初始化時啟動一個訊息佇列來監聽訊息。

    @Override
    public void init() {
        mBaseModule = this;
        //啟動執行緒接收訊息
        mWorkThread = new WorkThread();
        mWorkThread.start();
        //註冊跳轉路由
        OkBus.getInstance().register(Constants.ROUTER_OPEN_URL, new Event() {
            @Override
            public void call(Message msg) {
                String url = (String) msg.obj;
               //實際跳轉使用的Router               
               Router.openLocalUrl(BaseAppModuleApp.getBaseApplication(), url);
            }
        }, Bus.UI); //執行緒引數
    }

    public class WorkThread extends Thread {
        Handler mHandler;
        public Messenger clientHandler;

        @Override
        public void run() {
            Looper.prepare();
            //每個module都有接收訊息處理ClientHandler
            mHandler = new ClientHandler();
            clientHandler = new Messenger(mHandler);
            if(resultRef!=null){
                try {
                    resultRef.set(clientHandler);
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    //使用CountDownLatch喚醒機制保證執行緒安全
                    latch.countDown();
                }
            }
            Looper.loop();
        }

        public void quit() {
            mHandler.getLooper().quit();
        }
    }
複製程式碼

2.繫結MessengerService作為程式間管理,並且繫結每個模組的ServiceConnection

/**
     * 連線伺服器
     */
    public void connectService() {
        Intent intent = new Intent(MessengerService.class.getCanonicalName());// 5.0+ need explicit intent
        intent.setPackage(Constants.SERVICE_PACKAGE_NAME); // the package name of Remote Service
        //繫結MessengerService作為程式間管理,並且繫結每個模組的ServiceConnection
        boolean mIsBound = bindService(intent, mBaseModule.mConnection, BIND_AUTO_CREATE);
        LogUtils.i(Constants.TAG + " connectService", " ServiceConnection-->bindService  mIsBound: " + mIsBound);
    }
複製程式碼

3.啟動MessengerService,啟動訊息迴圈

 @Override
    public void onCreate() {
        super.onCreate();
        LogUtils.i(Constants.TAG + " essengerService", "MessengerService -->onCreate");
        mWorkThread = new WorkThread();
        mWorkThread.start();
    }

    public class WorkThread extends Thread {
        public ServiceHandler mHandler;

        @Override
        public void run() {
            Looper.prepare();
            LogUtils.i(Constants.TAG + " essengerService", "MessengerService -->new ServiceHandler");
            //通過ServiceHandler來處理收到的訊息
            mHandler = new ServiceHandler();
            Messenger mMessenger = new Messenger(mHandler);
          //  OkBus.getInstance().mServiceMessenger = mMessenger;
            try {
                resultRef.set(mMessenger);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                latch.countDown();
            }
            Looper.loop();
        }

        public void quit() {
            mHandler.getLooper().quit();
        }
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        try {
            latch.await(10, TimeUnit.SECONDS); //最多等待10秒
        } catch (Exception e) { //等待中斷
            e.printStackTrace();
        }
        Messenger mMessenger = resultRef.get();
        return mMessenger.getBinder();
    }
複製程式碼

4.初始化OkBus併傳送訊息註冊

    public void initModule(BaseModule mBaseModule, Messenger mServiceMessenger, int mModuleId, Messenger mClientMessenger) {
        this.mServiceMessenger = mServiceMessenger;
        this.mModuleId = mModuleId;
        this.mBaseModule = mBaseModule;
        isModule.set(true);
        mBaseModule.isConnected.set(true);
        //執行緒池獲取資訊
        Message msg = Message.obtain();
        Bundle data = new Bundle();
        data.putInt(Constants.REGISTER_ID, mModuleId);//註冊模組資訊
        msg.setData(data);
        msg.replyTo = mClientMessenger;   //將處理訊息的Messenger繫結到訊息上帶到服務端
        try {
            //傳送到MessengerService中處理
            mServiceMessenger.send(msg);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
複製程式碼

5.ServiceHandler中維護一個對Service Messenger到多個Client Messenger的關係

    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        try {
            Bundle bundle = msg.getData();
            int registerId = bundle.getInt(Constants.REGISTER_ID, -1);
            if (registerId > 0) {//註冊模組型別的訊息
                LogUtils.logOnUI(Constants.TAG, "handleMessage: msg = [收到註冊模組型別的訊息]: registerId: " + Integer.toHexString(registerId));
                //每個模組對應的ClientHandler
                Messenger client = msg.replyTo; 
                mClientMessengers.put(registerId, client);//儲存Client端接受處理訊息的Messenger來傳送Message到Client

                Message data = Message.obtain();
                Bundle mBundle = new Bundle();
                mBundle.putInt(Constants.REGISTER_RES, Constants.REGISTER_SEC);    //通知Client模組註冊成功
                data.setData(mBundle);
                try {
                    client.send(data);  //回撥註冊狀態給模組
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
複製程式碼

6.介紹的模組註冊結果

public class ClientHandler extends Handler {

    @Override
    public void handleMessage(Message msg) {
        ……
        int resCode = bundle.getInt(Constants.REGISTER_RES, -1);
        if (resCode < 0) {//收到普通訊息
        ……
        } else {//收到模組註冊結果訊息
            boolean isRegisterSec = resCode == Constants.REGISTER_SEC;
            if (isRegisterSec) {
                LogUtils.logOnUI(Constants.TAG, "handleMessage() : reply = [註冊成功]");
            }
        }
    }
}
複製程式碼

7.其繫結完畢之後,將module資訊註冊到OkBus,之後會使用afterConneted()來初始化想要接收的訊息

public ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            LogUtils.logOnUI(Constants.TAG, "ServiceConnection-->onServiceConnected 已自動喚醒伺服器");
            Messenger mServiceMessenger = new Messenger(service);
            OkBus.getInstance().initModule(mBaseModule, mServiceMessenger, getModuleId(), mWorkThread.clientHandler);
            afterConnected();
        }
    
複製程式碼

8.通過ServiceBus來註冊其他module會跨module呼叫過來的訊息。

@Override
    public void afterConnected() {

        ServiceBus.getInstance().registerService(Constants.SERVICE_A_UID, msg -> {
            LogUtils.logOnUI(Constants.TAG, "afterConnected  a 程式收到[服務請求]訊息:ServiceMessage-->hello:  " + Integer.toHexString(Math.abs(msg.what)));
            return "10086";
        });
    }
複製程式碼

9.服務註冊後,可以看到其會通過onEvent返回回撥的msg物件(CallBack介面)

    /**
     * 註冊服務
     *
     * @param serviceId 服務id
     * @param callback  服務呼叫的回撥
     * @param <T>       服務返回的資料範型
     */
    public <T> void registerService(final int serviceId, final CallBack<T> callback) {
        LogUtils.logOnUI(Constants.TAG, "註冊服務  " + Integer.toHexString(Math.abs(serviceId)));
        okBus.unRegister(serviceId);//服務提供者只能有一個
        okBus.register(serviceId, msg -> {
            //TODO 優化到子執行緒
            okBus.onEvent(serviceId - 1, callback.onCall(msg));
        });
    }
複製程式碼

到這裡就介紹完初始化和註冊流程了。

接下來介紹一下訊息傳送的架構,注意一下的模組服務規則

    //==================模組間的服務定義============//
    /**
     * 服務定義規則:
     * 1、服務的請求ID必須是負值(正值表示事件)
     * 2、服務的請求ID必須是奇數,偶數表示該服務的返回事件,
     * 即:   requestID-1 = returnID
     * 例如  -0xa001表示服務請求  -0xa002表示-0xa001的服務返回
     */
    public static final int SERVICE_A_UID = -0xa001;

        /**
         * 非同步呼叫遠端服務
         */
        findViewById(R.id.bt_1).setOnClickListener(v -> {
        //非同步呼叫,
           ServiceBus.getInstance().fetchService(Constants.SERVICE_A_UID, msg -> {
                LogUtils.logOnUI(Constants.TAG, "b 程式收到[非同步服務返回]訊息:  獲取到的UID-->" + msg.obj);
                Toast.makeText(BModuleActivity.this,
                        "b 程式收到[非同步服務返回]訊息:  獲取到的UID-->" + msg.obj,
                        Toast.LENGTH_SHORT).show();
            });
        });
複製程式碼

具體步驟
1.對ID的請求限制
2.module連線的判斷,沒有連線就嘗試連線
3.喚醒目標程式
4.註冊回撥
5.通知目標模組

[Android]你不知道的Android程式化(5)--程式通訊Messenger框架
跨模組通訊
    /**
     * 非同步呼叫服務
     *
     * @param serviceId 服務id
     * @param callback  回撥
     */
    public void fetchService(final int serviceId, final Event callback) {
        if (serviceId > 0 || serviceId % 2 == 0) {
            assert false : "請求ID必須是負奇值!";
            return;
        }
        if (okBus.isModule() && !okBus.isModuleConnected()) {
            LogUtils.logOnUI(Constants.TAG, "請求失敗,服務已經斷開連結,嘗試重新開啟服務,進行請求");
            BaseAppModuleApp.getBaseApplication().connectService();
            return;
        }

        //自動喚醒目標程式
        if (okBus.isModule()) {
            String module_name = Integer.toHexString(Math.abs(serviceId)).substring(0, 1);
            noticeModule(module_name, serviceId, null);
        }

        //1、先註冊回撥
        okBus.register(serviceId - 1, msg -> {
            callback.call(msg);
            okBus.unRegister(serviceId - 1);//服務是單次呼叫,觸發後即取消註冊
        });
        //2、通知目標模組
        okBus.onEvent(serviceId);
    }
複製程式碼

喚醒目標程式

    /**
     * 喚醒目標程式
     *
     * @param module_name 模組名
     * @param serviceId   服務ID
     * @param url         要開啟的url
     */
    public void noticeModule(String module_name, int serviceId, String url) {
        Intent ait = new Intent(NoticeService.class.getCanonicalName());// 5.0+ need explicit intent        //喚醒目標程式的服務Action名
        ait.setPackage(Constants.MODULE_PACKAGE_PRE + module_name);   //喚醒目標程式的包名
        //繫結包名
        BaseAppModuleApp.getBaseApplication().bindService(ait, new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                if (service != null) {
                    LogUtils.logOnUI(Constants.TAG, "已經自動喚醒" + module_name);
                    Messenger moduleNameMessenger = new Messenger(service);
                    Message _msg = Message.obtain();
                    Bundle _data = new Bundle();
                    _data.putBoolean(Constants.NOTICE_MSG, true);
                    _msg.setData(_data);
                    _msg.replyTo = okBus.mServiceMessenger;//把伺服器的信使給目標元件的信使,讓他倆自己聯絡,這裡僅僅是通知
                    try {
                        moduleNameMessenger.send(_msg);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }

                    try {
                        Thread.sleep(200);//給伺服器和目標元件500ms聯絡的時間
                    } catch (Exception e) {
                        e.printStackTrace();
                    }

                } else {
                    LogUtils.logOnUI(Constants.TAG, module_name + "程式,本來就是醒的");
                }

                if (serviceId < 0) {  //喚醒成功,繼續傳送非同步請求,通知目標模組
                    okBus.onEvent(serviceId);
                }
                if (!TextUtils.isEmpty(url)) {  //目標url不為空,繼續開啟目標
                    OkBus.getInstance().onEvent(Constants.ROUTER_OPEN_URL, url);
                }
            }

            @Override
            public void onServiceDisconnected(ComponentName name) {
                LogUtils.logOnUI(Constants.TAG, "自動喚醒目標程式失敗 module_name:" + module_name);
            }
        }, BIND_AUTO_CREATE);
    }
複製程式碼

啟動模組,並傳遞繫結物件

    /**
     * 收到喚醒通知之後,初始化模組,並自動去伺服器註冊
     *
     * @param intent
     * @return
     */
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        LogUtils.logOnUI(Constants.TAG, getPackageName() + " 收到喚醒通知");
        //獲取模組的module
        BaseModule mBaseModule = BaseAppModuleApp.getBaseApplication().mBaseModule;
        if (!mBaseModule.isConnected.get()) {
            LogUtils.logOnUI(Constants.TAG, getPackageName() + " 我被喚醒啦");
            //初始化module,啟動module的ClientHandler(Messenger)
            mBaseModule.init(latch, resultRef);
            mBaseModule.afterConnected();
            try {
                //超時限制
                latch.await(2000, TimeUnit.SECONDS);
            } catch (Exception e) { //等待中斷
                e.printStackTrace();
            }
        }
        //返回ClientHandler的Binder物件
        return mBaseModule.mWorkThread.clientHandler.getBinder();
    }
複製程式碼

傳送訊息,其最終是通過ServiceBus轉發到每個模組OkBus,然後Binder傳遞的Messenger關聯來完成資訊傳遞。

/**
     * @param tag  傳送訊息的事件ID
     * @param data 傳送訊息的資料
     * @return
     */
    public OkBus onEvent(int tag, Object data) {
        //傳送時間,所以tag小於0
        String hex = Integer.toHexString(Math.abs(tag));
        LogUtils.i("Message OkBus", "onEvent  " + (tag > 0 ? "[普通]" : "[服務]") + "  tag: " + hex);

        //1、本地先處理非服務訊息
        if (tag >= 0) onLocalEvent(tag, data);

        //2、如果是組建化,向伺服器發訊息
        if (isModule.get()) {
            //保證傳送時服務啟動
            if (!isModuleConnected()) {
                LogUtils.i("Message OkBus", "發訊息失敗,服務已經斷開連結,嘗試重新開啟服務,進行發訊息");
                BaseAppModuleApp.getBaseApplication().connectService();
                return this;
            }
           //資料為空,即為事件
            if (data == null || data instanceof Serializable) {
                Message newMsg = new Message();
                if (data != null) {
                    Bundle bundle = new Bundle();
                    bundle.putSerializable(Constants.MESSAGE_DATA, (Serializable) data);
                    newMsg.setData(bundle);
                }
                newMsg.arg1 = mModuleId;
                newMsg.what = tag;
                try {
                    //傳送資訊到目標Service
                    mServiceMessenger.send(newMsg);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            } else {
                assert false : "跨程式時,你傳遞的物件沒有序列化!";
            }
        } else if (tag < 0) {//非元件化時本地處理服務訊息
            onLocalEvent(tag, data);
        }
        return this;
    }
複製程式碼

使用Messenger通訊框架設計就介紹到這裡。
1.Modular框架,模組內傳輸使用了OkBus的路由傳輸,而在跨模組則使用Messenger的方式來完成
2.Messenger實際是一個封裝好的IBinder物件
3.Modular通過合理設定跨模組的傳輸的協議邏輯來完成資訊傳輸


相關文章