Android中的IPC方式

使用Bundle

我們知道泉瞻,四大組件中的三大組件(Activity莽囤,Service,Broadcast)都是支持在Intent中傳輸Bundle數(shù)據(jù)的房官,由于Bundle實現(xiàn)了Parcelable接口趾徽,所以他可以很方便的在進程間傳輸,通過Bundle我們不就可以傳輸基本類型翰守,也可以傳輸實現(xiàn)了Parcelable接口或者Serializable接口的對象

使用文件共享

  • 在Windows上附较,一個文件如果被加了排斥鎖將會導(dǎo)致其他線程無法對其進行訪問,包括讀和寫
  • 而Android由于是基于Linux系統(tǒng)潦俺,使得這個并發(fā)讀寫操作可以無限制的進行拒课,盡管這可能出問題
  • 但是這種方式卻是解決了不同進程數(shù)據(jù)傳輸?shù)膯栴}

接下來簡單的舉個栗子示范一下在本進程中寫數(shù)據(jù):

Data data = new Data(10,"這是主進程寫的數(shù)據(jù)");
            try {
                ObjectOutputStream out = new ObjectOutputStream(
                        new FileOutputStream("/storage/emulated/0/1/sina/sdadas.txt"));
                out.writeObject(data);
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
                Log.d(TAG, "ssss: 序列化的時候失敗" + e.toString());
            }

在另一進程中讀數(shù)據(jù)

Data data = null;
            try {
                ObjectInputStream in = new ObjectInputStream(
                        new FileInputStream("/storage/emulated/0/1/sina/sdadas.txt"));
                data = (Data) in.readObject();
            }catch (IOException e){
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }

            if(data != null){
                Log.d(TAG, "onClick: 讀取成功 , data.id = "  + data.id + "data.name = " + data.name);
            }else {
                Log.d(TAG, "onClick: 錯誤");
            }

這是正常并且操作成功的事示,說明這種方式是可行的早像,不過我們這里雖然說的是傳輸數(shù)據(jù),只不過我們保證的是內(nèi)容一樣肖爵,他們實際上還是兩個不同的對象卢鹦,通過這種方式,我們應(yīng)盡量控制讀寫時的時序問題

使用Messenger

  • Messenger可以翻譯為信使,通過它可以在不同進程中傳遞Messenger對象冀自,在Messenger中可以放入我們需要傳遞的對象揉稚,就可以輕松跨進程傳輸

  • Messenger是一種輕量級的IPC方案,他的底層實現(xiàn)是AIDL熬粗,為什么這么說呢搀玖,我們?nèi)タ匆幌滤臉?gòu)造方法

    public Messenger(Handler target) {
      mTarget = target.getIMessenger();
    }
    public Messenger(IBinder target) {
      mTarget = IMessenger.Stub.asInterface(target);
    }
    

從上構(gòu)造方法我們可以很容易想到底層是AIDl的
Messenger的用法非常簡單,他對AIDL做了封裝驻呐,使得我們可以更簡單的跨進程通信
同時灌诅,由于他一次處理一個請求,因此在服務(wù)端我們不同考慮線程同步的問題含末,這是因為服務(wù)端不承諾在并發(fā)執(zhí)行的問題猜拾,下面我們來看看怎么實現(xiàn)一個Messenger

我們首先來看看服務(wù)端進程中怎么實現(xiàn)
只需要在服務(wù)端創(chuàng)建一個Service來處理客戶端的連接請求,并通過它來創(chuàng)建一個Messenger對象佣盒,然后在Service的onBind中返回這個Messenger對象底層的Binder即可

public class MessengerService extends Service {

private static final String TAG = "MessengerService";

private static class Messengerhandler extends Handler {
    @Override
    public void handleMessage(Message msg) {
        switch (msg.what){
            case 1:
                Log.d(TAG, "handleMessage: receive msg from Client " + msg.getData().getString("msg"));
                break;
        }
        super.handleMessage(msg);
    }
}
private final Messenger mMessenger = new Messenger(new Messengerhandler());

@Nullable
@Override
public IBinder onBind(Intent intent) {
    return mMessenger.getBinder();
}
}

