开发艺术探索--IPC机制

第二章: IPC机制

本章包括:
1. Android多进程概念,
2. 多进程开发注意事项,
3. 序列化机制,
4. binder(AIDL),
4. 进程间通信方式,
5. binder连接池,
6. 各种进程间通信方式的优缺点及使用场景.

IPC简介
多进程模式
IPC 基础概念
IPC 方式
Binder连接池
选择合适的IPC方式

一 . IPC 简介

线程:CPU调度的最小单元,是一种有限的系统资源,
进程:一个执行单元,一个进程中可以包含多个线程.
IPC(Inter-Process Communication): 进程间通信或者跨进程通信
一个进程中可以只有一个线程,在Android中叫UI线程,如果耗时任务放在主线程执行,则容易引起ANR

  • 除了 BinderAndroid还支持 Socket,Socket可以用在两个终端间通信,也可以用在一个设备上的两个进程间通信。
  • Android对单个应用所使用的最大内存做了限制,早期是 16M,不同设备有不同大小。

二. 多进程模式

开启多进程
  1. 添加android:process即可在应用内实现多进程,
  2. ":"开头,则是私有进程,其他应用的组件不可以和它跑在同一个进程中
  3. 不以":"开头,则属于全局进程,其它应用通过ShareUID方式可以和它跑在同一个进程中
  4. 没指定 process 属性的进程,运行在默认进程中,进程名为包名。
多进程运行机制
  1. 每个进程都会分配独立的虚拟机(有着不同的地址),一个类的多个副本互不干扰.
  2. 不同进程的组件的确会有独立的虚拟机,Application及内存空间,
  3. 同一个应用间的多进程相当于两个不同的应用采用了SharedUID的模式.
  4. 运行在不同进程中的四大组件,通过内存共享数据都会失败。
  5. 多进程造成的几个方面的影响:
  • 静态成员和单例模式完全失效;
  • 线程同步机制完全失效;
  • SharedPreferences的可靠性下降;
  • Application会多次创建

三. IPC基础概念

Serializable: 序列化
Parcelable: 序列化
Binder: 传递序列化后的数据

  • Serializable
    1. 只有serialVersionUID相同才能够被正常的反序列化
    2. 不指定则会根据当前类自动生成hash值,如果反序列化时类结构改变则序列化失败而crash
    3. 静态成员,transient变量 -> 不会参与序列化
    4. 最好手动指定serialVersionUID,一般都会手动指定 serialVersionUID = 1L,保证发序列化成功.
    5. 开销很大,需要大量I/O操作
  • Parcelable
    1. Parcelable在Android上效率更高,建议Android上用此种方式
    2. Parcelable是Android推荐的序列化方式,主要用在内存序列化上
Binder
  1. Binder实现了IBinder接口,是Android中的一种跨进程通信方式,也可以理解为一种物理设备(对应/dev/binder)
  2. BinderServiceManager链接各种Manager和ManagerService的桥梁.
  3. SDK会自动为我们生产AIDL对应的Binder类.所有Binder中传输的接口都需要实现IInterface(包括Stub).
  4. transact是垮进程的,当客户端和服务端在同一个进程时,则不会走此方法,这个逻辑由Stub的内部类Proxy完成.
  5. 可以通过isBinderAlive来判断Binder是否死亡
  6. bindService的时候,服务端会返回一个包含了服务端业务调用的 Binder 对象
  7. 当客户端发起远程请求时,线程会被挂起,因此如果远程方法是耗时的,则不能再UI线程发起远程请求。
binder相关方法
  • asInterface
  1. 用于将服务端的Binder对象转换成客户端所需的AIDL接口类型的对象,这种转换过程是区分进程的【如果客户端和服务端位于同一进程,那么此方法返回的就是服务端的Stub对象本
  2. 如果客户端和服务端位于同一进程,asInterface则返回的就是Stub本身,否则为Stub.proxy.
//Activity中
 private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            IBookManager bookManager = IBookManager.Stub.asInterface(service);
        }
        //...
    };
//...
 protected void onCreate(@Nullable Bundle savedInstanceState) {
        bindService(intent,connection,Context.BIND_AUTO_CREATE);
 }
  • asBinder
//Stub
        @Override
        public android.os.IBinder asBinder() {
            return this;
        }
//Proxy
        private android.os.IBinder mRemote;

        @Override
        public android.os.IBinder asBinder() {
            return mRemote;
        }

Binder 代码中可以看出 asBinder 的返回值,是通过Stub.asInterface(service);传过去的,就是这个 service.

  • onTransact(服务端接收)
    参数解析:
    2. code:确定客户端请求的方法
    3. data:目标方法有参数的话,就从data取出
    4. reply:目标方法有返回值,就向reply中写入返回值
  • DESCRIPTOR
private static final java.lang.String DESCRIPTOR = "com.IBookManager";
//系统生成的,Binder的唯一标识,一般用当前Binder的类名表示。
  • transact(客户端调用)
binder死亡的两种处理办法:
  1. 设置死亡代理,
  2. 在onServiceDisconnect中重连服务.
