Android 程式之間通訊

憤怒的小鹹菜發表於2018-08-28

1.Binder

Binder是什麼(引用Carson_Ho部落格解釋):

Android 程式之間通訊

  • 從機制、模型角度來說:Binder是一種Android中實現跨程式通訊的方式。
  • 從模型的結構、組成來說:Binder是一種虛擬的物理驅動
  • 從Android程式碼來說:Binder是一個類,實現了IBinder介面。

 1.1 Linux的程式空間劃分

在Linux的系統中將一個程式分為 使用者空間 & 核心空間(kernel)。

  • 使用者空間:在不同程式間,使用者空間的資料不可共享。
  • 核心空間:在不同程式間,核心空間可共享。即所有的程式都共享一個核心空間。

Android 程式之間通訊

  • android中所有的程式都是相互獨立,隔離的,即一個程式不能直接操作或訪問另一個程式的資料。要實現跨程式通訊,需要通過所有程式共享的核心空間來實現。
    Android 程式之間通訊

 1.2 Binder跨程式通訊機制模型

Binder跨程式同通訊機制模型基於Client - Server模式

Android 程式之間通訊

模型組成說明圖:

角色 作用 備註
Client程式 使用服務程式 android客戶端
Server程式 提供服務的程式 android 服務端
ServiceManager程式 管理Service註冊與查詢(將字元形式的名字轉化
為Client中對該Binder的引用)
類似路由器
Binder驅動 連線Service程式,Client程式和ServiceManager的橋樑,具體作用為:
1.傳遞程式間的資料:通過記憶體對映傳遞程式間的資料
2.實現執行緒控制:採用Binder的執行緒池,並由Binder驅動自生管理
Binder驅動持有每個Server程式在核心空間中的Binder實體,並給Client程式提供Binder實體的引用。

1.3 Binder驅動的作用&原理:

Android 程式之間通訊

  Binder工作流程

  • Binder驅動建立一塊接收快取區;
  • 實現地址對映關係:根據需要對映的接收程式資訊,實現核心快取區和接收程式使用者空間地址同時對映到一個Binder建立的一個共享接收快取區中。
  • 發起程式通過系統呼叫copy_from_user傳送資料到虛擬記憶體區域(資料拷貝1次)
  • 由於核心快取區與接受程式使用者空間地址存在對映關係(同時對映到Binder建立的接受快取區中),故發起程式傳送的資料到記憶體空間的資料也傳送到了使用者接受程式的使用者空間

示意圖:

Android 程式之間通訊

1.4 Android程式通訊流程說明

  • Server程式註冊服務:Service程式向Binder驅動發起註冊服務請求,Binder驅動將註冊請求轉發給ServiceManage程式,ServiceManage新增該Server程式。
  • Client程式獲取服務:Client向Binder驅動發去獲取服務的請求,傳遞需要獲取服務的名稱,Binder通過ServiceManage找到該服務程式,同時通過Binder驅動將服務程式資訊返回給Client程式。
  • 使用服務(程式間通訊):1.Binder驅動在核心空間建立快取區實現server程式與clietn程式的記憶體地址對映;2.Client程式將引數資料傳送到Server程式;3.Server程式根據Client程式的要求呼叫相應的方法;4.Server程式將目標方法的執行結果返回給Client程式。

Binder請求的執行緒管理
Binder模型的執行緒管理由Binder驅動的執行緒池,並由Binder驅動自生管理。一個程式的Binder執行緒數預設最大是16,超過的請求會被阻塞等待空閒的執行緒。

ServiceManage
ServiceManage本身是一個程式,它的作用是將字元形式的Binder名字轉化為Client中對該Binder的引用。當Server程式通過向ServiceManager註冊的過程也是程式間的通訊。此時server程式座位Client端,ServiceManager座位service端也,他們之間也是通過Binder來通訊。ServiceManager程式中的Binder實體在其它所以的程式中都是通過"0"這個字元來獲得其引用。

Binder架構
引用Jeanboydev部落格

Android 程式之間通訊

Binder機制圖解
引用Jeanboydev部落格

Android 程式之間通訊