可以看到挎袜,因為我們在上面的Messenger構(gòu)造器中發(fā)現(xiàn)Messenger的構(gòu)造器需要傳入一個handler 類型的參數(shù),很明顯這個Handler就是用來處理客戶端發(fā)來的Message消息的
接下來看看客戶端的代碼
客戶端首先要綁定Service肥惭,然后發(fā)送Message類型的消息

void bindMyMessengerService(){
    ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mService = new Messenger(service);
            sendData();
        }
        @Override
        public void onServiceDisconnected(ComponentName name) {
            mService = null;
        }
    };
    Intent intent = new Intent(this,MessengerService.class);
    bindService(intent,mConnection, Context.BIND_AUTO_CREATE);
}

void sendData(){
    Message msg = Message.obtain(null,1);
    Bundle data = new Bundle();
    data.putString("msg","這是客戶端的消息");
    msg.setData(data);
    try{
        mService.send(msg);
    }catch (RemoteException e) {
        e.printStackTrace();
    }
}

這段代碼相信沒什么難度盯仪,不過這里問題又來了?誒务豺?我們只是發(fā)送了消息給服務(wù)端磨总,可是通信,通信笼沥,不是應(yīng)該能互相發(fā)的嗎蚪燕?客戶端是怎么接收服務(wù)端的呢?
接下來我們看看服務(wù)端怎么給客戶端發(fā)送消息奔浅,先來看看服務(wù)端怎么修改

private static class Messengerhandler extends Handler {
    @Override
    public void handleMessage(Message msg) {
        switch (msg.what){
            case 1:
                Log.d(TAG, "handleMessage: receive msg from Client " + msg.getData().getString("msg"));
                Messenger client = msg.replyTo;
                Message clientData = Message.obtain(null,1);
                Bundle bundle = new Bundle();
                bundle.putString("reply","嗯馆纳,我收到了你的消息,我是服務(wù)端");
                clientData.setData(bundle);
                try {
                    client.send(clientData);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
                break;
        }
        super.handleMessage(msg);
    }
}
  • 這里我將服務(wù)端接收消息的handler的Hand可Message方法改變了一下汹桦,讓他能在接收到消息之后直接再給客戶端發(fā)送一個消息

  • 這里可以看到鲁驶,我們發(fā)送消息的Messenger是從客戶端發(fā)送過來msg中得到的,能猜到吧舞骆,客戶端將自己進程所在的Messenger通過Messenger發(fā)送給服務(wù)端钥弯,然后服務(wù)端就可以拿到客戶端進程的Messenger句柄給客戶端發(fā)送消息了

  • 看一下客戶端的代碼

    private Messenger mCilentMessenger = new Messenger(new ClientMessenger());
    private static class ClientMessenger extends Handler{
    @Override
    public void handleMessage(Message msg) {
    switch (msg.what){
    case 1:
    Log.d(TAG, "handleMessage: 接收到服務(wù)端的消息 = " +msg.getData().getString("reply"));
    break;
    }

          super.handleMessage(msg);
      }
    

    }

void sendData(){
    Message msg = Message.obtain(null,1);
    msg.replyTo = mCilentMessenger;
    Bundle data = new Bundle();
    data.putString("msg","這是客戶端的消息");
    msg.setData(data);
    try{
        mService.send(msg);
    }catch (RemoteException e) {
        e.printStackTrace();
    }
}
  • 這里我添加了一個Messenger對象負責(zé)作為發(fā)送消息時候的replyTo參數(shù),然后將這個參數(shù)在發(fā)送消息時添加到replyTo參數(shù)上即可
  • 具體代碼其實也沒啥難度督禽,不太清楚的小伙伴認真分析一下即可
Messenger總結(jié)
  • 我們發(fā)現(xiàn)Messenger的核心就是使用本進程的Messenger在另外一個線程發(fā)送(Message)消息就可

使用AIDL

  • Binder之間通信還是分為客戶端和服務(wù)端

  • 先來看服務(wù)端脆霎,還是創(chuàng)建一個Service來監(jiān)聽客戶端的連接請求,然后創(chuàng)建一個AIDL文件狈惫,將暴露給客戶端的接口在這個AIDL文件中聲明睛蛛,最后在Service中實現(xiàn)這個AIDL接口即可

  • 先看看服務(wù)端AIDL接口的創(chuàng)建吧

