一、IPC 基础

1.1 Android 多进程

对于 Android 而言,要实现多进程,有且只有一个方法:在 Android 清单文件中为四大组件声明 android:process 属性。当然还有一些 Native 层的方法,比如通过 JNI 在 Native 层 fork 一个新的进程。

开启 Android 多进程看似很简单,但是这需要做周全的考虑,一旦开启了多进程,一些问题也会接踵而至:

  • Application 会创建多个实例;
  • 单例模式、静态变量失效;
  • 线程同步机制失效;
  • 文件同步读写功能的可靠性下降;
  • ......

然而,虽然 Android 多进程存在很多问题,但是多进程的优点也有很多,比如保活、缓解单进程内存压力、执行后台服务能力等。

其实,Android 多进程带来的各种问题,无非就是跨进程数据共享的问题,Android 系统为开发者提供了多样化的跨进程数据共享途径,这为解决上述问题提供了巨大便利。

1.2 Serializable 接口

Serializable 是一个空接口,是标准 Java 语言中提供的、为处理序列化和反序列化而专门提供的接口。

serialVersionUID 该属性是实现对象反序列化的关键,若用户未指定该属性值,当类结构变化时进行反序列化,可能导致反序列化失败。

1.3 Parcelable 接口

Parcelable 是 Android 系统提供的进行序列化、反序列化的核心接口。相比 Serializable 而言,Parcelable 实现起来较为复杂,但是功能强大、性能较好,更适合 Android 平台。

一个类实现 Parcelable 接口有着固定的格式,实现该接口的类的对象能够方便地被 Intent 和 Binder 传递。

