进程间通信的方式

319 阅读19分钟

零、IPC—进程间通信的基础概念

1、Android中的多进程模式

Android中多进程是指一个应用中存在多个进程的情况,因此这里不讨论两个应用之间的情况,首先在Android中使用多进程只有一种方法,那就是给四大组件指定android:process。默认进程名是包名。

 <activity
      android:name=".MainActivity"
      android:configChanges="orientation|screenSize"
      android:launchMode="standard">
      <intent-filter>
          <action android:name="android.intent.action.MAIN" />
          <category android:name="android.intent.category.LAUNCHER" />
      </intent-filter>
  </activity>

  <activity
      android:name=".SecondActivity"
      android:process=":remote" />

  <activity
      android:name=".ThirdActivity"
      android:process="com.liuguilin.multiprocesssample.remote" />

上面示例分别为SecondActivity和ThirdActivity指定了process属性,并且属性值不同,当SecondActivity启动时,系统会为它创建一个单独的进程,进程名为con.liuguilin.multiprocesssample:remote;当ThridActivity启动的时候,系统也会为他创建一个单独的进程,进程名com.liuguilin.multiprocesssample.remote,同时,入口Activity是MainActivity,没有为他指定process属性,那么他运行在默认进程中,当我们运行的时候就可以看到,进程列表末尾存在三个进程。查看进程方法如下:

C:\Users\hWX393093>adb shell ps | find "hfy"
u0_a128   27860 501   2278864 81132          0 0000000000 S demo.hfy.com.viewtest

SecondActivity和ThirdActivity的进程名分别为“:remote”和包名.remote,区别在两面:

a、”:“的含义,是指在当前的进程名前附加上包名,这是一种简写的方法,对于SecondActivity来说,他完整的进程名为 com.liuguilin.multiprocesssample:remote,而对于ThirdActivity中的申明方式,它是一种完整的命名方式,不会附加包名信息;

b、进程以”:“开头的属于当前应用的私有进程,其他应用的组件不可以和它跑在同一个进程中,而进程名不以”:“开头的进程属于全局进程,其他应用通过ShareUID方式可以和它跑在同一进程中。 一般使用私有进程。即“:”的形式。

Android为每个进程都分配一个独立的虚拟机,不同的虚拟机在内存上有不同的地址空间,这导致在不同的虚拟机中访问同一个类的对象会产生多份副本,就我们这个例子来说,在两个进程中都存在一个UserManager类,并且这两个类是互不干扰的,在一个进程中修改mUseld的值只会影响当前进程,对其他进程不会造成任何影响,这样我们就可以理解为什么在MainActivitv中修改了mUserld的值,但是在 SecondActivity 中这个值却 没有发生改变这个现象。 所有运行在不同进程中的四大组件,只要它们之间需要通过内存来共享数据,都会共享失败这也是多进程带来的主要影响,正常情况下四大组件中间不可能不通过一些中间层来共享数据,那么通过简单地指定进程名来开启多进程都会无法正确运行。

一般来说,使用多进程会造成如下几方面的问题:

a.静态成员和单例模式完全失效。----因为不同内存空间,是互不干扰的。

b.线程同步机制完全失效。----因为不是同一块内存,即锁对象不是同一个。

c.SharedPreferences的可靠性下降。----底层是xml,并发读写可能有问题的。

d.Application会多次创建。----运行在不同进程的组件是属于不同虚拟机和Application。

2、IPC基础概念 — Serializable接口、Parcelable接口、Binder

Parcelable和Serializable的区别

a、Serializable是Java中的序列化接口,其使用起来简单但是开销很大,序列化和反序列化过程需要大量I/O操作。

b、Parcelable是Andrord中的序列化方式,因此更适合用在Android平台上,它的缺点就是使用起来稍微麻烦点,但是它的效率很高,这是Android推荐的序列化方式,因此我们要首选Parcelatle。

c、Parcelable主要用在内存序列化上,通过Parcelable将对象序列化到存储设备中或者将对象序列化后通过网络传输也都是可以的,但是这个过程会稍显复杂,因此在这两种情况下建议大家使用Serializabie。

Binder是什么?

a、Binder是Android中的一个类,它实现了IBinder接口。

b、从IPC角度来说,Binder是Android中的一种跨进程通信方式。