    // IBookManager.aidl
    package com.example.learnretrofit.LearnMoreProcess;
    import com.example.learnretrofit.LearnMoreProcess.Book;
    interface IBookManager {
    List<Book> getBookList();
    void addBook(in Book book);
    }
    
  • 創(chuàng)建AIDL文件的方法上篇文章講過,在文章最開始有鏈接

  • 在AIDL文件中,并不是所有類型都可使用忆肾,他所能使用的數(shù)據(jù)類型如下

基本數(shù)據(jù)類型

  • String和CharSequence
  • List:只支持ArrayList荸频,里面每個元素都必須能被AIDL支持
  • Map:只支持HashMap,里面每個元素都必須能被AIDL支持
  • Parcelable:所有實現(xiàn)了Parcelable接口的對象客冈,使用時需要手動import
  • AIDL:所有的AIDL接口本身也可以在AIDL文件中使用旭从,使用時需要手動import

這里由于在AIDL接口中使用了Book類,所以必須在前面import郊酒,另外如果AIDL文件用到了自定義的Parcelable對象遇绞,必須新建一個和他同名的AIDL文件键袱,并在其中聲明他為Parcelable類型燎窘,因為在上面的接口定義中,我們使用了Book類蹄咖,所以我們必須定義一個aidl文件去聲明他

// Book.aidl
package com.example.learnretrofit.LearnMoreProcess;

parcelable Book;
  • 不知道大家注意到接口定義的方法的參數(shù)聲明前有個in褐健,這個參數(shù)是什么意思呢?AIDL中除了基本數(shù)據(jù)類型之外澜汤,其他類型的參數(shù)都必須標上方向蚜迅,in , out 俊抵,或者inout谁不,in表示輸入性參數(shù),out表示輸出型參數(shù)徽诲,inout表示輸入輸出型參數(shù)刹帕,因為這個參數(shù)代表著底層的操作,所以在標注的時候不能隨便標

  • 最后要注意的是AIDL接口中只支持方法谎替,不支持靜態(tài)變量偷溺,還有,服務(wù)端和客戶端的包結(jié)構(gòu)必須一致钱贯,因為客戶端需要反序列話服務(wù)器中和AIDL接口相關(guān)的所有類挫掏,如果包路徑不同就會無法反序列化成功

  • 那么上面的接口定義好了,我們就來看看我們的服務(wù)端怎么實現(xiàn)
    注意秩命,在編寫好aidl文件之后記得build-make project

    public class BookManagerService extends Service {

    private static final String TAG = "BookManagerService";

    private CopyOnWriteArrayList<Apple> mAppleList = new CopyOnWriteArrayList<>();

    private Binder mBinder = new IAppleManager.Stub() {
    @Override
    public List<Apple> getAppleList() throws RemoteException {
    return mAppleList;
    }

      @Override
      public void addApple(Apple apple) throws RemoteException {
          mAppleList.add(apple);
      }
    };
    
