天天看點

跨程序通信使用總結(二)_Android中的IPC方式

第一種方式,使用Bundle

Android中的4大元件都是支援在Intent中傳遞Bundle資料的,由于Bundle實作了Parcelable接口,是以它可以友善的在不同的程序間傳輸

第二種方式,使用檔案共享:

讀寫在同一個地方的檔案(例如讀取外存儲卡上的data.txt檔案,前提是要有讀寫檔案的權限)

第三種方式.使用Messenger(信使)

通過信使Messenger可以在不同的程序中傳遞Message(消息)對象,在消息對象中放入我們要傳遞的資料,就可以輕松實作程序中傳遞資料了,信使是一種輕量級的IPC方案,它的底層是AIDL,信使是一次處理一個請求,是以服務端不用考慮線程同步的問題,因為服務端不存線上程并發的情況

信使的2種構造函數如下:

//一般用在服務端
 public Messenger(Handler target) {
        mTarget = target.getIMessenger();
    }
           
//一般用在用戶端
 public Messenger(IBinder target) {
        mTarget = IMessenger.Stub.asInterface(target);
    }
           

我們先看一下它的大體邏輯:

服務端程序:

首先,我們要在服務端建立一個Service來處理用戶端的請求,同時建立一個Handler并通過它來建立一個Mesenger對象,然後在Service的onBind中傳回這個Messenger對象底層的Binder即可

用戶端程序:

首先要綁定服務端的Service,綁定成功後用服務端傳回的IBinder對象建立一個Messenger,通過這個Messenger就可以向服務端發送消息,發送的消息類型是:Message對象.

都是在Handler裡面從msg上去擷取資料的

.

服務端具體實作代碼如下:

public class MessengerService extends Service{
    
    private class MessengerHandler  extends Handler{
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what){
                case Constants.MSG_FROM_CLIENT:
                    //從用戶端msg裡面取到的消息内容
                    String msgContent=   msg.getData().getString("client");
                    LogUtils.d("服務端msgContent"+msgContent);
                    //從用戶端msg裡面取到的信使
                    Messenger msger=  msg.replyTo;
                    //建立一個新的消息對象,一會兒給用戶端傳遞消息
                    Message tempMsg=Message.obtain(null, Constants.MSG_FROM_SERVICE);
                    //給消息裡面放資料
                    Bundle bundle=new Bundle();
                    bundle.putString("serviceReply","你好,用戶端,你發送的消息我已經收到了");
                    tempMsg.setData(bundle);
                    try {
                        //信使攜帶消息去用戶端
                        msger.send(tempMsg);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                    break;
                 default:
                     super.handleMessage(msg);
                     break;
            }
        }
    }
    Messenger  messenger=new Messenger(new MessengerHandler());
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return messenger.getBinder();
    }
    
}
           

用戶端的具體實作完整代碼如下:

public class MessengerClient extends AppCompatActivity {

    private class MessengerHandler  extends Handler{
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what){
                case Constants.MSG_FROM_SERVICE:
                    //從服務端msg裡面取到的消息内容
                    String msgContent= msg.getData().getString("serviceReply");
                    LogUtils.d("msgContent"+msgContent);
                    break;
                default:
                    super.handleMessage(msg);
                    break;
            }
        }
    }

    Messenger  replyMsger = new Messenger(new MessengerHandler());


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_book_manager);
        Intent a=new Intent(this,MessengerService.class);
        bindService(a,mServiceConnection, Context.BIND_AUTO_CREATE);
    }
    private ServiceConnection mServiceConnection=new ServiceConnection(){
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            //執行在主線程
            //用服務端傳回的service,建立Messenger信使,一會兒給服務端發送資訊
            Messenger  msger=new Messenger(service);
            //建立一個新的消息對象,作為資料的載體
            Message clientMsg=Message.obtain(null, Constants.MSG_FROM_CLIENT);
            //給消息裡面放資料
            Bundle bundle=new Bundle();
            bundle.putString("client","你好,服務端,這是我用戶端發送的消息");
            clientMsg.setData(bundle);

            //注意這步:把信使放到msg裡面,友善服務端從msg裡面取信使
            clientMsg.replyTo=replyMsger;

            try {
                //信使攜帶消息去服務端
                msger.send(clientMsg);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            //執行在主線程
        }

        @Override
        public void onBindingDied(ComponentName name) {
            //執行在主線程 :重新連接配接
        }
    };

    @Override
    protected void onDestroy() {
        unbindService(mServiceConnection);
        super.onDestroy();
    }
}

           

第四種方式,使用AIDL

1,AIDL通信的流程:

第一步:服務端:

服務端首先要建立一個service來監聽用戶端的連接配接請求,然後建立一個AIDL檔案,把暴露給用戶端調用的接口在這個AIDL檔案中聲明,最後在service中實作這個接口

第2步:用戶端,

首先綁定服務端的service,綁定成功後,把服務端傳回的Binder對象自己轉換成AIDL接口所屬的類型,接口就可以調用AIDL中的方法了,注意連結斷開的時候增加Binder死亡監聽

第3步:AIDL接口的建立:建立檔案名為IBookManager2 .aidl的檔案

