Android进程间通信(二)

在上一节中,主要阐述了Parcelable的序列化和AIDL文件的书写规范,以及相应的示例。这一节,将实现一个完整的通过AIDL实现进程通信的示例。首先要说明的是,这个例子并不符合Android的开发规范, AIDL是在涉及跨应用并需要处理多线程的情况下才有必要使用的,而这个例子并不涉及跨应用的情况。为了方便起见,此例将客户端和服务端放在了同一应用下的不同进程。(AIDL代码和Parcelable代码沿用上文示例)

Android进程间通信(一)

准备工作

在示例中,创建一个简单的Activity和一个简单的Service。以Activity作为客户端, 以Service作为服务端, 在AndroidManifest.xml文件中将service的进程设为 :remote

接下来实现Service中的相关内容, 首先要实现Binder

//定义一个Binder, IMyAidlInterface.Stub类继承自Binder类, 并实现了IMyAidlInterface接口的抽象类, 经AIDL文件编译后生成
private final IMyAidlInterface.Stub mBinder = new IMyAidlInterface.Stub() {
        @Override
        public NewsEntity getData(int id, String title) throws RemoteException {
            System.out.println("Server: Thread.currentThread().getName() = " + Thread.currentThread().getName());
            try {
                Thread.sleep(2000);
                callbackClient();
            }catch (Exception e){
                e.printStackTrace();
            }
            return new NewsEntity(id, title);
        }
    };
@Nullable
@Override
public IBinder onBind(Intent intent) {
    return mBinder;
}

Activity 的相关实现

//在Activity中要实现ServiceConnection接口的相关方法, 例如.
private ServiceConnection mConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        iService = IMyAidlInterface.Stub.asInterface(service);

        System.out.println("mConnection: Thread.currentThread().getName() = " + Thread.currentThread().getName());
        try {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        NewsEntity entity = iService.getData(1, "Hello Activity");
                        System.out.println(entity.toString());
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                }
            }).start();


        }catch (Exception e){
            e.printStackTrace();
        }
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {

    }
};

客户端调用服务端

首先要在Activity中绑定Service

Intent intent = new Intent(this, RemoteService.class);
intent.setAction(RemoteService.class.getName());
bindService(intent, mConnection, BIND_AUTO_CREATE);

在与Service连接成功后, 需要获取到对应的接口对象

iService = IMyAidlInterface.Stub.asInterface(service);

调用Service方法

new Thread(new Runnable() {
    @Override
    public void run() {
        try {
            NewsEntity entity = iService.getData(1, "Hello Activity");
            System.out.println(entity.toString());
        }catch (Exception e){
            e.printStackTrace();
        }
    }
).start();

为什么另起线程调用?

在AIDL文件中, 若没有在对应的方法中声明oneway, 则调线程将会处于阻塞状态,直到被调用的方法执行完毕.在客户端的主线程中跨进程调用,有可能导致ANR的发生.

服务端回调客户端

要实现服务端回调客户端, 需要借助到RemoteCallbackList类.RemoteCallbackList的主要作用是为了负责维护一个远程接口列表,比如执行服务端对客户端的回调.

  1. 新建一个.aidl文件: IClientInterface.aidl
// IClientInterface.aidl
package th.how.bean;

// Declare any non-default types here with import statements

interface IClientInterface {
    void callbackClient();
}
  1. 修原来的.aidl文件,新添加两个方:

register(IClientInterface callback);

unRegister(IClientInterface callback);

package th.how.bean;
import th.how.bean.NewsEntity;
import th.how.bean.IClientInterface;

interface IMyAidlInterface {
    NewsEntity getData(int id, String title);
    void register(IClientInterface callback);
    void unRegister(IClientInterface callback);
}
  1. 修改Service文件, 增加新的RemoteCallbackList成员, 和修改Binder, 实现aidl文件中新增的两个方法, 以及添加一个回调事件:callbackClient().
private RemoteCallbackList<IClientInterface> mCallback = new RemoteCallbackList<>();

private final IMyAidlInterface.Stub mBinder = new IMyAidlInterface.Stub() {
    @Override
    public NewsEntity getData(int id, String title) throws RemoteException {
        System.out.println("Server: Thread.currentThread().getName() = " + Thread.currentThread().getName());
        try {
            Thread.sleep(2000);
            callbackClient();
        }catch (Exception e){
            e.printStackTrace();
        }
        return new NewsEntity(id, title);
    }

    @Override
    public void register(IClientInterface callback) throws RemoteException {
        mCallback.register(callback);
    }

    @Override
    public void unRegister(IClientInterface callback) throws RemoteException {
        mCallback.unregister(callback);
    }
};
    
private void callbackClient(){
    int num = mCallback.beginBroadcast();
    for(int i = 0; i < num; i ++){
        IClientInterface iClientInterface = mCallback.getBroadcastItem(i);
        if(iClientInterface != null){
            try{
                iClientInterface.callbackClient();
            }catch(Exception e){
                e.printStackTrace();
            }
        }
    }
    mCallback.finishBroadcast();
}
  1. 在客户端实现IClientInterface.aidl编译后生成的接口, 并调用service 的register方法
private IClientInterface client = new IClientInterface.Stub() {
    @Override
    public void callbackClient() throws RemoteException {
        System.out.println("Client: Thread.currentThread().getName() = " + Thread.currentThread().getName());
    }
};

private ServiceConnection mConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        iService = IMyAidlInterface.Stub.asInterface(service);
        System.out.println("mConnection: Thread.currentThread().getName() = " + Thread.currentThread().getName());
        try {
            iService.register(client);
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        NewsEntity entity = iService.getData(1, "Hello Activity");
                        System.out.println(entity.toString());
                    }catch (Exception e){
                        e.printStackTrace();
                    }
                }
            }).start();

        }catch (Exception e){
            e.printStackTrace();
        }
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {

    }
};