c、Binder还可以理解为一种虚拟的物理设备,它的设备驱动是/dev/binder,该通信方式在Linux中没有;

d、从AndroidFramework角度来说,Binder是ServiceManager连接各种Manager(ActivityManager、WindowManager,等等)和相应Managerservice的桥梁:

e、从Android应用层来说,Binder是客户端和服务端进行通信的媒介,当bindService的时候,服务端会返回一个包含了服务端业务调用的Binder对象,通过这个Binder对象,客户端就可以获取服务端提供的服务或者数据,这里的服务包括普通服务和基于AIDL的服务。

Android开发中,Binder主要用在Service中,包括AIDL和Messenger,其中普通Service中的Binder不涉及进程间通信,所以较为简单,无法触及Binder的核心,而Messenger的的底层其实是AIDL。

3、Binder详解

Binder工作机制:

a、当客户端发起远程请求时,由于当前线程会被挂起直至服务器进程返回数据,所以如果一个远程方法是很耗时的,那么不能再UI线程中发起此远程请求。

b、由于服务器的Binder方法运行在Binder的线程池中,所以Binder方法不管是否耗时都应该采用同步的方式去实现,因为他已经运行在一个线程中了。

Binder死亡:Binder运行在服务端进程,如果服务端进程由于某种原因异常终止,这个时候我们到服务端的Binder连接断裂(称之为Binder死亡),会导致我们的远程调用失败。更为关键的是,如果我们不知道Binder连接已经断裂,那么客户端的功能就会受到影响。为了解决这个问题,Binder中提供了两个配对的方法linkTopeath和unlinkTopeath,通过 linkToDeath我们可以给Binder设置一个死亡代理,当Binder死亡时,我们就会收到通知,这个时候我们就可以重新发起连接请求从而恢复连接。那么到底如何给Binder设置死亡代理成?也很简单:

首先,生命一个Deathleciient对象、Deathleciient是一个接口,其内部只有一个方法binderDied,我们需要实现这个方法,当Binder死亡的时候,系统就会回调binderDied方法,然后我们就可以移出之前绑定的binder代理并重新绑定远程服务;

private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
        @Override
        public void binderDied() {
            if(mBookManager == null){
                return;
            }
            mBookManager.asBinder().unlinkToDeath(mDeathRecipient,0);
            mBookManager = null;
            //这个可以重新绑定远程service
        }
    };

其次,在客户端绑定远程服务成功之后,给binder设置死亡代理;

mService= IMessageBoxManager.Stub.asInterface(binder);
binder.linkToDeath(mDeathRecipient,0);

其次linkToDeath的第二个参数为标记位,我们直接设置为0即可,经过上述的步骤,我们就可以给Binder设置死亡代理了,当Binder死亡之后我们就可以收到通知了,另外。通过Binder的方法isBinderAlive也可以判断Binder是否死亡。

一、使用Bundle

在一个进程中使用另外一个进程的Activity,Service,Receiver,在Bunlder中附加我们需要传输给远程进程的信息,然后用intent发送过去,当然,我们传输的数据必须能够序列化,比如基本数据类型,实现了Parcelable接口的对象,实现了Serializable接口的对象以及一些Android支持的特殊对象(具体内容可以看下Bundler这个类,Bundler不支持的类型我们无法通过他在进程间传递数据)——这是一种很简单的进程间通信方式。

二、使用文件共享

1、文件共享是一种不错的进程间通讯的方式,两个进程通过读/写同一个文件来交换数据,比如A进程把数据写入文件,B再去读取。

2、通过文件共享的方式也是有局限性的,如果并发读/写,那么我们读出的内容就有可能不是最新的,如果是并发写的话那就更严重了。

3、SharedPreferences也属于文件的一种,但是由于系统对它的读/写有一定的缓存策略,即在内存中会有一份SharedPreferences文件的缓存,因此在多进程模式下,系统对它的读/写就变得不可靠,当面对高并发的读/写访问Sharedpreferences有很大几率会丢失数据,因此,不建议在进程间通信中使SharedPreferences。

三、使用Messenger

在不同进程中传递Message对象,在Message中放入我们需要传递的数据,就可以轻松地实现数据的进程间传递了。Messenger是一种轻量级的IPC方案,它的底层实现是AIDL。

服务端进程

