2. IPC机制《Android开发艺术探索》

2018-06-22  本文已影响0人  tesla1984

2.1 Android IPC简介

IPC是Inter-Process Communication的缩写,含义为进程间通信或者跨进程通信,是指两个进程之间进行数据交换的过程
进程一般指一个执行单元,在PC和移动设备上指一个程序或者一个应用。一个进程可以包含多个线程。

2.2 Android中的多进程模式

通过给四大组件指定android:process属性,我们可以轻易地开启多进程模式

  1. 开启多进程模式

    • SecondActivity和ThirdActivity的android:process属性分别为“:remote”和“com.ryg.chapter_2.remote”,那么这两种方式有区别吗?
      • 首先,“:”的含义是指要在当前的进程名前面附加上当前的包名,这是一种简写的方法,对于SecondActivity来说,它完整的进程名为com.ryg.chapter_2:remote,这一点通过图2-1和2-2中的进程信息也能看出来,而对于ThirdActivity中的声明方式,它是一种完整的命名方式,不会附加包名信息
      • 其次,进程名以“:”开头的进程属于当前应用的私有进程,其他应用的组件不可以和它跑在同一个进程中,而进程名不以“:”开头的进程属于全局进程,其他应用通过ShareUID方式可以和它跑在同一个进程中。
    • Android系统会为每个应用分配一个唯一的UID,具有相同UID的应用才能共享数据。这里要说明的是,两个应用通过ShareUID跑在同一个进程中是有要求的,需要这两个应用有相同的ShareUID并且签名相同才可以
  2. 多进程模式的运行机制

    • Android为每一个应用分配了一个独立的虚拟机,或者说为每个进程都分配一个独立的虚拟机,不同的虚拟机在内存分配上有不同的地址空间,这就导致在不同的虚拟机中访问同一个类的对象会产生多份副本
    • 所有运行在不同进程中的四大组件,只要它们之间需要通过内存来共享数据,都会共享失败
    • 使用多进程会造成如下几方面的问题:
      • 静态成员和单例模式完全失效
      • 线程同步机制完全失效
      • SharedPreferences的可靠性下降
      • Application会多次创建

2.3. IPC基础概念介绍

主要包含三方面内容:Serializable接口、Parcelable接口以及Binder,只有熟悉这三方面的内容后,我们才能更好地理解跨进程通信的各种方式。Serializable和Parcelable接口可以完成对象的序列化过程,当我们需要通过Intent和Binder传输数据时就需要使用Parcelable或者Serializable。还有的时候我们需要把对象持久化到存储设备上或者通过网络传输给其他客户端,这个时候也需要使用Serializable来完成对象的持久化

2.3.1. Serializable接口

2.3.2. Parcelable接口

Serializable是Java中的序列化接口,其使用起来简单但是开销很大,序列化和反序列化过程需要大量I/O操作。而Parcelable是Android中的序列化方式,因此更适合用在Android平台上,它的缺点就是使用起来稍微麻烦点,但是它的效率很高,这是Android推荐的序列化方式,因此我们要首选Parcelable

2.3.3. Binder

Binder是Android中的一个类,它继承了IBinder接口。从IPC角度来说,Binder是Android中的一种跨进程通信方式,Binder还可以理解为一种虚拟的物理设备,它的设备驱动是/dev/binder,该通信方式在Linux中没有;从Android Framework角度来说,Binder是ServiceManager连接各种Manager(ActivityManager、WindowManager,等等)和相应ManagerService的桥梁;从Android应用层来说,Binder是客户端和服务端进行通信的媒介,当bindService的时候,服务端会返回一个包含了服务端业务调用的Binder对象,通过这个Binder对象,客户端就可以获取服务端提供的服务或者数据,这里的服务包括普通服务和基于AIDL的服务