通过以上步骤,就大概实现了Service和Client的双向通信.

补充

  • DeathRecipient IBinder的死亡通知机制

DeathRecipient是一个接口类,设计出来是为了当对应Ibinder的进程死亡时另一进程能接受回调通知

IBinder.java

/**
     *为这个BInder注册一个死亡通知容器.如果这个Binder意外死
     *亡,则对应的DeathRecipient对象的binderDied方法将会被调
     *用
     *它只能用来接受来自远端Binder的通知,而不能用来处理本地
     *IBinder的死亡
     * Register the recipient for a notification if this binder
     * goes away.  If this binder object unexpectedly goes away
     * (typically because its hosting process has been killed),
     * then the given {@link DeathRecipient}'s
     * {@link DeathRecipient#binderDied DeathRecipient.binderDied()} method
     * will be called.
     * 
     * <p>You will only receive death notifications for remote binders,
     * as local binders by definition can't die without you dying as well.
     * 
     * @throws RemoteException if the target IBinder's
     * process has already died.
     * 
     * @see #unlinkToDeath
     */
    public void linkToDeath(DeathRecipient recipient, int flags) throws RemoteException;
/**
     *移除已经注册了的死亡通知, 如果这个IBinder对象死掉,容器也不会再
     *发出通知
     * Remove a previously registered death notification.
     * The recipient will no longer be called if this object
     * dies.
     * 
     * @return {@code true} if the <var>recipient</var> is successfully
     * unlinked, assuring you that its
     * {@link DeathRecipient#binderDied DeathRecipient.binderDied()} method
     * will not be called;  {@code false} if the target IBinder has already
     * died, meaning the method has been (or soon will be) called.
     * 
     * @throws java.util.NoSuchElementException if the given
     * <var>recipient</var> has not been registered with the IBinder, and
     * the IBinder is still alive.  Note that if the <var>recipient</var>
     * was never registered, but the IBinder has already died, then this
     * exception will <em>not</em> be thrown, and you will receive a false
     * return value instead.
     */
    public boolean unlinkToDeath(DeathRecipient recipient, int flags);
  • RemoteCallbackList

负责维护一个远程接口列表, 如执行从服务端到客户端的回调.它保持了对一系列注册的回调事件的追踪.通过调用IInterface.asBinder()方法可以分辨出它们.

使用mCallbacks的对象锁,应对多线程调用.并在不持有列表锁的情况下,可以线程安全的遍历列表.

public class RemoteCallbackList<E extends IInterface> {

    //一个存储IBinder与Callback键值对的Map
    /*package*/ ArrayMap<IBinder, Callback> mCallbacks
            = new ArrayMap<IBinder, Callback>();
    private Object[] mActiveBroadcast;
    private int mBroadcastCount = -1;
    private boolean mKilled = false;
    
    //实现DeathRecipient接口, 当IBinder对应的进程死亡后,移除对应的Callback
    private final class Callback implements IBinder.DeathRecipient {
        final E mCallback;
        final Object mCookie;
        
        Callback(E callback, Object cookie) {
            mCallback = callback;
            mCookie = cookie;
        }

        public void binderDied() {
            synchronized (mCallbacks) {
                mCallbacks.remove(mCallback.asBinder());
            }
            onCallbackDied(mCallback, mCookie);
        }
    }