首先,我们需要在服务端创建一个Service来处理客户端的连接请求,同时创建一个Handler并通过它来创建一个Messenger对象,然后在Service的onBind中返回这个Messenger对象底层的Binder即可。

客户端进程

客户端进程中,首先要绑定服务端的Service,绑定成功后用服务端返回的IBinder对象创建一个Messenger,通过这个Messenger就可以向服务端发送消息了,发消息类型为 Message对象。

如果需要服务端能够回应客户端,就和服务端一样,我们还需要创建一个Handdler并创建一个新的Messenger,并把这个Messenger对象通过Message的replyTo参数传递给服务端,服务端通过这个replyTo参数就可以回应客户端。

1、看一个简单点的例子,这个例子中服务端无法回应客户端。

服务端:这是服务端的典型代码,可以看到MessengerHandler用来处理客户端发送的消息,并从消息中取出客户端发来的文本信息,而mMessenger是一个Messenger对象,他和MessengerHandler相关联,并在onBind方法中返回他里面的IBind对象

public class MessengerService extends Service {
    public static final String TAG = "MessengerService";
    private static class MessengerHandler extends Handler{
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case 100:
                    Log.i(TAG,"数据:" + msg.getData());
                    break;
                default:
                super.handleMessage(msg);
            }
        }
    }
    private final Messenger mMessenger = new Messenger(new MessengerHandler());
    @Override
    public IBinder onBind(Intent intent) {
        return mMessenger.getBinder();
    }
}

客户端如下:

public class MessengerActivity extends AppCompatActivity {
    public static final String TAG = "MessengerActivity";
    private Messenger mService;
    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mService =  new Messenger(service);
            Message msg = Message.obtain(null,10);
            Bundle data = new Bundle();
            data.putString("msg","hell this is client");
            msg.setData(data);
            try {
                mService.send(msg);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
    };
    @Override
    protected void onCreate( Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_messenger);
        Intent intent = new Intent(this,MessengerService.class);
        bindService(intent,mConnection, Context.BIND_AUTO_CREATE);
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        unbindService(mConnection);
    }
}

通过上面的例子可以看出,在Messenger中进行数据传递必须将数据放入Messsage中,而Messenger和Message都实现了Parcelable接口,因此可以跨进程传输,简单来说,Messebger所支持的数据类型就是Message所支持的传输类型。实际上,通过 Messenger来传递Message,Message中能使用的载体就只有what、arg1、arg2、Bundle以及replyTo。Message的另一个字段object在同一个进程中是很实用的,但是在进程间通信的时候,在Android2.2以前object字段不支持跨进程传输,即便是2.2以后,也仅仅是系统提供的实现了Parcelable接口的对象才能通过它来传输。这就意味着我们自定义的Parcelable对象是无法通过object字段来传输的,读者可以试一下。非系统的Parcelable对象的确无法通过object字段来传输,这也导致了object字段的实用性大大降低,所幸我们还有Bundle,Bundle中 可以支持大量的数据类型。

2、有时候我们还需要能回应客户端,下面就介绍下:

首先看服务端的修改,服务端只需要修改MessengerHandler,当收到消息后,会立即回复一条消息给客户端

private static class MessengerHandler extends Handler{
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case 100:
                    Log.i(TAG,"数据:" + msg.getData());
                    Messenger messenger = msg.replyTo;
                    Message reply = Message.obtain(null,200);
                    Bundle bundle = new Bundle();
                    bundle.putString("reply","收到你的消息,等下回复");
                    try {
                        messenger.send(reply);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    }

为了接受服务端的恢复,客户端也需要准备一个接收消息的Messenger和handler,如下:

private Messenger mGetReplyMessenger = new Messenger(new MessengerHandler());