首先,它声明了两个方法getBookList和addBook,显然这就是我们在IBookManager.aidl中所声明的方法,同时它还声明了两个整型的id分别用于标识这两个方法,这两个id用于标识在transact过程中客户端所请求的到底是哪个方法。接着,它声明了一个内部类Stub,这个Stub就是一个Binder类,当客户端和服务端都位于同一个进程时,方法调用不会走跨进程的transact过程,而当两者位于不同进程时,方法调用需要走transact过程,这个逻辑由Stub的内部代理类Proxy来完成。这么来看,IBookManager这个接口的确很简单,但是我们也应该认识到,这个接口的核心实现就是它的内部类Stub和Stub的内部代理类Proxy

  1. DESCRIPTOR
    Binder的唯一标识,一般用当前Binder的类名表示
  2. asInterface(android.os.IBinder obj)
    用于将服务端的Binder对象转换成客户端所需的AIDL接口类型的对象,这种转换过程是区分进程的,如果客户端和服务端位于同一进程,那么此方法返回的就是服务端的Stub对象本身,否则返回的是系统封装后的Stub.proxy对象
  3. asBinder
    此方法用于返回当前Binder对象
  4. onTransact
    这个方法运行在服务端中的Binder线程池中,当客户端发起跨进程请求时,远程请求会通过系统底层封装后交由此方法来处理。该方法的原型为public Boolean onTransact(int code,android.os.Parcel data,android.os.Parcel reply,int flags)。服务端通过code可以确定客户端所请求的目标方法是什么,接着从data中取出目标方法所需的参数(如果目标方法有参数的话),然后执行目标方法。当目标方法执行完毕后,就向reply中写入返回值(如果目标方法有返回值的话),onTransact方法的执行过程就是这样的
  5. Proxy#getBookList
    这个方法运行在客户端,当客户端远程调用此方法时,它的内部实现是这样的:首先创建该方法所需要的输入型Parcel对象_data、输出型Parcel对象_reply和返回值对象List;然后把该方法的参数信息写入_data中(如果有参数的话);接着调用transact方法来发起RPC(远程过程调用)请求,同时当前线程挂起;然后服务端的onTransact方法会被调用,直到RPC过程返回后,当前线程继续执行,并从_reply中取出RPC过程的返回结果;最后返回_reply中的数据

2.4 Android中的IPC方式

具体方式有很多,比如可以通过在Intent中附加extras来传递信息,或者通过共享文件的方式来共享数据,还可以采用Binder方式来跨进程通信,另外,ContentProvider天生就是支持跨进程访问的,因此我们也可以采用它来进行IPC。此外,通过网络通信也是可以实现数据传递的,所以Socket也可以实现IPC。上述所说的各种方法都能实现IPC

2.4.1 使用Bundle

由于Bundle实现了Parcelable接口,所以它可以方便地在不同的进程间传输。基于这一点,当我们在一个进程中启动了另一个进程的Activity、Service和Receiver,我们就可以在Bundle中附加我们需要传输给远程进程的信息并通过Intent发送出去。
除了直接传递数据这种典型的使用场景,它还有一种特殊的使用场景。比如A进程正在进行一个计算,计算完成后它要启动B进程的一个组件并把计算结果传递给B进程,可是遗憾的是这个计算结果不支持放入Bundle中,因此无法通过Intent来传输,这个时候如果我们用其他IPC方式就会略显复杂。可以考虑如下方式:我们通过Intent启动进程B的一个Service组件(比如IntentService),让Service在后台进行计算,计算完毕后再启动B进程中真正要启动的目标组件,由于Service也运行在B进程中,所以目标组件就可以直接获取计算结果,这样一来就轻松解决了跨进程的问题。这种方式的核心思想在于将原本需要在A进程的计算任务转移到B进程的后台Service中去执行,这样就成功地避免了进程间通信问题,而且只用了很小的代价。

2.4.2 使用文件共享

文件共享方式适合在对数据同步要求不高的进程之间进行通信,并且要妥善处理并发读/写的问题。

2.4.3 使用Messenger

Messenger可以翻译为信使,顾名思义,通过它可以在不同进程中传递Message对象,在Message中放入我们需要传递的数据,就可以轻松地实现数据的进程间传递了。Messenger是一种轻量级的IPC方案,它的底层实现是AIDL
Messenger的使用方法很简单,它对AIDL做了封装,使得我们可以更简便地进行进程间通信。同时,由于它一次处理一个请求,因此在服务端我们不用考虑线程同步的问题,这是因为服务端中不存在并发执行的情形

  1. 服务端进程
    我们需要在服务端创建一个Service来处理客户端的连接请求,同时创建一个Handler并通过它来创建一个Messenger对象,然后在Service的onBind中返回这个Messenger对象底层的Binder即可。
  2. 客户端进程
    客户端进程中,首先要绑定服务端的Service,绑定成功后用服务端返回的IBinder对象创建一个Messenger,通过这个Messenger就可以向服务端发送消息了,发消息类型为Message对象。如果需要服务端能够回应客户端,就和服务端一样,我们还需要创建一个Handler并创建一个新的Messenger,并把这个Messenger对象通过Message的replyTo参数传递给服务端,服务端通过这个replyTo参数就可以回应客户端。
    服务端代码
 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 MyConstants.MSG_FROM_CLIENT:
                     Log.i(TAG,"receive msg from Client:" + msg.getData().
                     getString("msg"));
                     break;
                 default:
                     super.handleMessage(msg);
                 }
             }
         }
         private final Messenger mMessenger = new Messenger(new Messenger-
         Handler());
         @Override
         public IBinder onBind(Intent intent) {
             return mMessenger.getBinder();
         }
    }