    @Override
    public void onCreate() {
      super.onCreate();
      mAppleList.add(new Apple(10,"紅富士"));
      mAppleList.add(new Apple(10,"早熟蘋果"));
    }
    
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
      return mBinder;
    }
    }
    
  • 這里的IAppleManager.Stub是系統(tǒng)給我們生成的東西尉共,他實際上是一個Binder類,然后我們先在onCreate方法里面添加兩個數(shù)據(jù)弃锐,注意到我們這里使用了CopyOnWriteArrayList袄友,因為他在原理上是支持并發(fā)讀寫的,* * 而AIDL支持的是抽象的List這個接口拿愧,雖然服務(wù)端返回的是CopyOnWriteArrayList杠河,但是在Binder中會按照List的規(guī)范去訪問數(shù)據(jù)并最終形成一個ArrayList傳遞給客戶端,所以我們在這里使用CopyOnWriteArrayList是完全合理的,類似的還有ConcurrentHashMap券敌,然后在注冊文件中注冊這個Service
    接著來看看客戶端的實現(xiàn)

  • 客戶端的話我們就需要綁定服務(wù)唾戚,然后將服務(wù)端返回的Binder對象轉(zhuǎn)換成AIDL接口,然后就可以通過這個接口去調(diào)服務(wù)端的遠程方法了待诅,看看客戶端的代碼

      void bindMyAppleService(){
      ServiceConnection mConnection = new ServiceConnection() {
          @Override
          public void onServiceConnected(ComponentName name, IBinder service) {
              IAppleManager appleManager = IAppleManager.Stub.asInterface(service);
              try {
                  List<Apple> list = appleManager.getAppleList();
                  Log.d(TAG, "onServiceConnected: query apple list ,list type is " + list.getClass().getCanonicalName());
    
                  for(Apple apple : list){
                      Log.d(TAG, "onServiceConnected: apple.color = " + apple.color + ",apple.size = " + apple.size);
                  }
              } catch (RemoteException e) {
                  e.printStackTrace();
              }
          }
          @Override
          public void onServiceDisconnected(ComponentName name) {}
      };
      bindService(new Intent(this,AppleManagerService.class),mConnection,Context.BIND_AUTO_CREATE);
    }
    
  • 在綁定成功之后直接跨進程獲取數(shù)據(jù)叹坦,注意這里是為了方便才這么寫,一般還是寫在新線程中比較好卑雁,因為這個服務(wù)端響應(yīng)時間是不定的募书,萬一響應(yīng)時間過長就會出現(xiàn)ANR

  • 如果你對AIDL還不太清楚,可能看這些有點模糊测蹲,煩請移駕androidIPC機制探索先把這個看看

  • 在啟動程序后打印出來的log信息為

    onServiceConnected: query apple list ,list type is java.util.ArrayList
    onServiceConnected: apple.color = 紅富士,apple.size = 10
    onServiceConnected: apple.color = 早熟蘋果,apple.size = 10
    
  • 可見客戶端收到的確實是Arraylist類型莹捡,證實了我們上面的說法,同時下面的信息也證明了我們的跨進程操作成功

  • 我們再試一下他的addApple方法扣甲,修改onServiceConnection方法

    public void onServiceConnected(ComponentName name, IBinder service) {
              IAppleManager appleManager = IAppleManager.Stub.asInterface(service);
              try {
                  List<Apple> list = appleManager.getAppleList();
                  Log.d(TAG, "onServiceConnected: query apple list ,list type is " + list.getClass().getCanonicalName());
                  for(Apple apple : list){
                      Log.d(TAG, "onServiceConnected: apple.color = " + apple.color + ",apple.size = " + apple.size);
                  }
                  Apple apple = new Apple(10,"綠色的蘋果");
    
                  appleManager.addApple(apple);
                  list = appleManager.getAppleList();
                  Log.d(TAG, "onServiceConnected: 重新接收");
                  for(Apple apple1 : list){
                      Log.d(TAG, "onServiceConnected: apple.color = " + apple1.color + ",apple.size = " + apple1.size);
                  }
    
              } catch (RemoteException e) {
                  e.printStackTrace();
              }
          }
    

log信息

  onServiceConnected: query apple list ,list type is java.util.ArrayL
 onServiceConnected: apple.color = 紅富士,apple.size = 10
 onServiceConnected: apple.color = 早熟蘋果,apple.size = 10
 onServiceConnected: 重新接收
 onServiceConnected: apple.color = 紅富士,apple.size = 10
 onServiceConnected: apple.color = 早熟蘋果,apple.size = 10
 onServiceConnected: apple.color = 綠色的蘋果,apple.size = 10

沒毛病篮赢,我們接下來看看AIDL更多的用法

AIDL深入探索

  • 我們看到,前面我們的跨進程通信都是一問一答式琉挖,就是客戶端是主導(dǎo)启泣,服務(wù)器是被主導(dǎo),那么會不會有一種需求示辈,我們不想去實時的查詢了寥茫,因為這樣太耗費時間,每次還不一定都有信息更新矾麻,所以纱耻,能不能有一種操作,客戶端去問服務(wù)器:當有信息更新的時候你能不能直接給我說呢射富?

  • 以上需求是一種典型的觀察者模式膝迎,服務(wù)器在有數(shù)據(jù)更新的時候通知每一位想知道這個消息的客戶端,可是怎么用AIDL實現(xiàn)呢胰耗?

  • 首先限次,我們需要提供一個AIDL接口,每個用戶都需要實現(xiàn)這個接口并對服務(wù)器申請數(shù)據(jù)更新的通知功能柴灯,當然用戶也可以隨時取消這種功能卖漫,這里我們創(chuàng)建一個

    // InewDataListener.aidl
    package com.example.learnretrofit.LearnMoreProcess;
    import com.example.learnretrofit.LearnMoreProcess.Apple;
    interface InewDataListener {
    void DataUpdata(in Apple apple);
    }
    