    private static class MessengerHandler extends Handler{
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what){
                case 200:
                    Log.i(TAG,"Service:" + msg.getData().getString("reply"));
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    }

除了上述的修改,还有关键的一点,当客户端发送消息的时候,需要把接收服务端回复的Messenger通过Message的replyTo参数传递给服务端,如下:

private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mService =  new android.os.Messenger(service);
            Message msg = Message.obtain(null,100);
            Bundle data = new Bundle();
            data.putString("msg","hell this is client");
            //注意这句话
            msg.replyTo  = mGetReplyMessenger;
            msg.setData(data);
            try {
                mService.send(msg);
            } catch (RemoteException e) {
                e.printStackTrace();
            }

        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

四、使用AIDL

Messenger是以串行的方式处理客户端发来的消息,如果大量的消息同时发送到服务端,服务端仍然只能一个个处理,如果有大量的并发请求,那么用Messenger就不太合适了。同时,Messenger的作用主要是为了传递消息,很多时候我们可能需要跨进程调用服务端的方法,这种情形用Messenger就无法做到了,但是我们可以使用AIDL来实现跨进程的方法调用。

AIDL是Messenger的底层实现,因此Messenger本质上也是AIDL,只不过系统为我们做了封装,从而方便上层的调用而已。前面介绍了Binder的概念,在Binder的基础上我们可以更加容易地理解AIDL。这里先介绍使用AIDL 来进行进程间通信的流程,分为服务端和客户端两个方面。

1)进程间的Listener

假设有一种需求:用户不想时不时地去查询图书列表了,太累了,于是,他去问图书馆,“当有新书时能不能把书的信息告诉我呢?”。大家应该明白了,这就是一种典型的观察者模式,每个感兴趣的用户都观察新书,当新书到的时候,图书馆就通知每一个对这本书感兴趣的用户,这种模式在实际开发中用得很多,下面我们就来模拟这种情形。

首先,我们需要提供一个AIDL接口,每个用户都需要实现这个接口并且向图书馆申请新书的提醒功能,当然用户也可以随时取消这种提醒。之所以选择AIDL接口而不是普通接口,是因为AIDL中无法使用普通接口。这里我们创建一个IOnNewBookArrivedListener.aidl文件,我们所期望的情况是:当服务端有新书到来时,就会通知每一个己经申请提醒功能的用户。从程序上来说就是调用所有IOnNewBookArivedListener对象中的onNewBookArived方法,并把新书的对象通过参数传递给客户端,内容如下所示。

// IOnNewBookArrivedListener.aidl
package com.liuguilin.ipcsample;
import com.liuguilin.ipcsample.Book;

interface IOnNewBookArrivedListener {
   void onNewBookArrived(in Book newBook);
}

除了要新增加一个AIDL外,还需要在原有的接口中添加两个新的方法:

// IBookManager.aidl
package com.liuguilin.ipcsample;

import com.liuguilin.ipcsample.Book;
import com.liuguilin.ipcsample.IOnNewBookArrivedListener.aidl;

interface IBookManager {

    List<Book>getBookList();
    void addBook(in Book book);
    void registerListener(IOnNewBookArrivedListener listener);
    void unregisterListener(IOnNewBookArrivedListener listener);
}

接下来,修改服务端,实现注册、解注册,并且每隔5s向感兴趣的用户提醒:

public class BookManagerService extends Service {

    private static final String TAG = "BookManagerService";

    private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>();

    private AtomicBoolean mIsServiceDestoryed = new AtomicBoolean(false);

    private CopyOnWriteArrayList<IOnNewBookArrivedListener> mListenerList = new CopyOnWriteArrayList<>();

    private Binder mBinder = new IBookManager.Stub() {

        @Override
        public List<Book> getBookList() throws RemoteException {
            return mBookList;
        }

        @Override
        public void addBook(Book book) throws RemoteException {
            mBookList.add(book);
        }

        @Override
        public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException {
            if(!mListenerList.contains(listener)){
                mListenerList.add(listener);
            }else {
                Log.i(TAG,"already exists");
            }
            Log.i(TAG,"registerListener size:" + mListenerList.size());
        }

        @Override
        public void unregisterListener(IOnNewBookArrivedListener listener) throws RemoteException {
                if(mListenerList.contains(listener)){
                    mListenerList.remove(listener);
                    Log.i(TAG,"remove listener succeed");
                }else {
                    Log.i(TAG,"not found, can not remove listener");
                }
            Log.i(TAG,"unregisterListener size:" + mListenerList.size());
        }
    };

    @Override
    public void onCreate() {
        super.onCreate();

        mBookList.add(new Book(1,"Android"));
        mBookList.add(new Book(1,"IOS"));
        new Thread(new ServiceWorker()).start();
    }

    @Override
    public IBinder onBind(Intent intent) {

        return mBinder;
    }

    @Override
    public void onDestroy() {
        mIsServiceDestoryed.set(true);
        super.onDestroy();
    }

