美文网首页
IPC(艺术探索读书笔记)

IPC(艺术探索读书笔记)

作者: 最美下雨天 | 来源:发表于2019-07-09 17:01 被阅读0次

概述:进程一般指一个执行单元,在移动设备中指一个应用程序,当然一个应用程序可以包含多个进程。
线程是CPU调度的基本单元,一个进程可以包含多个线程,在最简单的情况下,一个进程可以只包含一个主线程

有时候为了加大一个应用的可使用内存,我们可以在应用中运行多个进程从而获取多分内存空间

创建多进程

  • 第一种方式:在AndroidManifest中给四大组件指定android:process属性
  • 第二种方式:通过JNI在native层fork一个新进程

注意事项

在AndroidManifest中通过android:process指定多进程时有两种写法:假设应用的包名是com.miduo.weilaihui

android:process=":remote"
android:process="com.miduo.weilaihui.remote"

第一种方式的完整的进程名字是com.miduo.weilaihui:remote
第二种方式的完整的进程名字是com.miduo.weilaihui.remote

第一种方式属于应用的私有进程,其他应用不可以和它跑在同一个进程中,第二种方式属于全局进程,其他应用可以通过shareUID的方式和它跑在同一个进程中

多进程存在的问题

由于每个进程有自己独立的内存单元,因此只要是通过内存来共享数据都会失败

  • 比如说应用程序中有个静态变量,Activity1改变了它的值,Activity2去访问会发现还是原来的值(如果Activity2运行于其他线程中)
  • 多进程中静态成员、单例模式都会失效
  • 多进程中线程同步机制失效
  • 多进程中Application会多次创建
  • SharedPreferences可靠性下降

Serializable、Parcelable

我们这样理解:如果把一个对象保存到设备上,仍然能够从设备上恢复,那么我们就认为这个对象是可序列化的

Serializable接口比较简单,只需要让对象implement Serializable即可,实现Serializable接口时,一般我们最好手动指定一下serialVersionUID的值。这个值在序列化和反序列化的时候是有用的,原则上序列化后的数据中的serialVersionUID只有和当前类的serialVersionUID相同才能被正常的反序列化

如果没有手动指定serialVersionUID,那么系统在序列化时会根据当前类结构生成一个默认值,那么如果我们在反序列化的时候如果我们当前类添加了新成员,那么就会反序列化失败。

  • 静态成员属于类对象,不参与序列化过程
  • 用transient关键字标记的成员变量不参与序列化过程

什么意思呢?比如说Person类有个静态变量count

Person p1=new Person();
Person p2=new Person();
p1.count=10;
saveToFile(p1);
p2.count=20;
saveToFile(p2);

如果我们对p1进行反序列化,然后打印p1中count的值,会发现是20而不是10

Binder

到底什么是Binder呢?直观来说Binder是Android中的一个类,实现了IBinder接口,从IPC角度来说,Binder是Android中的一种跨进程通信的方式

那么跨进程通信方式与我们Android中的这个Binder类有什么关系呢?

利用AIDL来分析Binder的工作机制

相关类及接口:Binder类、IBinder接口、IInterface接口,这些类和接口的作用都是什么呢?

模拟场景:进程A有两个整数,进程B负责将两个整数相加,然后再将最后的结果返回给进程A

大致流程:进程A通过bindService方法去绑定在进程B中注册的一个Service,然后进程B调用onBind方法返回给进程A一个代理对象,然后进程A在onServiceConnected获取到代理对象后,依靠代理对象来完成两个整数的相加

我们在aidl自动生成的java文件中可以看到:

  • Stub类是一个Binder对象,这个对象是服务端要返回给客户端的
  • Proxy类是Stub的内部类,这个类是干嘛用的呢?如果说进程A和进程B处于同一个进程中,那么进程B返给进程A的就是这个Stub,如果说进程A和进程B处于不同的进程中,那么进程B返给进程A的就是这个Proxy对象
  • 在Stub的构造方法中,首先调用attachInterface(this,DESCRIPTOR),后面有一步queryLocalInterface(DESCRIPTOR)是根据DESCRIPTOR来查找IInterface的,如果这儿调用了attachInterface方法,那么后面就可以查到
  • 服务端将创建好的stub返回给客户端,客户端在onServiceConnected方法中根据IMyAidlInterface.Stub.asInterface(binder)来获取这个Binder对象,但是有一点需要知道如果进程A和进程B处于不同进程中时,获取到的这个Binder对象是Binder的内部类BinderProxy
  • 客户端在调用IMyAidlInterface.Stub.asInterface(binder)这个方法时,内部会调用binder的queryLocalInterface(DESCRIPTOR)方法查找IInterface,而Binder的内部类BinderProxy的这个方法返回的是null
public static com.wuyr.litepagertest.IMyAidlInterface asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.wuyr.litepagertest.IMyAidlInterface))) {
                return ((com.wuyr.litepagertest.IMyAidlInterface) iin);
            }
            return new com.wuyr.litepagertest.IMyAidlInterface.Stub.Proxy(obj);
        }

从上面的代码就可以验证上面的第二条结论:

如果说进程A和进程B处于同一个进程中,那么进程B返给进程A的就是这个Stub,如果说进程A和进程B处于不同的进程中,那么进程B返给进程A的就是这个Proxy对象

  • 接着客户端就可以调用Proxy的getSum方法来获取值了,首先将a、b封装到_data中,然后调用transact方法,服务端的onTransact方法会被回调,然后将计算好的值写入到_reply中,客户端就可以获取到值了
@Override
            public int getSum(int a, int b) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                int _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    _data.writeInt(a);
                    _data.writeInt(b);
                    mRemote.transact(Stub.TRANSACTION_getSum, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.readInt();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }
  • 服务端的Binder对象的onTransact方法会被回调,从data中读取到两个整数,然后将值写入reply中
 @Override
        public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
            java.lang.String descriptor = DESCRIPTOR;
            switch (code) {
             case TRANSACTION_getSum: {
                    data.enforceInterface(descriptor);
                    int _arg0;
                    _arg0 = data.readInt();
                    int _arg1;
                    _arg1 = data.readInt();
                    int _result = this.getSum(_arg0, _arg1);
                    reply.writeNoException();
                    reply.writeInt(_result);
                    return true;
                }
               
            }
        }

两点注意事项

  • 1、当客户端发起远程请求时,当前线程会被挂起直至服务端进程返回数据,所以如果服务端的这个方法比较耗时,那么不能在ui线程中发起这个远程请求
  • 2、服务端的远程方法是运行于Binder线程池中的,所以不管是否耗时都没有必要再另开线程

Android中的IPC方式

  • 1、Bundle
    我们在一个进程中去启动另一个进程的Activity、Service、BroadCastReceiver时,可以利用Intent将Bundle数据传递过去
  • 2、使用文件共享
    A进程将数据写入一个文件,B进程从这个文件中将数据读取出来
    但是如果对于多线程并发读写,文件共享会存在一定的隐患
  • 3、使用Messenger
    轻量级的IPC方案,底层实现是AIDL
    Messenger可以方便的实现进程间互通消息(就像聊天一样),Messenger传递的对象是Message

Messenger依靠的是Handler机制,所以是串行执行的,当有多个Message到来时必须排队处理

Messenger的主要作用是为了传递消息,并不能调用服务端的方法

Messenger的使用步骤跟AIDL大致相同:
1、服务端需要创建一个Service来处理客户端的连接请求,同时创建一个Messenger对象,创建Messenger需要用到Handler,在onBind方法中返回messenger.getBinder()
2、客户端在onServiceConnected方法中创建Messenger,参数是使用binder对象,然后使用messenger.send(Message)来给服务器发送Message对象
3、如果需要服务端也给客户端发送信息,那么我们在客户端也需要创建一个带handler的Messenger对象(remessenger),然后在第二步给服务器发送Message的时候message.replyTo=remessenger,将这个新的Messenger对象封装到Message中一并发送给服务器
4、服务器在handler中收到Message后,可以从Message中解析出客户端发来的Messenger,然后利用这个Messenger给客户端发送消息

AIDL

支持的数据类型:

基本数据类型、String、ArrayList、HashMap、Parcelable

观察者模式的运用:

1、创建一个aidl接口,在aidl中无法使用普通接口
2、服务端提供注册与反注册的方法
3、客户端在onServiceConnected方法中拿到binder对象后,可以调用注册观察者方法
4、服务端在状态改变时从观察者列表中遍历调用观察者方法
5、客户端onDestroy时反注册观察者
两点注意事项:在上面的最后一步中,反注册观察者并不能成功,对象在通过Binder传递给服务端后,会产生一个全新的对象,对象跨进程传输的本质是反序列化的过程,与客户端是两个不同的对象;
第二点是在服务端遍历调用观察者的方法时,这个方法是运行在客户端的binder线程池中,并不是在ui线程

那么如何进行反注册呢?在服务端不能使用普通的List集合来存放观察者,应该使用RemoteCallbackList

监听Binder意外死亡

Binder意外死亡往往是由于服务端进程意外停止了,这是我们需要重新连接服务

第一种方式是客户端的onServiceDisconnected方法会回调,这个方法是运行在客户端的ui线程中,我们可以在这里重新绑定服务
第二种方式是在客户端的onServiceConnected方法中拿到binder后,给binder设置监听器DeathRecipient

binder.linkToDeath(deathRecipient,0);

这样的话,当binder死亡时,binderDied方法就会被回调,我们可以在这里重新绷定服务,这个方法运行在客户端的binder线程池中

权限验证

如果我们的远程服务不想任何人都可以随意连接的话,我们需要加入权限验证

第一种方法,我们可以在服务端的AndroidManifest中自定义权限,然后在客户端的AndroidManifest中声明该权限,然后在服务端onBind方法中调用checkCallingPermission方法判断客户端是否有该权限
Determine whether the calling process of an IPC you are handling has been granted a particular permission.

第二种方法,在服务端重写onTransact方法,在onTransact方法中做权限校验

Socket

这个平时接触应该挺多的,比如说聊天室,主要用到类ServerSocket、Socket

ContentProvider

相关文章

网友评论

      本文标题:IPC(艺术探索读书笔记)

      本文链接:https://www.haomeiwen.com/subject/mzznhctx.html