/** * User interface state that is stored by TextView for implementing * {@link View#onSaveInstanceState}. */public static class SavedState extends View.BaseSavedState {    int selStart = -1;    int selEnd = -1;    CharSequence text;    boolean frozenWithFocus;    CharSequence error;    ParcelableParcel editorState;  // Optional state from Editor.    SavedState(Parcelable superState) {        super(superState);    }    @Override    public void writeToParcel(Parcel out, int flags) {        super.writeToParcel(out, flags);        out.writeInt(selStart);        out.writeInt(selEnd);        out.writeInt(frozenWithFocus ? 1 : 0);        TextUtils.writeToParcel(text, out, flags);        if (error == null) {            out.writeInt(0);        } else {            out.writeInt(1);            TextUtils.writeToParcel(error, out, flags);        }        if (editorState == null) {            out.writeInt(0);        } else {            out.writeInt(1);            editorState.writeToParcel(out, flags);        }    }    @Override    public String toString() {        String str = "TextView.SavedState{"                + Integer.toHexString(System.identityHashCode(this))                + " start=" + selStart + " end=" + selEnd;        if (text != null) {            str += " text=" + text;        }        return str + "}";    }    @SuppressWarnings("hiding")    public static final Parcelable.Creator CREATOR =            new Parcelable.Creator() {                public TextView.SavedState createFromParcel(Parcel in) {                    return new TextView.SavedState(in);                }                public TextView.SavedState[] newArray(int size) {                    return new TextView.SavedState[size];                }            };    private SavedState(Parcel in) {        super(in);        selStart = in.readInt();        selEnd = in.readInt();        frozenWithFocus = (in.readInt() != 0);        text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);        if (in.readInt() != 0) {            error = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);        }        if (in.readInt() != 0) {            editorState = ParcelableParcel.CREATOR.createFromParcel(in);        }    }}

1.4 Parcel

Android 进程间的数据传递是 Binder 机制中的重要一环。而数据传递的载体就是 Parcel,Parcel 具有打包和重组的能力,正是 Parcel 的这种能力才能让 Android 跨进程通信成为可能。

简而言之,Parcel 是数据载体,用于承载通过 IBinder 发送的信息,包括数据和对象引用。

如果写入数据时系统发现已经超出了 Parcel 的默认存储能力,它会自动扩展存储空间。

Parcel 作为数据载体,在 Android 系统中举足轻重。其提供了大部分可以传递的数据类型,包括 Java 基本数据类型及其引用类型、String、Map、List、数组、SparseArray、Serializable、Parcelable、Bundle、IBinder、IInterface 等。

Parcel 传入的通常是对象的内容,除此之外,Parcel 还支持 Active Objects 的写入,被 Parcel 写入的 Active Objects 是它们的引用,读取时也是当初被写入的实例,而不是重新创建的实例对象。目前,支持此类传输方式的对象仅有以下两种:

1.Binder
Binder 不仅是 Android 跨进程传递的核心机制,也是一个对象。利用 Parcel 将 Binder 对象写入,读取时仍然是原始的 Binder 对象,或者是其特殊的代理实例(最终操作的对象还是原始的 Binder)。

/** * Write an object into the parcel at the current dataPosition(), * growing dataCapacity() if needed. */public final void writeStrongBinder(IBinder val) {    nativeWriteStrongBinder(mNativePtr, val);}/** * Write an object into the parcel at the current dataPosition(), * growing dataCapacity() if needed. */public final void writeStrongInterface(IInterface val) {    writeStrongBinder(val == null ? null : val.asBinder());}

其中,通过 writeStrongInterface 方法写入的 IInterface 对象,最终会转换为 IBinder 进行处理。

2.FileDescriptor
FileDescriptor 是 Linux 系统中的文件描述符。传递后的 FileDescriptor 对象仍然是基于和原对象相同的文件流进行操作。

/** * {@hide} * This will be the new name for writeFileDescriptor, for consistency. **/public final void writeRawFileDescriptor(FileDescriptor val) {    nativeWriteFileDescriptor(mNativePtr, val);}/** * {@hide} * Write an array of FileDescriptor objects into the Parcel. * * @param value The array of objects to be written. */public final void writeRawFileDescriptorArray(FileDescriptor[] value) {    if (value != null) {        int N = value.length;        writeInt(N);        for (int i=0; i

Parcel 作为读写文件的载体,其实质是一个中介,真正进行数据读写的操作其实是通过 Native 方法进行的。

// base/core/jni/android_os_Parcel.cppstatic jlong android_os_Parcel_create(JNIEnv* env, jclass clazz) {    Parcel* parcel = new Parcel();    return reinterpret_cast(parcel);}

1.5 Bundle

上述提及了 Parcelable 和 Parcel,而 Bundle 是 Parcelable 的实现,也是 Parcel 支持的数据传输类型。通过 Parcel 的 writeBundle 方法和 readBundle 方法进行 Bundle 的读写操作。

/** * Flatten a Bundle into the parcel at the current dataPosition(), * growing dataCapacity() if needed. */public final void writeBundle(Bundle val) {    if (val == null) {        writeInt(-1);        return;    }    val.writeToParcel(this, 0);}
/** * Read and return a new Bundle object from the parcel at the current * dataPosition(), using the given class loader to initialize the class * loader of the Bundle for later retrieval of Parcelable objects. * Returns null if the previously written Bundle object was null. */public final Bundle readBundle(ClassLoader loader) {    int length = readInt();    if (length < 0) {        if (Bundle.DEBUG) Log.d(TAG, "null bundle: length=" + length);        return null;    }    final Bundle bundle = new Bundle(this, length);    if (loader != null) {        bundle.setClassLoader(loader);    }    return bundle;}

Bundle 以键值对的方式存取数据,是一种特殊的 type-safe 容器,并对数据读写进行了一定程度的优化。

除此之外,Bundle 是 Android 开发中常见的数据传递容器,多用于 Android 各组件之间进行数据传递。

1.6 Binder

Binder 与 Linux 息息相关,其涉及到 Linux 内核层。在 Linux 中,可以将 Binder 理解为一个虚拟的物理设备,而这个设备的启动、进程间数据的传输都要依赖于一个叫做 Binder Driver(Binder 驱动)的东西。

Binder 及其复杂,只需简单了解即可,不必深究。在 Android 中,Binder 是一种跨进程通信的方式;也是 ServiceManager(可以理解为互联网中的 DNS 服务,是一种典型的 Binder Server) 连接各种 Manager (ActivityManager、WindowManager 等)的纽带;在 Android 应用层中,Binder 是客户端和服务端进行通信的媒介,客户端可通过 Binder 服务获取来自服务端的业务功能,实现数据跨进程共享。

AIDL 简介
AIDL,接口描述语言,与 Binder 息息相关,其底层就是基于 Binder 的实现。利用 AIDL,Android 可以将跨进程通信玩转地非常溜。

一般开说,创建 aidl 文件后,编译器会自动生成对应的 java 文件,默认代码未格式化,需要开发人员手动格式化查阅。

比如,新建了一个 IMyAidlInterface.aidl 文件:

// IMyAidlInterface.aidlpackage com.example.demo.sample;// Declare any non-default types here with import statementsinterface IMyAidlInterface {    int add(int a, int b);}

对应自动生成的 java 代码:

/* * This file is auto-generated.  DO NOT MODIFY. * Original file: .../IMyAidlInterface.aidl */package com.example.demo.sample;// Declare any non-default types here with import statementspublic interface IMyAidlInterface extends android.os.IInterface {    /**     * Local-side IPC implementation stub class.     */    public static abstract class Stub extends android.os.Binder implements com.example.demo.sample.IMyAidlInterface {        private static final java.lang.String DESCRIPTOR = "com.example.demo.sample.IMyAidlInterface";        /**         * Construct the stub at attach it to the interface.         */        public Stub() {            this.attachInterface(this, DESCRIPTOR);        }        /**         * Cast an IBinder object into an com.example.demo.sample.IMyAidlInterface interface,         * generating a proxy if needed.         */        public static com.example.demo.sample.IMyAidlInterface asInterface(android.os.IBinder obj) {            if ((obj == null)) {                return null;            }            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);            if (((iin != null) && (iin instanceof com.example.demo.sample.IMyAidlInterface))) {                return ((com.example.demo.sample.IMyAidlInterface) iin);            }            return new com.example.demo.sample.IMyAidlInterface.Stub.Proxy(obj);        }        @Override        public android.os.IBinder asBinder() {            return this;        }        @Override        public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {            switch (code) {                case INTERFACE_TRANSACTION: {                    reply.writeString(DESCRIPTOR);                    return true;                }                case TRANSACTION_add: {                    data.enforceInterface(DESCRIPTOR);                    int _arg0;                    _arg0 = data.readInt();                    int _arg1;                    _arg1 = data.readInt();                    int _result = this.add(_arg0, _arg1);                    reply.writeNoException();                    reply.writeInt(_result);                    return true;                }            }            return super.onTransact(code, data, reply, flags);        }        private static class Proxy implements com.example.demo.sample.IMyAidlInterface {            private android.os.IBinder mRemote;            Proxy(android.os.IBinder remote) {                mRemote = remote;            }            @Override            public android.os.IBinder asBinder() {                return mRemote;            }            public java.lang.String getInterfaceDescriptor() {                return DESCRIPTOR;            }            @Override            public int add(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_add, _data, _reply, 0);                    _reply.readException();                    _result = _reply.readInt();                } finally {                    _reply.recycle();                    _data.recycle();                }                return _result;            }        }        static final int TRANSACTION_add = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);    }    public int add(int a, int b) throws android.os.RemoteException;}

这样就生成了一个 AIDL 类 IMyAidlInterface.java,这个类提供了一个自定义的求和方法:

int add(int a, int b);

Stub 和 Stub 中的 Proxy 类是 IMyAidlInterface 的核心,其中一些方法和属性如下:

DESCRIPTOR Binder 的唯一标识,一般为该类类名,用于 attachInterface 和 queryLocalInterface 等场景下使用。

asBinder 用于返回当前的 Binder 对象。

asInterface 将 Binder 对象转换成客户端所需的 IInterface 对象。若客户端和服务端同属一个进程,则返回 Stub 对象;若非同一进程,则返回 Stub.Proxy 代理对象。

onTransate 该方法是处理客户端请求数据,并将处理后的数据返回给客户端的方法,其运行在 Binder 线程池中。其中,入参 code 决定调用的服务端方法,data 是客户端传入的数据,reply 是经过服务端处理后的、需要返回给客户端的信息。

transact 该方法是 Binder 中的方法,其会间接调用 onTransate 方法:

/**     * Default implementation rewinds the parcels and calls onTransact.  On     * the remote side, transact calls into the binder to do the IPC.     */    public final boolean transact(int code, Parcel data, Parcel reply,            int flags) throws RemoteException {        if (false) Log.v("Binder", "Transact: " + code + " to " + this);        if (data != null) {            data.setDataPosition(0);        }        boolean r = onTransact(code, data, reply, flags);        if (reply != null) {            reply.setDataPosition(0);        }        return r;    }

而在跨进程通信的场景下,Stub.Proxy 又会调用 transact 方法,如此便形成一个客户端、服务端跨进程通信的核心标准。

private static class Proxy implements com.example.demo.sample.IMyAidlInterface {            private android.os.IBinder mRemote;            Proxy(android.os.IBinder remote) {                mRemote = remote;            }            @Override            public android.os.IBinder asBinder() {                return mRemote;            }            public java.lang.String getInterfaceDescriptor() {                return DESCRIPTOR;            }            @Override            public int add(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_add, _data, _reply, 0);                    _reply.readException();                    _result = _reply.readInt();                } finally {                    _reply.recycle();                    _data.recycle();                }                return _result;            }        }

由于 Stub.Proxy 只在跨进程的时候初始化,因此 onTransact 也是在跨进程的时候调用的。那么,为什么跨进程的时候才会调用 onTransact 呢?

因为,跨进程时 Android 将原来的 IBinder 对象转换成了 BinderProxy,而 BinderProxy 的 queryLocalInterface 方法返回了 null,所以才会执行到 Stub.Proxy 类中的相关代码。

private static IServiceManager getIServiceManager() {    private static IServiceManager getIServiceManager() {    if (sServiceManager != null) {        return sServiceManager;    }    // Find the service manager    sServiceManager = ServiceManagerNative.asInterface(BinderInternal.getContextObject());    return sServiceManager;}

至于何时转换成了客户端 BinderProxy,涉及到 Native 层的代码。BinderInternal 的 getContextObject 方法会间接调用 JNI 方法 javaObjectForIbinder。其实,JNI 层的 javaObjectForIbinder() 函数创建了 BinderProxy:

jobject javaObjectForIBinder(JNIEnv* env, const sp& val) {    if (val == NULL) return NULL;    if (val->checkSubclass(&gBinderOffsets)) {        // One of our own!        jobject object = static_cast(val.get())->object();        LOGDEATH("objectForBinder %p: it's our own %p!\n", val.get(), object);        return object;    }    // For the rest of the function we will hold this lock, to serialize    // looking/creation of Java proxies for native Binder proxies.    AutoMutex _l(mProxyLock);    // Someone else's...  do we know about it?    jobject object = (jobject)val->findObject(&gBinderProxyOffsets);    if (object != NULL) {        jobject res = jniGetReferent(env, object);        if (res != NULL) {            ALOGV("objectForBinder %p: found existing %p!\n", val.get(), res);            return res;        }        LOGDEATH("Proxy object %p of IBinder %p no longer in working set!!!", object, val.get());        android_atomic_dec(&gNumProxyRefs);        val->detachObject(&gBinderProxyOffsets);        env->DeleteGlobalRef(object);    }    object = env->NewObject(gBinderProxyOffsets.mClass, gBinderProxyOffsets.mConstructor);    if (object != NULL) {        LOGDEATH("objectForBinder %p: created new proxy %p !\n", val.get(), object);        // The proxy holds a reference to the native object.        env->SetLongField(object, gBinderProxyOffsets.mObject, (jlong)val.get());        val->incStrong((void*)javaObjectForIBinder);        // The native object needs to hold a weak reference back to the        // proxy, so we can retrieve the same proxy if it is still active.        jobject refObject = env->NewGlobalRef(                env->GetObjectField(object, gBinderProxyOffsets.mSelf));        val->attachObject(&gBinderProxyOffsets, refObject,                jnienv_to_javavm(env), proxy_cleanup);        // Also remember the death recipients registered on this proxy        sp drl = new DeathRecipientList;        drl->incStrong((void*)javaObjectForIBinder);        env->SetLongField(object, gBinderProxyOffsets.mOrgue, reinterpret_cast(drl.get()));        // Note that a new object reference has been created.        android_atomic_inc(&gNumProxyRefs);        incRefsCreated(env);    }    return object;}

二、Android 四大组件与 Binder

Binder 最大的消费者便是来自 Android 中的各种应用程序。应用程序通过四大组件实现 Binder 数据传输和跨进程通信。简单的 startActivity、bindService、sendBroadcast、query 等方法底层却蕴含着与其他进程交互的核心逻辑。

Android 进程间通信(IPC)_第1张图片 应用程序间的 Binder 通信

2.1 Activity

通过 startActivity 可启动目标进程,内部通过 Intent 匹配最佳目标,该目标可能来自其他进程,比如通过该方法启动系统通讯录。

public void startActivity(Class<? extends ActivityCapacity> capacityClass) {    Intent intent = new Intent(mActivity, BaseActivityImpl.class);    intent.putExtra(ActivityCapacity.CAPACITY_NAME, capacityClass.getName());    mActivity.startActivity(intent);}
public @NonNull Intent putExtra(String name, String value) {    if (mExtras == null) {        mExtras = new Bundle();    }    mExtras.putString(name, value);    return this;}

Intent 通过 Bundle 建立进程间交互的基础数据,由于 Bundle 实现了 Parcelable 接口,所以它可以方便地在各进程间传输。

2.2 Service

应用程序可以通过 startService 或 bindService 来启动特定服务,而这个服务可能来自其他进程。

启动之前仍然可以通过构造 Bundle 来进行数据传输。

2.3 BroadcastReceiver

应用程序可以通过 sendBroadcast 发送一个特定的广播给目标进程,实现跨进程通信。广播携带的数据可以通过 Intent 声明,数据打包可利用 Bundle 实现。

2.4 ContentProvider

除上面的三大组件外,ContentProvider 是唯一一个不通过 Bundle 实现跨进程数据共享的组件。

通过 ContentProvider 的 query、insert、update、delete 等方法,Android 跨进程数据共享成为可能。ContentProvider 的底层仍然是 Binder,我们无需关心底层具体实现,只需默默享受 Binder 带来的便利即可。

ContentProvider 操作的数据一般为 SQLite 中的内容,可以认为,ContentProvider 是 Binder 通信与文件共享相结合的一种跨进程通信方案。

三、Android 中的其他 IPC

我们知道,Android 中的三大组件 Activity、Service、BroadcastReceiver 可利用 Bundle 辅助完成进程间通信,而 ContentProvider 也可以利用自身特殊功能完成数据共享和操作。

除此之外,Android 跨进程通信还可以利用其他方式实现,比如 AIDL、Messenger、文件共享、Socket 等。

3.1 Messenger

Messenger 是一种轻量级的跨进程传输方式,可将数据打包成 Message,通过这种方式轻松实现跨进程传输。

其实,Messenger 的底层实现依然是 AIDL,只不过 Messenger 将 AIDL 进行了再次封装,使得跨进程传输更加便捷。

public final class Messenger implements Parcelable {    private final IMessenger mTarget;    /**     * Create a new Messenger pointing to the given Handler.  Any Message     * objects sent through this Messenger will appear in the Handler as if     * {@link Handler#sendMessage(Message) Handler.sendMessage(Message)} had     * been called directly.     *      * @param target The Handler that will receive sent messages.     */    public Messenger(Handler target) {        mTarget = target.getIMessenger();    }        /**     * Send a Message to this Messenger's Handler.     *      * @param message The Message to send.  Usually retrieved through     * {@link Message#obtain() Message.obtain()}.     *      * @throws RemoteException Throws DeadObjectException if the target     * Handler no longer exists.     */    public void send(Message message) throws RemoteException {        mTarget.send(message);    }        /**     * Retrieve the IBinder that this Messenger is using to communicate with     * its associated Handler.     *      * @return Returns the IBinder backing this Messenger.     */    public IBinder getBinder() {        return mTarget.asBinder();    }       /**     * Create a Messenger from a raw IBinder, which had previously been     * retrieved with {@link #getBinder}.     *      * @param target The IBinder this Messenger should communicate with.     */    public Messenger(IBinder target) {        mTarget = IMessenger.Stub.asInterface(target);    }}

Messenger 的特点

  1. 仅支持串行通信,一次只处理一个请求,无需考虑并发问题;
  2. 通过 Message 打包数据进行传输;
  3. Message 中的 object 字段虽在 Android 2.2 后支持了 Parcelable,但是只有系统提供的 Parcelable 对象才能通过 object 跨进程传输,自定义的 Parcelable 无法跨进程传输,但可通过 msg、arg1、arg2 承载跨进程传输需要的数据。

使用 Message
首先模拟远程客户端进程(这里使用同一 App 的不同进程模拟不同 App 的不同进程,其实质上无任何区别),创建 RemoteService 类:

public class RemoteService extends Service {    private static final int SUM_COMP = 0x01;    private static final int NUM_SEND = 0x02;    private Handler mServeHandle = new Handler() {        @Override        public void handleMessage(Message msg) {            Message msgToClient = Message.obtain();            switch (msg.what) {                case NUM_SEND:                    SystemClock.sleep(2000L);                    msgToClient.what = SUM_COMP;                    msgToClient.arg1 = msg.arg1;                    msgToClient.arg2 = msg.arg1 + msg.arg2;                    try {                        msg.replyTo.send(msgToClient);                    } catch (RemoteException e) {                        e.printStackTrace();                    }                    break;            }            super.handleMessage(msg);        }    };    private Messenger serveMessenger = new Messenger(mServeHandle);    @Nullable    @Override    public IBinder onBind(Intent intent) {        return serveMessenger.getBinder();    }}

别忘了在清单文件中注册 Service:

 

上面的代码展示了 Messenger 利用 mServeHandle 将客户端发送过来的消息进行处理的过程,当接收到客户端消息后,可通过 replyTo 对象将消息处理后回传给客户端。

而这个 replyTo 对象指向的就是客户端的 Messenger 对象(实现了
Parcelable 接口,可跨进程传输),send 方法内部其实是通过 Handler 进行处理的:

public class ClientActivity extends AppCompatActivity {    private LinearLayout llRoot;    private boolean conned = false;    private Messenger serveMessenger;    private Messenger clientMessenger = new Messenger(new Handler() {        @Override        public void handleMessage(Message msgFromServer) {            switch (msgFromServer.what) {                case SUM_COMP:                    TextView tv = (TextView) findViewById(msgFromServer.arg1);                    tv.setText(tv.getText() + "=>" + msgFromServer.arg2);                    break;            }            super.handleMessage(msgFromServer);        }    });    private static final int SUM_COMP = 0x01;    private static final int NUM_SEND= 0x02;    ServiceConnection serviceConnection = new ServiceConnection() {        @Override        public void onServiceConnected(ComponentName name, IBinder service) {            serveMessenger = new Messenger(service);            conned = true;            setTitle("已连接到服务");        }        @Override        public void onServiceDisconnected(ComponentName name) {            conned = false;            setTitle("已与服务断开连接");            serveMessenger = null;        }    };    int mA;    @Override    protected void onCreate(@Nullable Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_client);        llRoot = (LinearLayout) findViewById(R.id.root);        findViewById(R.id.send).setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                if (!conned) {                    return;                }                try {                    int a = mA++;                    int b = (int) (Math.random() * 100);                    //创建一个tv,添加到LinearLayout中                    TextView tv = new TextView(ClientActivity.this);                    tv.setText(a + " + " + b + " = caculating ...");                    tv.setId(a);                    llRoot.addView(tv);                    Message msgFromClient = Message.obtain(null, NUM_SEND, a, b);                    msgFromClient.replyTo = clientMessenger;                    //往服务端发送消息                    serveMessenger.send(msgFromClient);                } catch (RemoteException e) {                    e.printStackTrace();                }            }        });        Intent intent = new Intent(this, RemoteService.class);        bindService(intent, serviceConnection, BIND_AUTO_CREATE);    }    @Override    protected void onDestroy() {        super.onDestroy();        unbindService(serviceConnection);    }}

上面的代码建立了客户端将消息发送给远程服务端,且收到服务端处理后的消息进行的一系列操作。

首先,客户端通过 ServiceConnection 与服务端建立通信,在 onServiceConnected 方法中获取来自服务端的 Messenger 对象 serveMessenger。

当客户端通过 send 按钮发起消息请求时,会利用 serveMessenger 将打包好的消息发送给远程服务端,消息中的 replyTo 对象承载了客户端的 Messenger 对象,用于处理服务端收到客户端消息后的消息回传。

和 AIDL 一样,当客户端建立与服务端的通信后,就可以利用服务端的 IBinder 对象处理信息,IBinder 对象中的 Handler 虽然不是跨进程支持的对象类型,但是由 IBinder 持有。由此可见,Parcel 确实支持引用对象(Active Objects) IBinder 的跨进程传输。

3.2 AIDL

创建 aidl 文件时,若文件中有使用到其他 Parcelable 类,必须声明其对应的 aidl 文件。比如 IMyAidlInterface 中使用到了 Person 类,则必须声明 Person 类对应的 aidl 文件:

// IMyAidlInterface.aidlpackage com.example.demo.sample;import com.example.demo.sample.Person;// Declare any non-default types here with import statementsinterface IMyAidlInterface {    int add(int a, int b);}
// Person.aidlpackage com.example.demo.sample;parcelable Person;
public class Person implements Parcelable {    private String name;    private int age;    protected Person(Parcel in) {        name = in.readString();        age = in.readInt();    }    public static final Creator CREATOR = new Creator() {        @Override        public Person createFromParcel(Parcel in) {            return new Person(in);        }        @Override        public Person[] newArray(int size) {            return new Person[size];        }    };    @Override    public int describeContents() {        return 0;    }    @Override    public void writeToParcel(Parcel dest, int flags) {        dest.writeString(name);        dest.writeInt(age);    }}

创建的 aidl 文件即使在同一 package 中,也最好要 import 类路径;其次,所有 aidl 文件最好在单独的 aidl 分类目录下统一创建,当拷贝至客户端编译环境中时,能保证类路径不会出错(客户端和服务端的 aidl 包名必须一致,否则通信时反序列化出错)。

创建远程服务端 Service,用于计算两个数的和:

public class RemoteService extends Service {    @Nullable    @Override    public IBinder onBind(Intent intent) {        return iBinder;    }    private IBinder iBinder = new IMyAidlInterface.Stub() {        @Override        public int add(int a, int b) throws RemoteException {            return a + b;        }    };}

客户端提供两个数给服务端进行求和:

public class AidlActivity extends AppCompatActivity {    private IMyAidlInterface iMyAidlInterface;    private Button btnAdd;    private LinearLayout llContent;    private int count;    private Handler handler = new Handler() {        @Override        public void handleMessage(Message msg) {            add();            SystemClock.sleep(10);            sendEmptyMessage(0);            super.handleMessage(msg);            test.Stub        }    };    private ServiceConnection serviceConnection = new ServiceConnection() {        @Override        public void onServiceConnected(ComponentName name, IBinder service) {            setTitle("已连接到服务");            iMyAidlInterface = IMyAidlInterface.Stub.asInterface(service);        }        @Override        public void onServiceDisconnected(ComponentName name) {            setTitle("已与服务断开连接");            iMyAidlInterface = null;        }    };    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_aidl);        llContent = (LinearLayout) findViewById(R.id.content);        btnAdd = (Button) findViewById(R.id.add);        btnAdd.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                handler.sendEmptyMessage(0);            }        });        bindService();    }    ScrollView sv;    private void add() {        TextView textView = new TextView(AidlActivity.this);        textView.setId(count++);        llContent.addView(textView);        int a = new Random().nextInt(100);        int b = new Random().nextInt(100);        textView.setText(a + " + " + b + "=");        try {            textView.setText(textView.getText().toString() + iMyAidlInterface.add(a, b));        } catch (RemoteException e) {            e.printStackTrace();        }        sv = (ScrollView) findViewById(R.id.sv);        sv.fullScroll(View.FOCUS_DOWN);    }    private void bindService() {        Intent service = new Intent(this, RemoteService.class);        bindService(service, serviceConnection, BIND_AUTO_CREATE);    }    @Override    protected void onDestroy() {        super.onDestroy();        unbindService(serviceConnection);    }}

至此,AIDL 跨进程的通信实现结束了。可以发现,自定义的 AIDL 能够提供更加强大的功能,客户端可以调用服务端提供的任何方法、获取任何数据,且支持并发实时通信。

对于 Messenger 而言,客户端只能通过调用 send 方法,通过发送消息的形式进行跨进程串行通信。

上面的 Demo 未处理并发操作,当多个客户端请求服务端功能时,会存在多个线程同时访问的情况,实际开发中需要考虑到并发调用的场景。

3.3 Clipboard

系统剪切板是一个系统提供的数据粘贴、复制工具,通常情况下由用户主动发起。开发人员也可以利用剪切板功能,实现跨进程数据传输。

由于系统剪切板面临数据的多变性、不稳定性,使用该功能处理跨进程数据传输时,最好应用于强交互的场景,让用户有所感知这种数据不稳定带来的缺点。

Android 开发人员可以利用系统提供的 ClipboardManager 类,实现跨进程数据共享。

3.4 文件

文件共享也可以作为 Android 跨进程共享的一种可选方案。通过两个进程对同一个文件进行读、写操作,实现跨进程通信。

处理文件读写时,需注意文件并发读写等问题,同时还需关注操作文件带来的一系列其它 IO 问题。

Android 中的 SharedPreference 对并发读写的支持并没有想象中的美好,SharedPreference 会有一份内存中的缓存,多进程模式下对 SharedPreference 的读写会变得不可靠,并发场景下可能导致数据丢失。

SharedPreference 特点如下:

  1. 支持跨进程获取数据,但不支持互斥读写操作;
  2. SharedPreference 文件缓存到内存中后,再次读取则读取的是内存中的数据;
  3. 数据轻量,适合存储和读取轻量数据,大数据可能导致主线程阻塞;
  4. commit 同步写,apply 异步读写(不支持并发);
  5. MODE_MULTI_PROCESS 形同虚设,已不建议使用,其并不支持多进程同步更新,而是在 getSharedPreferences 时,才会重新从 xml 加载,如果在一个进程中更新 xml,但是没有通知另一个进程,那么另一个进程的getSharePreferences 是不会自动更新的。

官方已不支持 MODE_MULTI_PROCESS:

This constant was deprecated in API level 23. MODE_MULTI_PROCESS does not work reliably in some versions of Android, and furthermore does not provide any mechanism for reconciling concurrent modifications across processes. Applications should not attempt to use it. Instead, they should use an explicit cross-process data management approach such as ContentProvider.

SQLite 本质上也是一种文件,基于 Binder 实现的 ContentProvider,结合 SQLite,也可以实现跨进程共享,且支持文件的并发互斥。

3.5 Socket

还有一个可能被我们忽略的跨进程通信的方案:Socket。Socket 是网络通信中的概念,同样适用于跨进程通信。

使用 Socket 进行通信,需要在 Android 清单文件中声明网络相关权限:

通过 Socket 在本地多进程模式下,实现跨进程的服务端示例代码如下:

public class SmsService extends IntentService {    private boolean isDes;    /**     * Creates an IntentService.  Invoked by your subclass's constructor.     *     * @param name Used to name the worker thread, important only for debugging.     */    public SmsService(String name) {        super(name);    }    public SmsService() {        super("SmsService");    }    @Override    protected void onHandleIntent(Intent intent) {        try {            ServerSocket serverSocket = new ServerSocket(8688);            while (!isDes) {                Socket client = serverSocket.accept();                replyClient(client);            }        } catch (IOException e) {            e.printStackTrace();        }    }    private void replyClient(final Socket socket) {        new Thread(new Runnable() {            PrintWriter pw;            BufferedReader br;            @Override            public void run() {                try {                    br = new BufferedReader(new InputStreamReader(                            socket.getInputStream()));                    pw = new PrintWriter(new OutputStreamWriter(                            socket.getOutputStream()), true);                    pw.println("我是服务器!!!");                } catch (IOException e) {                    e.printStackTrace();                }                Random random = new Random();                try {                    while (!isDes) {                        String msg = br.readLine().trim();                        if (!TextUtils.isEmpty(msg)) {                            SystemClock.sleep(random.nextInt(3000));                            pw.println("我收到了来自客户端的消息:" + msg);                        }                    }                } catch (IOException e) {                    e.printStackTrace();                } finally {                    pw.close();                    try {                        br.close();                        socket.close();                    } catch (IOException e) {                        e.printStackTrace();                    }                }            }        }).start();    }    @Override    public void onDestroy() {        super.onDestroy();        isDes = true;    }}

客户端代码如下:

public class SmsActivity extends AppCompatActivity {    private Socket socket;    private PrintWriter pw;    private TextView tvMsg;    private EditText etMsg;    private Button btnSendMsg;    private ScrollView sv;    private Executor threadPool = Executors.newFixedThreadPool(3);    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_sms);        etMsg = (EditText) findViewById(R.id.et_msg);        tvMsg = (TextView) findViewById(R.id.tv_msg);        btnSendMsg = (Button) findViewById(R.id.btn_send_msg);        sv = (ScrollView) findViewById(R.id.sv);        Intent i = new Intent(this, SmsService.class);        startService(i);        threadPool.execute(new Runnable() {            @Override            public void run() {                receiveFromServer();            }        });        btnSendMsg.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                String msg = etMsg.getText().toString().trim();                if (TextUtils.isEmpty(msg)) {                    return;                }                pw.println(msg);                tvMsg.append("客户端:" + msg + "\n");                etMsg.setText(null);                sv.fullScroll(View.FOCUS_DOWN);            }        });    }    private void receiveFromServer() {        BufferedReader br = null;        try {            while (socket == null) {                try {                    socket = new Socket("localhost", 8688);                    pw = new PrintWriter(                            new OutputStreamWriter(socket.getOutputStream()), true);                    br = new BufferedReader(                            new InputStreamReader(socket.getInputStream()));                } catch (Exception e) {                    e.printStackTrace();                }            }            while (!isFinishing()) {                final String msg = br.readLine();                if (msg != null) {                    runOnUiThread(new Runnable() {                        @Override                        public void run() {                            tvMsg.append("服务端:" + msg + "\n");                            sv.fullScroll(View.FOCUS_DOWN);                        }                    });                }            }            br.close();        } catch (IOException e) {            e.printStackTrace();        } finally {            if (pw != null)                pw.close();            try {                if (socket != null)                    socket.close();            } catch (IOException e) {                e.printStackTrace();            }        }    }    @Override    protected void onDestroy() {        super.onDestroy();    }}

