Android中的IPC机制

一、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;
        }
    };
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,423评论 6 491
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,147评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,019评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,443评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,535评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,798评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,941评论 3 407
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,704评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,152评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,494评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,629评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,295评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,901评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,742评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,978评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,333评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,499评论 2 348