设置死亡代理的步骤:
  1. 声明一个DeathRecipient(接口)对象,实现binderDied方法
  2. 在客户端绑定远程服务成功后,给binder设置死亡代理
// Activity
// binderDied在客户端binder线程池回调.
private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {  
        @Override  
        public void binderDied() {  
            if (mIAidlCall == null)  
                return;  
            mIAidlCall.asBinder().unlinkToDeath(mDeathRecipient, 0);  
            mIAidlCall = null;  
            // 重新绑定远程服务  
        }  
    }; 

 //onServiceDisconnect在客户端UI线程回调: 
 private ServiceConnection conn = new ServiceConnection() {  
        @Override  
        public void onServiceDisconnected(ComponentName name) {  
        }  

        @Override  
        public void onServiceConnected(ComponentName name, IBinder service) {  
            mIAidlCall = IAidlCall.Stub.asInterface(service);  
            try {  
                service.linkToDeath(mDeathRecipient, 0);  
                Toast.makeText(getApplicationContext(), mIAidlCall.getName(),  
                        Toast.LENGTH_LONG).show();  
            } catch (RemoteException e) {  
                e.printStackTrace();  
            }  
        }  
    };  
in,out,inout代表什么

in 表示数据只能由客户端流向服务端,
out 表示数据只能由服务端流向客户端,
而 inout 则表示数据可在服务端与客户端之间双向流通

四. IPC通信方式

名称优点缺点适用场景
Bundle简单只支持Bundle支持的数据类型四大组件
文件共享简单不适合高并发且无法做到进程间即时通信无并发访问,实时性不高的场景
AIDL强大,,一对多并发,实时通信适用稍复杂一对多通信且有RPC需求
Messenger功能一般,支持一对多串行,实时通信不能很好处理高并发,不支持RPC,只支持Bundle支持的数据,必须将数据放进Message中低并发,一对多,即时通信,无RPC需求
Contentprovider一对多,可通过Call方法扩展可理解为受约束的aidl,主要提供CRUD操作一对多进程间数据共享
Socket功能强大,通过网络传输字节流实现细节稍微麻烦,支持RPC网络数据交换
四种通信注意事项
  1. Bundle中必须传递可以被序列化,Bundle本身实现了Parcelable接口.
  2. SharedPreferences属于文件共享来实现IPC,因为其缓存策略,将变得不可靠.
  3. Messenger,其内部依然使用的是Binder.必须将数据放进Message中
  4. Socket,分为TCP(流式套接字)和UDP(用户数据报套接字),TCP面向连接,UDP面向无连接.
  5. ContentProvider 除了onCreate运行在ui线程,其他方法运行在 binder线程池中
  6. Socket分为TCP,UDP.
AIDL支持的数据类型:
  • 基本数据类型;
  • Sring 和 CharSequence;
  • ArrayList,里面的对象必须也是AIDL支持的
  • HashMap,里面的对象必须也是AIDL支持的
  • Parcelable,(必须声明同名的AIDL,并parcelable进来)
  • AIDL接口本身(需要import进来)
  1. 除了基本类型外,其他类型的参数必须标上方向,in(输入)/out(输出)
  2. AIDL的包结构,在服务端和客户端必须一致(反序列化)
  3. 自定义的Parcelable对象和AIDL对象必须显示的 import进来
  4. 如果AIDL文件中用到了自定义的Parcelable对象,必须建立同名的AIDL文件.
AIDL的注意事项:
  1. AIDL接口回调需要用RemoteCallBackList,才能被正确的 反注册.(beginBroadcast和finishBroadcast配对使用)
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.getBroadcastItem(i);
            if (l != null) {
                try {
                    l.onNewBookArrived(book);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        }
        mListenerList.finishBroadcast();
    }
  1. 客户端的onServiceConnectedonServiceDisconnect运行在UI线程,服务端方法运行在Binder线程池
  2. AIDL权限的验证功能,可以在onBind中验证(验证失败返回null)或者在服务端的onTransact中验证(验证失败返回false).
AIDL创建过程
  • 首先创建一个Service和一个AIDL接口,
  • 接着创建一个类继承自AIDL接口中的Stub类并实现Stub中的抽象方法,
  • 在Service的onBind方法中返回这个类的对象,
  • 然后客户端就可以绑定服务端的Service了。(编译器通过aidl文件生成一段代码,通过预先定义的接口达到两个进程内部通信进程的目的)
  • AIDL定义一个接口,客户端(调用端)通过bindService来与远程服务端建立连接,该连接建立时会返回一个IBinder对象,该对象是服务端Binder的BinderProxy,客户端通过asInterface函数将该BinderProxy对象包装成本地的Proxy,并将远程服务端的BinderProxy对象赋值给Proxy类的mRemote字段,通过mRemote执行远程方法调用

五. Binder连接池

  • Binder连接池是为了避免创建过多的Service,将所有的AIDL都放到一个Service中管理,提供queryBinder接口来判断不同的Binder.
  • 避免创建过多的Service和AIDL文件.
  • CountDownLatch类介绍:
    CountDownLatch的介绍和使用

使用IPC的一个小Demo:IPCDemo

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值