美文网首页工作生活
Android中的IPC机制

Android中的IPC机制

作者: Kris_Ni | 来源:发表于2019-07-03 17:54 被阅读0次

    一、Android IPC简介

    IPC,Inter-Process Comminication的缩写,含义为进程间通信或者跨进程通信,即两个进程之间进行数据交换的过程。首先,什么是进程?一般是指一个执行单元,在PC和移动设备上指一个程序或一个应用。而线程则是CPU调度的最小单元,同时也是有限的系统资源,它是进程的一部分。一个进度可以只有一个线程,即主线程,在Android的世界里又名UI线程,只有在UI线程里才可以去操作界面元素(不是一定的,具体原因可以自己了解一下)。在很多时候,如果在主线程中执行大量耗时的任务,就会造成界面无法响应,在Android里面叫做ANR(Application Not Responding),要想解决这个问题,就需要把这些任务放到别的线程中。

    二、Andorid中开启多进程模式

    如果想要在一个应用里面开启多个进程,只需要给四大组件在AndroidMenifest指定android:process属性即可,也可以通过JNI在native层fork一个新的进程,这种方法会在以后的JNI开发中演示,目前不做太多说明。
    在开启多进程之后,会面临如下问题

    • 静态成员和单例模式完全失效。
    • 线程同步机制失效
    • SharedPreferences的可靠性下降
    • Application会多次创建

    第一个和第二个问题的原因从本质上来说是一样的,每开启一个进程Android都会为其分配一个独立的虚拟机,不同的虚拟机在内存分配上有不同的地址空间,导致了不同的虚拟机访问同一个对象会产生多份副本。
    第三个问题是因为SharedPreferences不支持两个进程同时去执行写操作,否则可能会导致数据丢失。Shareferences底层是通过读写XML文件实现的,并发读写会出现问题。
    第四个问题很显而易见,当一个组件跑在一个新的进程中,由于系统创建新的进程会分配独立的虚拟机,所以这个过程就是重启一个应用的过程,自然会去创建新的Application.

    三、IPC基础概念

    在通过Intent和Binder传输数据时,需要将数据进行序列化操作才可以进行传输,可以通过Serializable和Parcelable来完成对象的持久化。

    3.1 Serializable

    Serializable是Java提供的一个序列化接口,它是一个空接口,为对象提供标准的序列化和反序列化操作。使用Serializable来实现序列化很简单,只需要将这个类实现Serializable接口并声明一个SerialVersionUID即可,甚至不声明这个SerialVersionUID也是可以的,可以实现序列化但是会对反序列化产生影响,只有序列化的数据中的serialVersionUID和当前类的serialVersionUID相同时才能被正常反序列化。另外,系统默认的序列化过程也是可以改变的,通过重写系统默认的writeObject和readObject方法就可以实现。

    public class User implements Serializable {
            private static final long serialVersionUID = 124332435687634567L;
            private String name;
            private boolean sex;
    }
    
            //序列化
            User user = new User("kris",true);
            try {
                ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("user.txt"));
                out.writeObject(user);
                out.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
            //反序列化
            try {
                ObjectInputStream in = new ObjectInputStream(new FileInputStream("user.txt"));
                User newUser = (User) in.readObject();
                in.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
    

    需要注意的是,静态变量属于类不属于对象,所以不会参与序列化过程。其次用transient关键字标记的成员变量也不会参与序列化。

    3.2 Parcelable接口

    Parcelable是Android特有的一个接口,只要实现这个接口,一个类的对象就可以实现序列化并可以通过Intent和Binder传递。常用写法如下:

    public class User implements Parcelable {
        private String name;
        private boolean sex;
        //从序列化后的对象中创建原始对象
        protected User(Parcel in) {
            name = in.readString();
            sex = in.readByte() != 0;
        }
        //反序列化功能由CREATOR来完成,其内部表明了如何创建序列化对象和数组,
        //并通过Parcel的一系列read方法来完成反序列化过程
        public static final Creator<User> CREATOR = new Creator<User>() {
            //从序列化对象创建原始对象
            @Override
            public User createFromParcel(Parcel in) {
                return new User(in);
            }
            //创建指定长度的原始对象数组
            @Override
            public User[] newArray(int size) {
                return new User[size];
            }
        };
        //内容描述功能,几乎在所有情况下都应该返回0,
        //仅当当前对象中存在文件描述符时,此方法返回1
        @Override
        public int describeContents() {
            return 0;
        }
        //序列化功能由writeToParcel方法来完成,最终是通过Parcel中的一系列write方法
        //来完成。将当前对象写入序列化结构中,flags为1时标识当前对象需要作为返回值返回,
        //不能立即释放当前资源,几乎所有情况都返回0
        @Override
        public void writeToParcel(Parcel dest, int flags) {
            dest.writeString(name);
            dest.writeByte((byte) (sex ? 1 : 0));
        }
    }
    

    既然Parcelable和Serializable都能实现序列化并且都能用于Intent间的数据传递,那么二者该如何抉择呢?Serializable使用起来简单但是开销很大,序列化和反序列化过程都需要大量的I/O操作。而Parcelable虽然使用起来麻烦点,但是传输效率很高,因此首选还是Parcelable。Parcleable主要是用在内存序列化上,通过Parcelable讲对象序列化到存储设备或者在网络中传输会稍显复杂,因此这两种情况还是推荐使用Serializable。

    3.3 Binder

    Binder是一个很深入的话题,本章节只会简单介绍一下。从直观上来说,Binder是Android中的一个类,它继承了IBinder接口。从IPC角度来讲,Binder是Android中的一种跨进程通信方式,Binder可以理解为一种虚拟的物理设备,它的驱动是/dev/binder,在Linux里面没有;从Android framework角度来说,Binder是ServerManager连接各种Manager(ActivityManager、WindowManager等等)和ManagerService之间的桥梁;从应用层来说,Binder是客户端和服务端进行通信的媒介,当bindService的时候,服务端会返回一个包含服务端业务调用的Binder对象,通过这个Binder对象,客户端就可以获取服务端提供的服务或者数据(包括普通服务和AIDL服务)。下面会通过AIDL来讲解Binder:(具体demo请在AIDL章节中查看)
    在项目中构建了AIDL代码后,会在app/build/generated/aidl_source_output_dir/debug/compileDebugAidl/out目录下找到对应的XXX.java类。这个类是一个接口并且继承了IInterface接口,虽然看起有点混乱,但是实际上还是很清晰的。

    AIDL结构.png
    首先声明了4个方法getBookList、addBook、registerListener、unregisterListener,这些就是我们在AIDL文件中声明的方法了。接着声明了一个抽象内部类Stub,这个Stub就是一个Binder类,内部声明了4个整形的id来标识transact过程中客户端请求的到底是哪个方法,当客户端和服务端位于同一个进程时,方法不会走跨进程的transact过程,而位于不同进程时,方法调用需要走transact过程,这个逻辑由Stub的内部代理类Proxy来完成。
    DESCRIPTOR
    Binder的唯一标识符,一般用当前Binder的类名表示
    asInterface(android.os.IBinder obj)
    用于将服务端的Binder对象转换成客户端需要的AIDL接口类型的对象,这种转换是区分进程的,如果客户端和服务端位于同一进程,那么此方法返回的就是服务端的Stub本身,否则返回的就是系统封装后的Stub.proxy对象。
    asBinder
    返回当前的Binder对象
    onTransact
    这个方法运行在服务端中的Binder线程池中,当客户端发起跨进程请求时,远程请求会通过系统底层封装后交给此方法处理。该方法的原型为public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags),服务端可以通过code来确定客户端所请求的方法是什么,接着从data中取出目标方法所需要的参数(如果存在参数的话),然后执行目标方法。当目标方法执行结束后,就向reply中写入返回值(如果存在返回值的话)。需要注意的是,如果此方法返回false,那么客户端的请求就会失败,通过这个特性我们可以用它来做权限验证,来避免其他进程随意调用服务端服务。
    proxy#getBookList
    这些方法是运行在客户端的,每当其被调用,首先会创建该方法所需要的输入性Parcel对象_data、输出型Parcel对象_reply和返回值对象_result;然后把该方法的参数信息写入_data中(如果存在参数);接着调用transact方法发起RPC(远程过程调用)请求,同时当前线程挂起;然后服务端的onTransact方法会被调用,直到RPC过程返回后,当前线程继续执行,并从_reply中取出RPC过程的返回结果;最后返回_result中的数据。
    @Override public java.util.List<com.wtwd.aidl.Book> getBookList() throws android.os.RemoteException
    {
    android.os.Parcel _data = android.os.Parcel.obtain();
    android.os.Parcel _reply = android.os.Parcel.obtain();
    java.util.List<com.wtwd.aidl.Book> _result;
    try {
    _data.writeInterfaceToken(DESCRIPTOR);
    mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
    _reply.readException();
    _result = _reply.createTypedArrayList(com.wtwd.aidl.Book.CREATOR);
    }
    finally {
    _reply.recycle();
    _data.recycle();
    }
    return _result;
    }
    

    通过上面的分析,可以初步的了解Binder的工作机制,可以看出当客户端发起远程请求时,当前的线程会被挂起直至服务器进程返回数据,如果这个远程方法是耗时的,就不能在UI线程中发起此次远程请求;其次,由于服务端的Binder方法运行在Binder线程池中,所以此Binder方法不管是否耗时都应该采取同步的方式去实现,因为它已经运行在一个线程中了,下图可以很形象地描述Binder的工作机制。


    Binder.jpg

    接下来会介绍Binder的两个很重要的方法linkToDeath和unlinkToDeath。Binder是运行在服务端进程的,如果服务端进程由于某种原因异常终止,这时候客户端到服务端的Binder会连接断裂(即是Binder死亡),如果客户端不知道断裂的话,其功能就会收到影响。为了解决这个问题,Binder提供了两个配对的方法linkToDeath和unlinkToDeath,通过linkToDeath我们可以给Binder设置一个死亡代理,当Binder死亡时,客户端可以接收到通知,这是可以进行重连操作从而恢复连接。

    //首先声明DeathRecipient 对象
    private IBinder.DeathRecipient deathRecipient = new IBinder.DeathRecipient() {
            //当Binder死亡的时候,系统就会回调binserDied方法,我们就可以移除之前绑定的binder代理并重新绑定远程服务
            @Override
            public void binderDied() {
                if (bookManager == null) {
                    return;
                }
                bookManager.asBinder().unlinkToDeath(deathRecipient,0);
                bookManager = null;
                //重新绑定service
                Intent intent = new Intent("com.wtwd.aidl.aidlService");
                intent.setPackage("com.wtwd.aidl");
                bindService(intent,mConnection, Context.BIND_AUTO_CREATE);
            }
        };
    

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

    private ServiceConnection mConnection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                bookManager = IBookManager.Stub.asInterface(service);
                try {
                    service.linkToDeath(deathRecipient,0);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
    
            @Override
            public void onServiceDisconnected(ComponentName name) {
                bookManager = null;
            }
        };
    

    相关文章

      网友评论

        本文标题:Android中的IPC机制

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