    /**
     * Simple version of {@link RemoteCallbackList#register(E, Object)}
     * that does not take a cookie object.
     */
    public boolean register(E callback) {
        return register(callback, null);
    }
    /**
     *使用synchronized关键字, 保证对mCallbacks操作的线程安全
     *构造Callback对象, 完成binder注册死亡通知事件,
     *存储键值对
     * @see #unregister
     * @see #kill
     * @see #onCallbackDied
     */
    public boolean register(E callback, Object cookie) {
        synchronized (mCallbacks) {
            if (mKilled) {
                return false;
            }
            IBinder binder = callback.asBinder();
            try {
                Callback cb = new Callback(callback, cookie);
                binder.linkToDeath(cb, 0);
                mCallbacks.put(binder, cb);
                return true;
            } catch (RemoteException e) {
                return false;
            }
        }
    }

    /**
     *从mCallbacks中移除对应的callback
     *并解绑死亡通知
     * @see #register
     */
    public boolean unregister(E callback) {
        synchronized (mCallbacks) {
            Callback cb = mCallbacks.remove(callback.asBinder());
            if (cb != null) {
                cb.mCallback.asBinder().unlinkToDeath(cb, 0);
                return true;
            }
            return false;
        }
    }

    /**
     *清空mCallbacks
     *
     * @see #register
     */
    public void kill() {
        synchronized (mCallbacks) {
            for (int cbi=mCallbacks.size()-1; cbi>=0; cbi--) {
                Callback cb = mCallbacks.valueAt(cbi);
                cb.mCallback.asBinder().unlinkToDeath(cb, 0);
            }
            mCallbacks.clear();
            mKilled = true;
        }
    }

    /**
     *callback对应的进程死亡后的回调
     */
    public void onCallbackDied(E callback) {
    }
    
    /**
     * Called when the process hosting a callback in the list has gone away.
     * The default implementation calls {@link #onCallbackDied(E)}
     * for backwards compatibility.
     * 
     * @param callback The callback whose process has died.  Note that, since
     * its process has died, you can not make any calls on to this interface.
     * You can, however, retrieve its IBinder and compare it with another
     * IBinder to see if it is the same object.
     * @param cookie The cookie object original provided to
     * {@link #register(E, Object)}.
     * 
     * @see #register
     */
    public void onCallbackDied(E callback, Object cookie) {
        onCallbackDied(callback);
    }

    /**
     *准备调用已经注册的回调, 为数组mActiveBroadcast赋值
     *需要注意的是同一时间,只能有一个broadcast是激活的.
     * @see #getBroadcastItem
     * @see #finishBroadcast
     */
    public int beginBroadcast() {
        synchronized (mCallbacks) {
            if (mBroadcastCount > 0) {
                throw new IllegalStateException(
                        "beginBroadcast() called while already in a broadcast");
            }
            
            final int N = mBroadcastCount = mCallbacks.size();
            if (N <= 0) {
                return 0;
            }
            Object[] active = mActiveBroadcast;
            if (active == null || active.length < N) {
                mActiveBroadcast = active = new Object[N];
            }
            for (int i=0; i<N; i++) {
                active[i] = mCallbacks.valueAt(i);
            }
            return N;
        }
    }

    /**
     * 从mActiveBroadcast中检索一item
     *只有在beginBroadcast和finishBroadcast之间获取值才有效需要处理.
     *由于返回的callback对应的进程可能kill了, 所以需要处理RemoteException(简单处理, 忽略掉它)
     * @param index Which of the registered callbacks you would like to
     * retrieve.  Ranges from 0 to 1-{@link #beginBroadcast}.
     *
     * @return Returns the callback interface that you can call.  This will
     * always be non-null.
     *
     * @see #beginBroadcast
     */
    public E getBroadcastItem(int index) {
        return ((Callback)mActiveBroadcast[index]).mCallback;
    }
    
    /**
     * Retrieve the cookie associated with the item
     * returned by {@link #getBroadcastItem(int)}.
     * 
     * @see #getBroadcastItem
     */
    public Object getBroadcastCookie(int index) {
        return ((Callback)mActiveBroadcast[index]).mCookie;
    }

    /**
     *清理掉mActiveBroadcast
     * @see #beginBroadcast
     */
    public void finishBroadcast() {
        synchronized (mCallbacks) {
            if (mBroadcastCount < 0) {
                throw new IllegalStateException(
                        "finishBroadcast() called outside of a broadcast");
            }

            Object[] active = mActiveBroadcast;
            if (active != null) {
                final int N = mBroadcastCount;
                for (int i=0; i<N; i++) {
                    active[i] = null;
                }
            }

            mBroadcastCount = -1;
        }
    }