// IBookManager2.aidl
package com.hlj.demo.aidl;

// Declare any non-default types here with import statements
//Book1 是自定義的parcelable對象,必須要顯示的import進來
import com.hlj.demo.aidl.Book1;
import android.os.Parcel;
import android.os.Parcelable;

import com.hlj.demo.aidl.IOnNewBookArrivedListener;

interface IBookManager2 {
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString);
    List<Book1> getBookList();
    //in:表示輸入類型
    //Book1 是自定義的parcelable對象,必須要顯示的import進來
    void addBook(in Book1 book1);

    void registerListerner(IOnNewBookArrivedListener lister);
    void unregisterListerner(IOnNewBookArrivedListener lister);

}

           

說明;

1,AIDL支援的資料類型為:基本資料類型,Arraylist,HashMap,Parcelable,AIDl

2,自定義的parcelable對象和AIDl的對象,必須要顯示的import進來,不管它們是否和目前的AIDL檔案處于同一個包内

3,除了基本資料類型之外,其他類型的參數必須要标上方向,in,out,或者inout

4,AIDL的包結構在服務端和用戶端要一緻,因為用戶端要反序列化服務端中和AIDl相關的所有類,如果類的完整路徑不一樣的話,否則會運作報錯,是以建議把所有和AIDL相關的類和檔案全部放入同一個包中

第4步和第5步就是服務端和用戶端的具體實作,後面會寫一個Demo具體看demo

代碼較多,就不具體貼出來啦 在這裡我提幾個重要的注意點:

1.集合用CopyOnWriteArrayList,理由是:它支援并發讀寫,因為AIDl方法是在Binder線程池中執行的,會存在多個線程同僚通路的情況,是以我們要在AIDl方法中處理線程同步,而CopyOnWriteArrayList可自動進行線程同步

CopyOnWriteArrayList<Book1> mBookList=new CopyOnWriteArrayList<Book1>();
           

2,Binder線程池中執行的程式不能更新UI,是子線程

3,有類似觀察者模式或者回調情況的集合要用RemoteCallbackList,RemoteCallbackList是系統專門提供的用于删除跨程序listener的接口,它内部也實作了線程同步

RemoteCallbackList<IOnNewBookArrivedListener> mListenerList=
            new RemoteCallbackList<IOnNewBookArrivedListener>();
           

用法:

public void onNewBookArrived(Book1  book1) throws RemoteException {
  mBookList.add(book1);
  //注意beginBroadcast和finishBroadcast要配對使用
    final int N=  mListenerList.beginBroadcast();
    for (int i = 0; i < N; i++) {
        IOnNewBookArrivedListener lister= mListenerList.getBroadcastItem(i);
        if (lister!=null) {
            lister.onNewBookArrived(book1);
        }
    }
    mListenerList.finishBroadcast();
};
           

注意beginBroadcast和finishBroadcast要配對使用

4遠端服務不是任何用戶端相連就可以連上的,是以我們要做一些驗證才可以:這裡做的是權限加包名驗證

<!-- 表示服務端自定義了一個權限 -->
    <permission
        android:name="com.hlj.demo.permiss_ACCESS_BOOK_SERVICE"
        android:protectionLevel="normal" />
        
 <!-- 表示所有連接配接這個服務的用戶端都要有這個權限,否則不會連接配接成功 -->
        <service
            android:name=".service.BookManagerService"
            android:permission="com.hlj.demo.permiss_ACCESS_BOOK_SERVICE"
            android:process=":BookManagerService" />
           
<!-- 表示用戶端可以使用這個權限,已經有了這個權限 -->
    <uses-permission android:name="com.hlj.demo.permiss_ACCESS_BOOK_SERVICE" />
           
@Override
    public IBinder onBind(Intent intent) {
      //先驗證權限在驗證包名,隻要有一個沒有通過就傳回null
        String pkgName="";
        int permission= ActivityCompat.checkSelfPermission(this,"com.hlj.demo.permiss_ACCESS_BOOK_SERVICE");
        if (permission == PackageManager.PERMISSION_DENIED) return null;
        String[] pkgNames=getPackageManager().getPackagesForUid(Binder.getCallingUid());
        if (pkgNames==null||pkgNames.length<=0) return null;
        pkgName=pkgNames[0];
        if (!pkgName.startsWith("com.hlj."))return null;
        return mBinder;
    }
           

5,Binder是有可能意外死亡的,那當服務斷開了的時候,我們要重新連接配接服務

第一種方式:

給Binder設定DeathRecipient監聽,當Binder死亡的時候,我們會受到binderDied方法的回調,可在此方法中重新連接配接(在非UI線程中執行)

第二種方式:

在onServiceDisconnected()方法中重新連接配接(在UI線程中執行)

第五種方式,使用ContentProvider

第六種方式,使用Socket(此2中方式在項目中使用較少,略過)

一點優化:

當要提供的AIDl服務過多時,可以使用Binder連接配接池

跨程式通信使用總結(二)_Android中的IPC方式

結尾:

終于寫完了,老衲甚是欣慰啊^- ^

繼續閱讀