    private class ServiceWorker implements Runnable{

        @Override
        public void run() {

            while (!mIsServiceDestoryed.get()){
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                int bookId = mBookList.size()+1;
                Book newBook = new Book(bookId,"new book#" + bookId);
                try {
                    onNewBookArrived(newBook);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private void onNewBookArrived(Book book) throws RemoteException{
        mBookList.add(book);
        Log.i(TAG,"onNewBookArrived size:" + mListenerList.size());
        for (int i = 0; i < mListenerList.size(); i++) {
            IOnNewBookArrivedListener listener = mListenerList.get(i);
            Log.i(TAG,"listener: "+ listener);
            listener.onNewBookArrived(book);
        }
    }
}

修改一下客户端的代码,主要是两方面:

1、客户端要注册IOnNewBookArrivedListener到远程的服务器,这样当有新书时服务端才能通知客户端,同时在我们的Activity的onDestory方法里面去取消绑定;

2、另一方面,当有新书的时候,服务端会回调客户端的OnNewBookArrived方法,这个方法是在客户端的Binder线程池中执行,因此,为了便于进行UI操作,我们需要有一个Handler可以将其切换到客户端的主线程去执行(Binder方法都是在Bind线程池中执行的)。 把代码贴上:

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private static final int MESSAGE_WHAT_NEW_BOOK_ARRIVED = 1;
    private IBookManager iBookManager;
    private boolean isBookManagerServiceConnected;
    private ServiceConnection connection;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        initView();
    }
    private void initView() {
        //略
    }

    @Override
    public void onClick(View v) {
        //略
    }

    /**
     * 调用远程服务:getBookList
     */
    private void getBookList() {
        if (!isBookManagerServiceConnected
                || iBookManager == null) {
            bindBookManageService();
        }

        if (iBookManager == null) {
            return;
        }

        //如果是耗时操作,则要在子线程进行
        new Thread(new Runnable() {
            @Override
            public void run() {
                List<Book> booklist = null;
                try {
                    booklist = iBookManager.getBooklist();
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
                if (booklist != null) {
                    for (Book book : booklist) {
                        Log.i(TAG, "getBookList: " + book.toString());
                    }
                }
            }
        }).start();

    }

    /**
     * 调用远程服务:addBook
     */
    private void addBook() {
        if (!isBookManagerServiceConnected
                || iBookManager == null) {
            bindBookManageService();
        }

        if (iBookManager == null) {
            return;
        }

        Book book = new Book("《Android》");
        try {
            iBookManager.addBook(book);
            Log.i(TAG, "addBook: " + book.toString());
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    /**
     * 绑定服务
     */
    private void bindBookManageService() {

        if (isBookManagerServiceConnected) {
            Toast.makeText(this, "isBookManagerServiceConnected : true", Toast.LENGTH_SHORT).show();
            return;
        }

        Intent intent = new Intent();
//        intent.setAction("BOOK_MANAGE_SERVICE");
        intent.setPackage("simplenet.hfy.com.demoapplication2");

        connection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {

                iBookManager = IBookManager.Stub.asInterface(service);
                isBookManagerServiceConnected = true;

                try {
                    //注册新书到达监听
                    iBookManager.registerOnNewBookArrivedListener(onNewBookArrivedListener);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }

                Log.i(TAG, "onServiceConnected: ");
            }

            @Override
            public void onServiceDisconnected(ComponentName name) {
                Log.i(TAG, "onServiceDisconnected: ");
                isBookManagerServiceConnected = false;
            }
        };

        bindService(intent, connection, BIND_AUTO_CREATE);
    }


    IOnNewBookArrivedListener onNewBookArrivedListener = new IOnNewBookArrivedListener.Stub() {

        @Override
        public void onNewBookArrived(Book newBook) throws RemoteException {

            //由于Binder的方法是在Binder线程池运行,所以 为了便于UI操作,此方法切到主线程执行
            Message message = Message.obtain();
            message.what = MESSAGE_WHAT_NEW_BOOK_ARRIVED;
            message.obj = newBook;
            uiHandler.sendMessage(message);
        }
    };

    //UIHandler
    @SuppressLint("HandlerLeak")
    Handler uiHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MESSAGE_WHAT_NEW_BOOK_ARRIVED:

                    Book newBook = (Book) msg.obj;

                    Log.i(TAG, "handleMessage: newBook:" + newBook.toString());
                    Toast.makeText(MainActivity.this,
                            "handleMessage: newBook:" + newBook.toString(), Toast.LENGTH_SHORT).show();
                    break;
                default:
                    break;
            }
        }
    };

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (isBookManagerServiceConnected) {
            unbindService(connection);
            isBookManagerServiceConnected = false;

            try {
                //解注册,此处可能会有问题,因为onNewBookArrivedListener传到服务端就不是注册时传过去的那个对象了(反序列化的结果)
                iBookManager.unregisterOnNewBookArrivedListener(onNewBookArrivedListener);
            } catch (RemoteException e) {
                e.printStackTrace();
            }

        }
    }
}