客户端代码

public class MessengerActivity extends Activity {
         private static final String TAG = " MessengerActivity";
         private Messenger mService;
         private ServiceConnection mConnection = new ServiceConnection() {
             public void onServiceConnected(ComponentName className,IBinder
             service) {
                 mService = new Messenger(service);
                 Message msg = Message.obtain(null,MyConstants.MSG_FROM_CLIENT);
                 Bundle data = new Bundle();
                 data.putString("msg","hello,this is client.");
                 msg.setData(data);
                 try {
                     mService.send(msg);
                 } catch (RemoteException e) {
                     e.printStackTrace();
                 }
             }
             public void onServiceDisconnected(ComponentName className) {
             }
         };
         @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() {
             unbindService(mConnection);
             super.onDestroy();
         }
    }

上面的例子演示了如何在服务端接收客户端中发送的消息,但是有时候我们还需要能回应客户端,下面就介绍如何实现这种效果。
服务端修改

private static class MessengerHandler extends Handler {
         @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
             case MyConstants.MSG_FROM_CLIENT:
                 Log.i(TAG,"receive msg from Client:" + msg.getData().getString
                 ("msg"));
                 Messenger client = msg.replyTo;
                 Message relpyMessage = Message.obtain(null,MyConstants.MSG_
                 FROM_SERVICE);
                 Bundle bundle = new Bundle();
                 bundle.putString("reply","嗯,你的消息我已经收到,稍后会回复你。");
                 relpyMessage.setData(bundle);
                 try {
                     client.send(relpyMessage);
                 } catch (RemoteException e) {
                     e.printStackTrace();
                 }
                 break;
             default:
                 super.handleMessage(msg);
             }
         }
    }

客户端修改

private Messenger mGetReplyMessenger = new Messenger(new Messenger-
    Handler());
    private static class MessengerHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
            case MyConstants.MSG_FROM_SERVICE:
                    Log.i(TAG,"receive msg from Service:" + msg.getData().
                    getString("reply"));
                    break;
            default:
                    super.handleMessage(msg);
            }
        }
    }

 "还有很关键的一点,当客户端发送消息的时候,需要把接收服务端回复的Messenger通过Message的replyTo参数传递给服务端"
    mService = new Messenger(service);
    Message msg = Message.obtain(null,MyConstants.MSG_FROM_CLIENT);
    Bundle data = new Bundle();
    data.putString("msg","hello,this is client.");
    msg.setData(data);
    //注意下面这句
    msg.replyTo = mGetReplyMessenger;
    try {
        mService.send(msg);
    } catch (RemoteException e) {
        e.printStackTrace();
    }
Messenger的工作原理

2.4.4 使用AIDL

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

  1. 服务端
    服务端首先要创建一个Service用来监听客户端的连接请求,然后创建一个AIDL文件,将暴露给客户端的接口在这个AIDL文件中声明,最后在Service中实现这个AIDL接口即可。

  2. 客户端
    客户端所要做事情就稍微简单一些,首先需要绑定服务端的Service,绑定成功后,将服务端返回的Binder对象转成AIDL接口所属的类型,接着就可以调用AIDL中的方法了。

  3. AIDL接口的创建

  1. 远程服务端Service的实现
public class BookManagerService extends Service {
         private static final String TAG = "BMS";
         private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArray-
         List<Book>();
         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 onCreate() {
             super.onCreate();
             mBookList.add(new Book(1,"Android"));
             mBookList.add(new Book(2,"Ios"));
         }
         @Override
         public IBinder onBind(Intent intent) {
             return mBinder;
         }
    }

  1. 客户端的实现
 public class BookManagerActivity extends Activity {
         private static final String TAG = "BookManagerActivity";
         private ServiceConnection mConnection = new ServiceConnection() {
             public void onServiceConnected(ComponentName className,IBinder
             service) {
                 IBookManager bookManager = IBookManager.Stub.asInterface
                 (service);
                 try {
                     List<Book> list = bookManager.getBookList();
                     Log.i(TAG,"query book list,list type:" + list.getClass().
                     getCanonicalName());
                     Log.i(TAG,"query book list:" + list.toString());
                 } catch (RemoteException e) {
                     e.printStackTrace();
                 }
             }
             public void onServiceDisconnected(ComponentName className) {
             }
         };
         @Override
         protected void onCreate(Bundle savedInstanceState) {
             super.onCreate(savedInstanceState);
             setContentView(R.layout.activity_book_manager);
             Intent intent = new Intent(this,BookManagerService.class);
             bindService(intent,mConnection,Context.BIND_AUTO_CREATE);
         }
         @Override
         protected void onDestroy() {
             unbindService(mConnection);
             super.onDestroy();
         }
    }