Socket 支持网络传输字节流,同样是一种本地跨进程通信的好方案,支持一对多并发实时通信,但是实现起来可能稍显复杂。

总结

Android 提供的跨进程方案林林总总,我们需要做的是,在合适场景下选择合适的跨进程通信方案,为业务提供最佳技术实现。除此之外,一些其他跨进程方案也值得尝试。

当然,为实现跨进程通信,一些第三方提供的 lib 也可以作为备选方案,比如 HermesEventBus、ProviGen、Tray 等。

本文由 Fynn_ 原创,未经许可,不得转载!

更多相关文章

  1. Android解析服务器端发来的xml数据示例
  2. Android 应用程序中使用 Internet 数据(XML、JSON 和 protocol bu
  3. 初学者---Android Fragment之间数据传递的三种方式
  4. Android多媒体数据库之MediaStore研究
  5. Android应用开发基础之数据存储和界面展现(二)

随机推荐

  1. Android访问网络,使用HttpURLConnection还
  2. application中android:name的作用
  3. android 4.x receiver、services无法启动
  4. Android中触摸事件传递过程
  5. Android 内核解剖 - AMS(Activity Manager
  6. Android(安卓)4.0 Launcher2源码分析——
  7. 如何把个人业余开源项目做到月入8万美元?
  8. Android并发编程线程间通信的三种基本方
  9. Android Shell 常用命令
  10. android开发之Google工程师多图详解Andro