AIDL跨程式通訊

 實現連個app跨程式通訊流程:

  • step1 :編寫兩個app通訊的實體類(如果是基本型別就不用寫,實體類要實現Parcelable介面。
    實體類圖
  • step2:編寫aidl實體類檔案和介面檔案。實體類檔名與相應的實體名相同。
      //Person.aidl
      package com.aoy.learn.source.bean;
      parcelable Person;
    複製程式碼

  aidl介面檔案:

   package com.aoy.learn.source.bean;

   import com.aoy.learn.source.bean.Person;
   
   /**
    * Created by drizzt on 2018/6/1.
    */
   interface IMyAidl{
    /**
        * 傳參時除了Java基本型別以及String,CharSequence之外的型別
        * 其他型別的引數都需要標上方向型別:in(輸入), out(輸出), inout(輸入輸出)
        */
       void addPerson(in Person person);
   
       List<Person> getPersonList();
   }
複製程式碼
  • step3: Make Proteced
  • step4: 編寫service,並且在AndroidManifest.xml註冊這個service,指明android:exported="true"
    public class MyService extends Service {
    private List<Person> mPersons = new ArrayList<>();

    private  IBinder mIBinder = new IMyAidl.Stub() {
        @Override
        public void addPerson(Person person) throws RemoteException {

        }

        @Override
        public List<Person> getPersonList() throws RemoteException {
            return mPersons;
        }
    };

    @Override
    public void onCreate() {
        super.onCreate();
        mPersons.add(new Person());
        mPersons.add(new Person());
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mIBinder;
    }
}
複製程式碼
  • step5: 在另外一個移植aidl檔案和實體類,移植的實體類的包名要和原始的包名一致,移植的aidl檔名和包名也要一致;
  • step6:在另外一個app中連線這個service並通過binder代理通訊;
//另外一個app中的mainActivity
public class MainActivity extends AppCompatActivity {
    final String TAG = MainActivity.class.getSimpleName();

    @BindView(R.id.btn)
    Button btn;

    MyServiceConnector mServiceConnector;
    IMyAidl myAidl;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
        init();
    }
    private void init(){
        try {
            mServiceConnector = new MyServiceConnector();
            toConneRemoteService();
            RxView.clicks(btn)
                    .subscribe(o -> handleRomoteBinde());
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    private void handleRomoteBinde(){
        if(myAidl != null){
            try {
                List<Person> mList =  myAidl.getPersonList();
                Log.i(TAG,"remote communications is successful: person list size:" + mList.size());
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    }

    private void toConneRemoteService(){
        Intent intent = new Intent();
        intent.setClassName("com.aoy.learn.source","com.aoy.learn.source.service.MyService");
        bindService(intent,mServiceConnector,BIND_AUTO_CREATE);
    }

    class MyServiceConnector implements ServiceConnection{
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.i(TAG,"Remote service has connected");
            myAidl = IMyAidl.Stub.asInterface(service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    }
}
複製程式碼

執行結果:

06-04 18:19:15.592 4023-4023/com.aoy.service.source I/MainActivity: remote communications is successful: person list size:2
複製程式碼

介面檔案中傳參tag:int out,inout
int,out,intou 代表aidl程式通訊中的資料流向,只能修飾aidl介面檔案中方法傳參,不能修飾方法的返回值。

  • in表示傳參物件只能由客戶端流向服務端,服務端會接受到完整的傳參物件,但是在服務端對傳入物件做任何改變不會影響客戶端的原有傳入物件;
  • out表示傳參物件只能由服務端傳入客戶端,不管客戶端傳入什麼物件,在相應的服務端都只能接受到傳入物件的null值,但是如果服務端對這個傳入值做任何修改,客戶端原有的物件將會發生相應的改變;
  • inout表示傳引數據可以在伺服器和客戶端之間雙向流通,服務端將接受到客戶端傳入物件的完整值,同時服務端對傳入物件的改變將影響客戶端的值。

Messenger實現程式之間的通訊

 利用Messenger實現兩個app間的通訊步驟:

  • step1:在提供服務的app工程裡編寫service類,這個service有一個Messenger屬性,service的Binder由這個messenger提供。

public class BindMineServiece extends Service {
    public static final String TAG = "BindMineServiece";

    Messenger messenger = new Messenger(new Handler(){
        @Override
        public void handleMessage(Message msg) {
          //  由handler處理app間的通訊資料
            if(AccountHandler.getInstance().getLoginUser() != null && AccountHandler.getInstance().getAccessToken() != null){
                Message message = Message.obtain();
                Bundle bundle = new Bundle();
                bundle.putInt("ret_data",1);
                message.setData(bundle);
                try {
                    msg.replyTo.send(message);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        }
    });

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return messenger.getBinder();
    }
}
複製程式碼
  • step2:在AndroidManifest.xml中註冊這個service,並設定其屬性 android:exported="true"
  • step3:在另外一個app中通過包名和類名連線這個service,並通過Messenger通訊。
 private void bindMineService(String appId, String serviceName) {
        if (serviceConnection == null) {
            serviceConnection = new ServiceConnection() {
                @Override
                public void onServiceConnected(ComponentName name, IBinder service) {
                    messenger = new Messenger(service);
                    Message message = Message.obtain();
                    message.replyTo = replyMessenger;
                    try {
                        messenger.send(message);
                    } catch (Exception e) {

                    }
                }

                @Override
                public void onServiceDisconnected(ComponentName name) {
                    //TODO 解除繫結
                    Log.i("serviceConnection", "解除繫結");
                }
            };
        }

        //replyMessenger負責接收remote service的資料
        if (replyMessenger == null) {
            replyMessenger = new Messenger(new Handler() {
                @Override
                public void handleMessage(Message msg) {
                    super.handleMessage(msg);
                    msg.getData().get("ret_data");
                }
            });
        }
        try {
            Intent intent = new Intent();
            intent.setComponent(new ComponentName(appId, serviceName));
            getActivity().bindService(intent, serviceConnection, BIND_AUTO_CREATE);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
複製程式碼

Messenger在服務端不能處理多執行緒,AIDL可以處理多執行緒

相關文章