2)RemoteCallbackList

如果你以为AIDL就这样结束了,那你就错了,AIDL远不止这么简单,目前我们还有一些难点还没有涉及。

从上面的代码我们可以看出,当BookManagerActivity关闭时,我们会在onDestory中去接触已经注册的服务端的listener,这就相当于我们不想再接收图书馆的新书提醒,所以我们可以随时取消这个提醒服务。按Back键退出BookManagerActivity,发现如下日志:

not found, can not remove listener

从上log可以看出,在解注册的过程中,服务端竟然无法找到我们之前注册的那个listener,在客户端我们注册和解注册时明明传的是同一个listener啊!其实,这是必然的,这种解注册的处理方式在日常开发过程中时常使用到,但是放到多进程中却无法奏效,因为Binder会把客户端传递过来的对象重新转化并生成一个新的对象。虽然在注册和解注册过程中使用的是同一个客户端对象,但是通过Binder传递到服务端后会产生两个全新的对象。别忘了对象是不能跨进程直接传输的,对象的跨进程传输本质上都是反序列化的过程,这就是为什么AIDL中的自定义对象都必须要实现Parcelable接口的原因。

那么我们要怎么做才能实现解注册的功能?答案是用RemoteCallbackList,这看起来很抽象,不过没关系,请看接下来的分析;

RemoteCallbackList是系统专门用来删除listener的接口,RemoteCallbackList是一个泛型,支持管理任意的AIDL接口,这点从他的声明就可以看出:

public class RemoteCallbackList<E extends IInterface>

因为它的工作原理很简单,在它的内部有一个Map结构专门用来保存所有的AIDL回调,这个Map的key是IBinder类型,value是Callback类型,如下所示。

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

其中Callback中封装了真正的远程listener。当客户端注册listener的时候,它会把这个listener的信息存入mCallbacks中,其中key和value分别通过下面的方式获得

IBinder key = listener.asBinder();
Callback value = new Callback(listener,cookie);

到这里,我相信读者应该都明白了,虽然说多次跨进程传输客户端的同一个对象会在服务端生成不同的对象,但是这些新生成的对象都有一个共同点,那就是他们底层的Binder对象是同一个,利用这些特性,就可以实现我们无法实现的功能了,当客户端解注册的时候,我们只要遍历服务端所有的listener,找到那个和解注册listener具有相同Binder对象的服务端listener并把它删掉,这就是RemoteCallbackList为我们做的事情,同时RemoteCallbackList还有一个很有用的功能,就是当客户端终止后,它能够自动移除客户端的listener,另外,RemoteCallbackList内部自动实现了线程同步的功能,所以我们使用他来注册和解注册,不需要做额外的线程工作,由此可见,RemoteCallbackList是一个很有价值的类,下面我们来演示一下他是如何解注册的

RemoteCallbackList使用起来很很简单,我们要对服务端BookManagerService做一些修改,首先我们创建一个RemoteCallbackList对象来替代之前的CopyonWriteArrayList

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

然后修改registerListener 和unregisterListener这两个接口的实现:

@Override
public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException {
    mListenerList.registener(listener);
}

@Override
public void unregisterListener(IOnNewBookArrivedListener listener) throws RemoteException {
    mListenerList.unregistener(listener);
}

怎么样,使用起来是不是很简单,接下来我们修改onNewBookArrived方法,当有新书的时候,我们就要通知所有已注册的listener:

private void onNewBookArrived(Book book) throws RemoteException{
        mBookList.add(book);
        final int N = mListenerList.beginBroadcast();
        for (int i = 0; i < N; i++) {
            IOnNewBookArrivedListener l = mListenerList.getBroadcast(i);
            if(i != null){
                l.onNewBookArrived(book);
            }
        }
    //注意,要和beginBroadCast()搭配使用
     mListenerList.finishBroadcast();
    }

3)AIDL使用的几点说明,重要!

到这里,AIDL的基本使用方法已经介绍完了,但是有几点还需要再次说明一下。 我们知道,客户端调用远程服务的方法,被调用的方法运行在服务端的Binder线程池中,同时客户端线程会被挂起,这个时候如果服务端方法执行比较耗时,就会导致客户端线程长时间地阻塞在这里,而如果这个客户端线程是UI线程的话,就会导致客户端ANR,这当然不是我们想要看到的。因此,

1、如果我们明确知道某个远程方法是耗时的,那么就要避免在客户端的UI线程中去访问远程方法。由于客户端的onServiceConnected和 onServiceDisconnected方法都运行在UI线程中,所以也不可以在它们里面直接调用服务端的耗时方法,这点要尤其注意。

2、由于服务端的方法本身就运行在服务端的Binder线程池中,所以服务端方法本身就可以执行大量耗时操作,这个时候切记不要在服务端方法中开线程执行异步任务,除非你明确知道自己在干什么,否则不建议这么做。

下面我们稍微改造一下服务端getBookList,我们让他耗时:

@Override
public List<Book> getBookList() throws RemoteException {
    SystemClock.sleep(5000);
    return mBookList;
}

多来几下就ANR了!避免出现ANR很简单,把调用放在非UI线程即可。

3、同理,当远程服务端需要调用 客户端的listener中的方法试,被调用的方法也运行在Binder线程池中,只不过是客户端的线程池。所以,我们同样不可以在服务端中调用客户端的耗时方法。比如BookManagerService中的onNewBookArrived(Book book) ,内部调用了IOnNewBookArrivedListener中的OnNewBookArrived方法,如果客户端的这个方法是耗时的,那么要保证服务端的onNewBookArrived(Book book)运行在非UI线程中。否则导致服务端无响应。

private void onNewBookArrived(Book book) throws RemoteException{
        mBookList.add(book);
        Log.i(TAG,"onNewBookArrived size:" + mListenerList.size());
        for (int i = 0; i < mListenerList.size(); i++) {
            IOnNewBookArrivedListener listener = mListenerList.get(i);
            Log.i(TAG,"listener: "+ listener);
            listener.onNewBookArrived(book);
        }
    }

4、由于客户端的IOnNewBookArrivedListener中的OnNewBookArrived方法 运行在客户端的Binder线程池中,所以不能在它里面访问UI相关的内容,如果要访问UI,请使用Handler切到UI线程。

5、Binder可能意外死亡(可能服务端意外停止),需要重新连接,两种方法:

客户端绑定远程服务成功之后,给binder设置死亡代理,前面介绍Binder时已说明。(Binder线程池中)

onServiceDisconnected中重连服务。(UI线程中)

6、给服务加权限验证功能,验证失败则无法调用服务

a、在onBinder中验证,验证不通过则返回null,客户端就无法绑定服务。比如可以使用permission验证:

服务端在AndroidMenifest中声明所需的权限: <pemission android:name="permission_aidl"/

然后在onBind中做权限验证:

@Override
    public IBinder onBind(Intent intent) {

        int check = checkCallingOrSelfPermission("permission_aidl");
        if (check == PackageManager.PERMISSION_DENIED)
        {
            return null;
        }
        String action = intent.getAction();
        Log.i(TAG, "onBind: action " + action);
        return new BookManagerBinder();
    }

客户端需要:(或者代码中设置)

<uses-permission android:name="permission_aidl"/>

如果客户端没有使用这个权限,就无法绑定服务。

b、在onTransact中验证权限,如果验失败就返回false。这样服务端就不会执行AIDL中的方法了。

具体验证也可以使用permission,或者验证包名。

@Override
        public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
            String[] packagesForUid = getPackageManager().getPackagesForUid(getCallingUid());
            String packageName = null;
            if (packagesForUid != null && packagesForUid.length > 0) {
                packageName = packagesForUid[0];
            }
            if (!"com.hfy".equals(packageName))
            {
                return false;
            }
            return super.onTransact(code, data, reply, flags);
        }