    /**
     * Returns the number of registered callbacks. Note that the number of registered
     * callbacks may differ from the value returned by {@link #beginBroadcast()} since
     * the former returns the number of callbacks registered at the time of the call
     * and the second the number of callback to which the broadcast will be delivered.
     * <p>
     * This function is useful to decide whether to schedule a broadcast if this
     * requires doing some work which otherwise would not be performed.
     * </p>
     *
     * @return The size.
     */
    public int getRegisteredCallbackCount() {
        synchronized (mCallbacks) {
            if (mKilled) {
                return 0;
            }
            return mCallbacks.size();
        }
    }
}

RemoteCallbackList帮助我们维护了一个Map,并且处理了远端进程死亡后的事件, 从Map中移除了对应的callback,实现了对资源的清理.

Binder连接池

Binder连接池是为了解决客户端通过多个AIDL的方式实现IPC需要创建多个Service的问题.
可以参考这篇文章

Android IPC机制(四):细说Binder连接池

本文完整代码

NewsEntity.aidl

package th.how.bean;
parcelable NewsEntity;

IMyAidlInterface.aidl

package th.how.bean;
import th.how.bean.NewsEntity;
import th.how.bean.IClientInterface;

interface IMyAidlInterface {
    NewsEntity getData(int id, String title);
    void register(IClientInterface callback);
    void unRegister(IClientInterface callback);
}

IClientInterface.aidl

package th.how.bean;

interface IClientInterface {
    void callbackClient();
}

RemoteService.java

package th.ipc.aidl;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.support.annotation.Nullable;

import th.how.bean.IClientInterface;
import th.how.bean.IMyAidlInterface;
import th.how.bean.NewsEntity;
import th.how.utils.DpUtils;

/**
 * Created by me_touch on 2017/8/16.
 *
 */

public class RemoteService extends Service{

    private RemoteCallbackList<IClientInterface> mCallback = new RemoteCallbackList<>();

    @Override
    public void onCreate() {
        super.onCreate();
    }

    private final IMyAidlInterface.Stub mBinder = new IMyAidlInterface.Stub() {
        @Override
        public NewsEntity getData(int id, String title) throws RemoteException {
            System.out.println("Server: Thread.currentThread().getName() = " + Thread.currentThread().getName());
            try {
                Thread.sleep(2000);
                int[] value = {10,20,1,5};
                int[] space = {6, 3, 12, 1};
                int value1 = DpUtils.completePack(4, space, value, 11);
                System.out.println("value1 = " + value1);
                callbackClient();
            }catch (Exception e){
                e.printStackTrace();
            }
            return new NewsEntity(id, title);
        }

        @Override
        public void register(IClientInterface callback) throws RemoteException {
            mCallback.register(callback);
        }

        @Override
        public void unRegister(IClientInterface callback) throws RemoteException {
            mCallback.unregister(callback);
        }
    };

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }

    @Override
    public boolean onUnbind(Intent intent) {
        return super.onUnbind(intent);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
    }

    private void callbackClient(){
        int num = mCallback.beginBroadcast();
        for(int i = 0; i < num; i ++){
            IClientInterface iClientInterface = mCallback.getBroadcastItem(i);
            if(iClientInterface != null){
                try{
                    iClientInterface.callbackClient();
                }catch(Exception e){
                    e.printStackTrace();
                }
            }
        }
        mCallback.finishBroadcast();
    }
}

AidlClientActivity

package th.ipc.aidl;

import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

import th.how.app.R;
import th.how.bean.IClientInterface;
import th.how.bean.IMyAidlInterface;
import th.how.bean.NewsEntity;

public class AidlClientActivity extends AppCompatActivity {

    private IMyAidlInterface iService;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_aidl_client);
        Intent intent = new Intent(this, RemoteService.class);
        intent.setAction(RemoteService.class.getName());
        bindService(intent, mConnection, BIND_AUTO_CREATE);
    }

    private IBinder.DeathRecipient deathRecipient = new IBinder.DeathRecipient() {
        @Override
        public void binderDied() {
            try {
                iService.asBinder().unlinkToDeath(deathRecipient, 0);
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                iService = null;
            }

        }
    };

    private IClientInterface client = new IClientInterface.Stub() {
        @Override
        public void callbackClient() throws RemoteException {
            System.out.println("Client: Thread.currentThread().getName() = " + Thread.currentThread().getName());
        }
    };

    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            iService = IMyAidlInterface.Stub.asInterface(service);
            System.out.println("mConnection: Thread.currentThread().getName() = " + Thread.currentThread().getName());
            try {
                service.linkToDeath(deathRecipient, 0);
                iService.register(client);
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            NewsEntity entity = iService.getData(1, "Hello Activity");
                            System.out.println(entity.toString());
                        }catch (Exception e){
                            e.printStackTrace();
                        }
                    }
                }).start();

            }catch (Exception e){
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unbindService(mConnection);
    }
}

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

推荐阅读更多精彩内容