这种解注册的处理方式在日常开发过程中时常使用到,但是放到多进程中却无法奏效,因为Binder会把客户端传递过来的对象重新转化并生成一个新的对象。虽然我们在注册和解注册过程中使用的是同一个客户端对象,但是通过Binder传递到服务端后,却会产生两个全新的对象。别忘了对象是不能跨进程直接传输的,对象的跨进程传输本质上都是反序列化的过程,这就是为什么AIDL中的自定义对象都必须要实现Parcelable接口的原因。那么到底我们该怎么做才能实现解注册功能呢?答案是使用RemoteCallbackList。
RemoteCallbackList是系统专门提供的用于删除跨进程listener的接口。Remote-CallbackList是一个泛型,支持管理任意的AIDL接口,这点从它的声明就可以看出,因为所有的AIDL接口都继承自IInterface接口,读者还有印象吗?

    public class RemoteCallbackList<E extends IInterface>

虽然说多次跨进程传输客户端的同一个对象会在服务端生成不同的对象,但是这些新生成的对象有一个共同点,那就是它们底层的Binder对象是同一个,利用这个特性,就可以实现上面我们无法实现的功能。当客户端解注册的时候,我们只要遍历服务端所有的listener,找出那个和解注册listener具有相同Binder对象的服务端listener并把它删掉即可,这就是RemoteCallbackList为我们做的事情。同时RemoteCallbackList还有一个很有用的功能,那就是当客户端进程终止后,它能够自动移除客户端所注册的listener。另外,RemoteCallbackList内部自动实现了线程同步的功能,所以我们使用它来注册和解注册时,不需要做额外的线程同步工作。
使用RemoteCallbackList,有一点需要注意,我们无法像操作List一样去操作它,尽管它的名字中也带个List,但是它并不是一个List。遍历RemoteCallbackList,必须要按照下面的方式进行,其中beginBroadcast和beginBroadcast必须要配对使用,哪怕我们仅仅是想要获取RemoteCallbackList中的元素个数,这是必须要注意的地方。

我们知道,客户端调用远程服务的方法,被调用的方法运行在服务端的Binder线程池中,同时客户端线程会被挂起,这个时候如果服务端方法执行比较耗时,就会导致客户端线程长时间地阻塞在这里,而如果这个客户端线程是UI线程的话,就会导致客户端ANR,这当然不是我们想要看到的。因此,如果我们明确知道某个远程方法是耗时的,那么就要避免在客户端的UI线程中去访问远程方法。由于客户端的onServiceConnected和onService Disconnected方法都运行在UI线程中,所以也不可以在它们里面直接调用服务端的耗时方法,这点要尤其注意。另外,由于服务端的方法本身就运行在服务端的Binder线程池中,所以服务端方法本身就可以执行大量耗时操作,这个时候切记不要在服务端方法中开线程去进行异步任务,除非你明确知道自己在干什么,否则不建议这么做。

Binder是可能意外死亡的,这往往是由于服务端进程意外停止了,这时我们需要重新连接服务。有两种方法,第一种方法是给Binder设置DeathRecipient监听,当Binder死亡时,我们会收到binderDied方法的回调,在binderDied方法中我们可以重连远程服务,具体方法在Binder那一节已经介绍过了,这里就不再详细描述了。另一种方法是在onServiceDisconnected中重连远程服务。这两种方法我们可以随便选择一种来使用,它们的区别在于:onServiceDisconnected在客户端的UI线程中被回调,而binderDied在客户端的Binder线程池中被回调。也就是说,在binderDied方法中我们不能访问UI,这就是它们的区别。

如何在AIDL中使用权限验证功能

  <permission
        android:name="com.ryg.chapter_2.permission.ACCESS_BOOK_SERVICE"
        android:protectionLevel="normal" />

2.4.5 使用ContentProvider

2.4.6 使用Socket

2.5 Binder连接池

上一篇下一篇

猜你喜欢

热点阅读