當然不止增加這個接口,我們還需要在原來接口做改動

// IBookManager.aidl
package com.example.learnretrofit.LearnMoreProcess;

import com.example.learnretrofit.LearnMoreProcess.Apple;
import com.example.learnretrofit.LearnMoreProcess.InewDataListener;

interface IAppleManager {
List<Apple> getAppleList();
void addApple(in Apple apple);

void registerListener(InewDataListener listener);
void unRegisterListener(InewDataListener listener);    
}
  • 這兩個方法很容易理解

  • 然后我們build -> make project

  • 然后服務(wù)端的Binder對象就會提示我們有兩個接口方法沒實現(xiàn)赠群,手動實現(xiàn)羊始,然后看看此時的服務(wù)端代碼有哪些改動吧

    private AtomicBoolean mAtomicBoolean = new AtomicBoolean(false);
    private CopyOnWriteArrayList<InewDataListener> mListeners = new CopyOnWriteArrayList<>();
    
    private Binder mBinder = new IAppleManager.Stub() {
      @Override
      public List<Apple> getAppleList() throws RemoteException {
          return mAppleList;
      }
    
      @Override
      public void addApple(Apple apple) throws RemoteException {
          mAppleList.add(apple);
      }
    
      @Override
      public void registerListener(InewDataListener listener) throws RemoteException {
          if(!mListeners.contains(listener)){
              mListeners.add(listener);
          }
      }
    
      @Override
      public void unRegisterListener(InewDataListener listener) throws RemoteException {
          if(mListeners.contains(listener)){
              mListeners.remove(listener);
          }
      }
    };
    
    
    public void onCreate() {
      super.onCreate();
      mAppleList.add(new Apple(10,"紅富士"));
      mAppleList.add(new Apple(10,"早熟蘋果"));
      new Thread(new ServiceWorker()).start();
    }
    
    private class ServiceWorker implements Runnable{
    
      @Override
      public void run() {
          while (!mAtomicBoolean.get()){
              try{
                  Thread.sleep(5000);
                  int appleId = mAppleList.size() + 1;
                  Apple apple = new Apple(appleId,"蘋果 " + appleId + " 號");
    
                  addNewApple(apple);
    
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
          }
      }
    }
    
    private void addNewApple(Apple apple) {
      mAppleList.add(apple);
      for(int i = 0;i < mListeners.size() ; i ++) {
          InewDataListener listener = mListeners.get(i);
          try {
              listener.DataUpdata(apple);
          } catch (RemoteException e) {
              e.printStackTrace();
          }
      }
    }
    
  • 分析起來不難理解,這里就不再說了查描,我們再看看客戶端的改動

    private InewDataListener mInewDataListener = new InewDataListener.Stub() {
      @Override
      public void DataUpdata(Apple apple) throws RemoteException {
          mHandler.obtainMessage(10,apple).sendToTarget();
      }
    };
    
    void bindMyAppleService(){
      ServiceConnection mConnection = new ServiceConnection() {
          @Override
          public void onServiceConnected(ComponentName name, IBinder service) {
              IAppleManager appleManager = IAppleManager.Stub.asInterface(service);
              try {
                  List<Apple> list = appleManager.getAppleList();
                  Log.d(TAG, "onServiceConnected: query apple list ,list type is " + list.getClass().getCanonicalName());
                  for(Apple apple : list){
                      Log.d(TAG, "onServiceConnected: apple.color = " + apple.color + ",apple.size = " + apple.size);
                  }
                  Apple apple = new Apple(10,"綠色的蘋果");
    
                  appleManager.addApple(apple);
                  list = appleManager.getAppleList();
                  Log.d(TAG, "onServiceConnected: 重新接收");
                  for(Apple apple1 : list){
                      Log.d(TAG, "onServiceConnected: apple.color = " + apple1.color + ",apple.size = " + apple1.size);
                  }
                  appleManager.registerListener(mInewDataListener);
    
              } catch (RemoteException e) {
                  e.printStackTrace();
              }
          }
          @Override
          public void onServiceDisconnected(ComponentName name) {}
      };
      bindService(new Intent(this,AppleManagerService.class),mConnection,Context.BIND_AUTO_CREATE);
    }
    
    
      @SuppressLint("HandlerLeak")
    private Handler mHandler = new Handler(){
      @Override
      public void handleMessage(Message msg) {
          switch (msg.what) {
              case 10:
                  Log.d(TAG, "handleMessage: receive The new Data " + msg.obj);
          }
          super.handleMessage(msg);
      }
    };
    
  • 定義了接口突委,聲明了當被回調(diào)時候的動作柏卤,接著在onServiceConnection中注冊接口

  • 這里要注意的是接口回調(diào)是在Binder線程池中執(zhí)行的,這一點可以通過我的上篇博客看到匀油,這里不再多說缘缚,因此我們要進行UI操作的話,還是盡量使用Handler將他弄到主線程使用

  • 這里運行程序看到log信息為

    onServiceConnected: query apple list ,list type is java.util.ArrayList
    onServiceConnected: apple.color = 紅富士,apple.size = 10
    onServiceConnected: apple.color = 早熟蘋果,apple.size = 10
    onServiceConnected: 重新接收
    onServiceConnected: apple.color = 紅富士,apple.size = 10
    onServiceConnected: apple.color = 早熟蘋果,apple.size = 10
    onServiceConnected: apple.color = 綠色的蘋果,apple.size = 10
    handleMessage: receive The new Data 蘋果 4 號
    handleMessage: receive The new Data 蘋果 5 號
    handleMessage: receive The new Data 蘋果 6 號
    

AIDL解決跨進程取消注冊問題

  • Android系統(tǒng)為我們提供了一個專門用于刪除跨進程listener的接口–RemoteCallbackList敌蚜,他是一個泛型桥滨,支持管理任意的AIDL接口,這點從他的聲明就可以看出來弛车,因為所有的AIDL接口都繼承自IInterface接口齐媒,這點如果是因為我們寫的aidl接口都會由系統(tǒng)為我們自行生成一個java文件,而標準格式就是都會繼承自IInterface接口纷跛,下面是這個RemoteCallbackList接口的聲明

    public class RemoteCallbackList<E extends IInterface>
    

它的工作原理很簡單喻括,在他的內(nèi)部有一個Map結(jié)構(gòu)專門用來保存所有的AIDL回調(diào),這個Map的key是IBinder類型忽舟,value是Callback類型

ArrayMap<IBinder, Callback> mCallbacks = new ArrayMap<IBinder, Callback>();

其中Callback中封裝了真正的遠程Listener双妨,當客戶端注冊listener的時候淮阐,他會把這個listener信息存入mCallback中叮阅,其中key和value分別通過下面的方式獲得


1.png
  • 到這里的時候有沒有點明悟了,還記得我們負責(zé)注冊的過程是誰來完成的嗎泣特?是服務(wù)端給我們返回的一個Binder對象浩姥,而我們的跨進程通信都是基于這個對象的,而一般一個組件都是只有一個Binder的状您,所以勒叠?我們每個客戶端與服務(wù)器之間的底層Binder是一一對應(yīng)的,這點是不會變的膏孟,所以就有了上面的寫法眯分,我們存binder就行了,到時候你想取消注冊柒桑,那就看看你的底層是哪個binder

  • RemoteCallbackList這個接口不光可以解決這個問題弊决,他還有一個很有用的功能,那就是當客戶端進程終止時魁淳,他會自動移除客戶端所注冊的listener

  • 另外飘诗,RemoteCallbackList內(nèi)部自動實現(xiàn)了線程同步的功能,所以我們用它的時候不需要做額外的線程同步工作

  • 說了這么多界逛,我們來看看他到底怎么用吧

  • 服務(wù)端的改變

  • 先用RemoteCallbackList創(chuàng)建新的集合代替原來的集合

    private RemoteCallbackList<InewDataListener> mCallbackList = new RemoteCallbackList<>();
    
    //    private CopyOnWriteArrayList<InewDataListener> mListeners = new CopyOnWriteArrayList<>();
    
  • 修改注冊和取消注冊兩個接口的實現(xiàn)

    public void registerListener(InewDataListener listener) throws RemoteException {
    //            if(!mListeners.contains(listener)){
    //                mListeners.add(listener);
    //            }
    
          mCallbackList.register(listener);
      }
    
      @Override
      public void unRegisterListener(InewDataListener listener) throws RemoteException {
    //            if(mListeners.contains(listener)){
    //                mListeners.remove(listener);
    //            }
          mCallbackList.unregister(listener);
      }
    
  • 接下來修改通知的方法

    private void addNewApple(Apple apple) {
      mAppleList.add(apple);
    //        for(int i = 0;i < mListeners.size() ; i ++) {
    //            InewDataListener listener = mListeners.get(i);
    //            try {
    //                listener.DataUpdata(apple);
    //            } catch (RemoteException e) {
    //                e.printStackTrace();
    //            }
    //        }
      final int N = mCallbackList.beginBroadcast();
      for (int i = 0;i < N;i++){
          InewDataListener listener = mCallbackList.getBroadcastItem(i);
          if(listener != null){
              try{
                  listener.DataUpdata(apple);
              } catch (RemoteException e) {
                  e.printStackTrace();
              }
          }
      }
      mCallbackList.finishBroadcast();
    
    }
    
  • 是不是非常簡單呢昆稿,我們來看一下具體效果吧,我在客戶端進程的activity中添加一個取消注冊的按鈕

  • 運行之后的log

    unRegisterListener: 取消之前的數(shù)量是 1
    unRegisterListener: 取消之后的數(shù)量是 0
    
  • 注意:使用RemoteCallbackList的時候息拜,我們無法像操作List一樣去操作它溉潭,他并不是一個List净响,而且,遍歷RemoteCallbackList的時候喳瓣,必須要將代碼寫在CallbackList.beginBroadcast()和CallbackList.finishBroadcast()這兩個方法之間别惦,無論是遍歷,還是哪怕只是獲取個元素個數(shù)

    final int N = mCallbackList.beginBroadcast();
      for (int i = 0;i < N;i++){
          InewDataListener listener = mCallbackList.getBroadcastItem(i);
          if(listener != null){
              try{
                  listener.DataUpdata(apple);
              } catch (RemoteException e) {
                  e.printStackTrace();
              }
          }
      }
    mCallbackList.finishBroadcast();
    
  • 到這里關(guān)于AIDL的基本東西就說完了夫椭,這里說幾點注意

  • 我們必須清楚什么方法執(zhí)行在主線程掸掸,什么方法執(zhí)行在Binder線程池,一些耗時的或者不可知的操作盡可能放在多線程中解決
  • 涉及到UI操作的蹭秋,如果當前線程非主線程扰付,要使用Handler切換到主線程再進行操作
    還有,binder是有可能意外死亡的仁讨,為了我們程序的健壯性羽莺,我們有必要給Binder設(shè)置死亡監(jiān)聽

AIDL拓展

  • 如果我們的服務(wù)想要更健全的話,我們有必要給他加上權(quán)限驗證功能洞豁,因為在默認情況下盐固,我們的遠程服務(wù)是任何人都能連接的,這是我們不愿意看到的丈挟,所以我們必須給服務(wù)加入權(quán)限驗證功能
    怎么加這個功能呢刁卜?我們想一下,服務(wù)最早接觸客戶端的方法是哪個方法曙咽?
  • 當然是onBind方法了蛔趴,我們在綁定服務(wù)的時候,會使用onBind方法返回的對象來拿到我們操作服務(wù)的句柄,那么我們的權(quán)限驗證就可以在這里做
  • 不過在做之前我們首先應(yīng)該定義自己的權(quán)限
  • 這里簡單說一下自定義權(quán)限
自定義權(quán)限簡單說明
  • 在AndroidManifest.xml中,像這個樣子

    <permission android:description="string resource"
         android:icon="drawable resource"
         android:label="string resource"
         android:name="string"
         android:permissionGroup="string"
         android:protectionLevel=["normal" | "dangerous" |"signature" | "signatureOrSystem"] />
    
  • 這里對各個屬性做個說明


    2.png
  • protectionLevel權(quán)限等級的說明


    3.png
  • 然后我們在這里在我們的AndroidManifest.xml聲明權(quán)限

    <permission
      android:name="com.example.learnretrofit.LearnMoreProcess.permission.ACCESS_APPLE_SERVICE"
      android:protectionLevel="normal"/>
    
  • 然后在我們的onBind方法添加權(quán)限驗證

      public IBinder onBind(Intent intent) {
      int check = checkCallingOrSelfPermission("com.example.learnretrofit.LearnMoreProcess.permission.ACCESS_APPLE_SERVICE");
      if(check == PackageManager.PERMISSION_DENIED){
          return null;
      }
      return mBinder;
    }
    
  • 如果我們自己的應(yīng)用想要綁定我們的服務(wù)冰更,只需要在他的AndroidManifest.xml中聲明權(quán)限即可

    <uses-permission android:name="com.example.learnretrofit.LearnMoreProcess.permission.ACCESS_APPLE_SERVICE"/>
    
  • 當然利朵,權(quán)限驗證不僅僅局限于AIDL,他可以用在所有你想用的地方

  • 第二種權(quán)限驗證的方法

  • 我們可以在服務(wù)端的Binder的onTransact方法中進行權(quán)限驗證,如果驗證失敗,就直接返回false,這樣服務(wù)端就不會去執(zhí)行客戶端想要的邏輯 羔挡,也達到了權(quán)限驗證的效果

  • 我們甚至還可以采用UID和PID來做驗證,通過getCallingUID和getCallingPid來拿到客戶端所屬進程的UID和Pid派撕,這樣我們就可以驗證包名婉弹,等具體的操作大家具體去嘗試吧

  • 下面我們繼續(xù)學(xué)習(xí)另外的IPC方式

使用ContentProvider

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市终吼,隨后出現(xiàn)的幾起案子镀赌,更是在濱河造成了極大的恐慌,老刑警劉巖际跪,帶你破解...
    沈念sama閱讀 218,546評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件商佛,死亡現(xiàn)場離奇詭異喉钢,居然都是意外死亡,警方通過查閱死者的電腦和手機良姆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,224評論 3 395
  • 文/潘曉璐 我一進店門肠虽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人玛追,你說我怎么就攤上這事税课。” “怎么了痊剖?”我有些...
    開封第一講書人閱讀 164,911評論 0 354
  • 文/不壞的土叔 我叫張陵韩玩,是天一觀的道長。 經(jīng)常有香客問我陆馁,道長找颓,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,737評論 1 294
  • 正文 為了忘掉前任叮贩,我火速辦了婚禮击狮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘益老。我一直安慰自己彪蓬,他們只是感情好,可當我...
    茶點故事閱讀 67,753評論 6 392
  • 文/花漫 我一把揭開白布杨箭。 她就那樣靜靜地躺著寞焙,像睡著了一般。 火紅的嫁衣襯著肌膚如雪互婿。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,598評論 1 305
  • 那天辽狈,我揣著相機與錄音慈参,去河邊找鬼。 笑死刮萌,一個胖子當著我的面吹牛驮配,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播着茸,決...
    沈念sama閱讀 40,338評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼壮锻,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了涮阔?” 一聲冷哼從身側(cè)響起猜绣,我...
    開封第一講書人閱讀 39,249評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎敬特,沒想到半個月后掰邢,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體牺陶,經(jīng)...
    沈念sama閱讀 45,696評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,888評論 3 336
  • 正文 我和宋清朗相戀三年辣之,在試婚紗的時候發(fā)現(xiàn)自己被綠了掰伸。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,013評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡怀估,死狀恐怖狮鸭,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情多搀,我是刑警寧澤怕篷,帶...
    沈念sama閱讀 35,731評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站酗昼,受9級特大地震影響廊谓,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜麻削,卻給世界環(huán)境...
    茶點故事閱讀 41,348評論 3 330
  • 文/蒙蒙 一蒸痹、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧呛哟,春花似錦叠荠、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,929評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至鳖孤,卻和暖如春者娱,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背苏揣。 一陣腳步聲響...
    開封第一講書人閱讀 33,048評論 1 270
  • 我被黑心中介騙來泰國打工黄鳍, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人平匈。 一個月前我還...
    沈念sama閱讀 48,203評論 3 370
  • 正文 我出身青樓框沟,卻偏偏與公主長得像,于是被迫代替她去往敵國和親增炭。 傳聞我的和親對象是個殘疾皇子忍燥,可洞房花燭夜當晚...
    茶點故事閱讀 44,960評論 2 355

推薦閱讀更多精彩內(nèi)容