Android进程间通信之Service篇,Messenger与AIDL

本文Demo见:https://github.com/w1374720640/IPCThroughServices

结合Demo阅读本文效果更好

利用Service进行进程间通信有两种方式,分别是Messenger和AIDL,Messenger底层是基于AIDL的封装,使用更加简洁高效,无需考虑并发问题,只能串行通信,有并发需求的只能用AIDL,不能用Messenger,一般情况下使用Messenger即可满足日常需求。Messenger和AIDL跨进程通信只能传递基本数据类型及实现Parcelable接口的类。

通常提供服务的进程称为服务端,获取服务的称为客户端,客户端通过bindService()的方式绑定服务端,获取IBinder的实例。本文创建了两个项目,包名分别为com.example.servicecom.example.client,对应服务端和客户端,下文不再重复说明。

Messenger

原理

查看Messenger源码可以发现,Messenger包含一个IMessenger的成员变量mTarget,通过mTarget可以向Handler传递Message消息。获取mTarget对象有两种方式,一种是在构造器中利用Handler获取mTarget的实例:

public Messenger(Handler target) {
    mTarget = target.getIMessenger();
}

另一种方法是在构造器中通过IBinder对象获取mTarget的实例:

public Messenger(IBinder target) {
    mTarget = IMessenger.Stub.asInterface(target);
}

注:可能有的同学发现Android sdk中没有IMessenger类,显示红色字体,那是因为IMessenger是一个AIDL文件,完整路径为android.os.IMessenger.aidl,只有一个抽象方法void send(in Message msg);(暂时忽略Message前面的in),AIDL文件编译后会生成同名的Java文件,Android sdk中不包含AIDL文件及编译后生成的临时文件,所以系统找不到IMessenger类,下文会详细介绍相关知识。

服务端创建Messenger对象用第一个构造器,客户端绑定服务端时用第二个构造器获取Messenger对象,两个进程间传递的对象为Messenger对象中的mTarget变量,通过mTarget对象可以跨进程发送Message给Handler:

//Messenger.java
public void send(Message message) throws RemoteException {
    mTarget.send(message);
}

使用方法

模拟客户端从服务端获取一个100以内的随机数。

在服务端新建一个RemoteMessengerService继承Service,并在AndroidManifest.xml中注册隐式启动方式

<service android:name=".RemoteMessengerService">
    <intent-filter>
        <action android:name="com.example.service.RemoteMessengerService"/>
        <category android:name="android.intent.category.DEFAULT"/>
    </intent-filter>
</service>

未指定隐式启动的要加属性android:exported="true"表示可以被其他进程启动,添加<intent-filter>标签后默认为true。

在RemoteMessengerService中创建Handler对象mRemoteHandler,调用Messenger的第一个构造器,利用mRemoteHandler创建Messenger对象:

Messenger mRemoteMessenger = new Messenger(mRemoteHandler);

重写Service的onBind方法,返回Messenger中的mTarget变量:

@Nullable
@Override
public IBinder onBind(Intent intent) {
    return mRemoteMessenger.getBinder();
}

在客户端新建一个MessengerActivity,启动时绑定服务端:

Intent intent = new Intent();
intent.setAction("com.example.service.RemoteMessengerService");
//Android 5.0以上需要设置包名
intent.setPackage("com.example.service");
bindService(intent,mConnection,BIND_AUTO_CREATE);

绑定成功后根据服务端返回的IBinder对象调用Messenger的第二个构造器,创建Messenger对象,通过Messenger对象可以向服务端发送消息:

ServiceConnection mConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        mRemoteMessenger = new Messenger(service);
        isConnect = true;
    }
    @Override
    public void onServiceDisconnected(ComponentName name) {
        isConnect = false;
    }
};

向服务端发送消息:

Message message = Message.obtain();
message.what = 0;
try {
    mRemoteMessenger.send(message);
} catch (RemoteException e) {
   e.printStackTrace();
}

然后在服务端注册的Handler就可以接收到客户端发送的Message了。

一个简单的进程间通信就基本完成了,这时只能由客户端向服务端发送消息,服务端无法向客户端传递数据,要解决这个问题,需要在客户端新建一个Handler及Messenger,在发送消息时将Messenger对象传递给Message的replyTo变量,服务端的Handler收到客户端的Message后,获取replyTo变量,通过获取到的Messenger对象向客户端发送消息。

修改后的客户端代码:

//MessengerActivity.java
Handler mClientHandler = new Handler(){
    @Override
    public void handleMessage(Message msg) {
        switch (msg.what){
            case 0:
                Log.d(TAG,"Client receive message:" + msg.arg1);
                break;
            default:
                break;
        }
    }
};
private Messenger mClientMessenger = new Messenger(mClientHandler);
private void sendMessage(){
    Log.d(TAG,"Client sendMessage()");
    if (!isConnect) return;
    Message message = Message.obtain();
    message.what = 0;
//    将客户端的Messenger对象传递到服务端,
//    不设置则只能单向通信,服务端无法向客户端传递信息
    message.replyTo = mClientMessenger;
    try {
//       向服务端发送消息
        mRemoteMessenger.send(message);
    } catch (RemoteException e) {
        e.printStackTrace();
    }
}

修改后的服务端代码:

//RemoteMessengerService.java
Handler mRemoteHandler = new Handler(){
    @Override
    public void handleMessage(Message msg) {
        Log.d(TAG,"Service receive client message,msg.what=" + msg.what);
        switch (msg.what){
            case 0:
                Message message = Message.obtain();
                message.what = 0;
                Random random = new Random();//获取随机数
                message.arg1 = random.nextInt(100);
//              msg的replyTo变量是客户端生成的Messenger对象
//              如果为空则不能由服务端向客户端传递消息,只能单向通信
                Messenger mClientMessenger = msg.replyTo;
                if (mClientMessenger == null) return;
                try {
//                  向客户端回传信息
                    mClientMessenger.send(message);
                    Log.d(TAG,"Service reply client message,random Num is:" + message.arg1);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
                break;
            default:
                break;
        }
    }
};

先运行服务端,再运行客户端,客户端向服务端发送消息后Log如下(注意包名):

Messenger测试Log.png

AIDL

在Android studio中AIDL文件位于app/src/main/aidl目录下,客户端与服务端AIDL文件相同,包名为服务端包名,只能传递基本数据类型及实现Parcelable接口的类。

AIDL文件实际为模板文件,用于生成复杂但套路固定的Java文件,生成的Java文件位于app/build/generated/source/aidl/debug/<packagemane>目录下,通过Java文件实现IPC通信,生成的Java文件单独使用具有同等效果,有兴趣的可以看看具体的实现,这里不过多讲解。

使用方式

模拟客户端通过用户ID查询用户信息、向服务端添加用户、服务端调用客户端方法实现双向通信。

新建Person类:先设置Person类的成员变量,然后实现Parcelable接口,在类名上alt + enter两次即可快捷生成Parcelable模板代码(Parcelable接口具体使用方式自行google),如下所示:

/*
若Person.java文件放在aidl目录下,需要在app/build.gradle的android标签中添加
   sourceSets {
      main {
        java.srcDirs = ['src/main/java', 'src/main/aidl']
      }
    }
*/
public class Person implements Parcelable{
    private int id;
    private String name;
    private int age;
    private String phone;

    public Person(){
    }

    protected Person(Parcel in) {
        id = in.readInt();
        name = in.readString();
        age = in.readInt();
        phone = in.readString();
    }

    public static final Creator<Person> CREATOR = new Creator<Person>() {
        @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.writeInt(id);
        dest.writeString(name);
        dest.writeInt(age);
        dest.writeString(phone);
    }

    /**
     * 实现Parcelable接口时不会自动创建此方法,
     * 但如果aidl文件中Person类添加了out或inout标签时必须手动实现此方法
     */
    public void readFromParcel(Parcel dest) {
        //注意,此处的读值顺序应当是和writeToParcel()方法中一致的
        id = dest.readInt();
        name = dest.readString();
        age = dest.readInt();
        phone = dest.readString();
    }

    @Override
    public String toString() {
        return "\"id=" + id + ",name=" + name + ",age=" + age + ",phone=" + phone + "\"";
    }

    public int getId() { return id; }
    public void setId(int id) { this.id = id; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public int getAge() { return age; }
    public void setAge(int age) { this.age = age; }
    public String getPhone() { return phone; }
    public void setPhone(String phone) { this.phone = phone; }
}

需要注意的是,模板代码并没有生成readFromParcel()方法,需要我们按照writeToParcel()的写入顺序依次读取参数,若未实现readFromParcel()方法,则aidl中只能用in修饰Person类,不能用outinout修饰,如上文中提到的IMessenger唯一抽象方法void send(in Message msg);。那三个修饰符有什么区别呢?以IMessenger的send方法为例:

  • 如果用in修饰,那msg对象为原对象的副本,msg值的变化不会影响原对象。
  • 如果用out修饰,无论msg传入的值是什么,都会在方法内部创建一个新的对象,方法执行结束会将新对象写入原msg对象,也就是说,无论你输入什么都忽视,结束后再把你的值改掉(够霸道的)。
  • 如果用inout修饰,则会复制输入对象的值,方法执行完后再写入原对象。
  • in修饰的对象执行效率最高,也最常用,outinout因为需要回写,效率较低,尽量少用。
  • 传递基本数据类型、String、aidl文件不用也不能添加标签,默认为in

创建AIDL文件:在服务端app/src/main目录下新建aidl/com/example/service文件夹,然后鼠标点击File>new>AIDL>AIDL File,输入文件名,创建RemoteInterface.aidl文件:

// RemoteAidlInterface.aidl
package com.example.service;

//即使在同一个包下,也需要手动导入类
import com.example.service.ClientCallback;
import com.example.service.Person;

//编译后生成的Java文件在app/build/generated/source/aidl/dubug/<packagename>目录下
//服务端创建,客户端调用
interface RemoteInterface {
//    根据用户ID获取用户信息
    Person getPersonById(int id);
//    添加用户,此处用in修饰
    void addPerson(in Person person);
//    向服务端注册监听
    void registClientCallback(ClientCallback callback);
//    取消注册
    void unRegistClientCallback(ClientCallback callback);
}

ClientCallback.aidl文件如下:

// ClientAidlCallback.aidl
package com.example.service;

//客户端向服务端注册,客户端创建,服务端调用
interface ClientCallback {
//    启动客户端
    void start();
//    停止客户端
    void stop();
}

如果想在进程间传递对象,除了需要实现Parcelable接口外,还需要创建一个aidl文件申明该类可用于进程间通信:

// Person.aidl
package com.example.service;

//注意!不是用interface开头,用parcelable开头,p小写,表示Person类可以进行进程间通信
//用interface开头会生成同名的Java文件,用parcelable开头不会,只有一个Person.java文件
parcelable Person;

创建完成,clean一下工程,如果编译报错说明aidl文件编写有问题,比如是否正确导包(即使包名相同也需要手动导包),parcelable是否是小写,传递序列化对象前必须加in|out|inout标记,重命名文件时其他地方不会自动替换新文件名,总之,编写aidl文件时基本没有任何提示,编译不通过肯定是你aidl文件写的有问题。编译通过后检查一下在app/build/generated/source/aidl/dubug/<packagename>目录下是否生成相应Java文件。

复制aidl目录下文件夹及所有文件到客户端相同目录下,编译。

aidl目录如下图:

aidl目录

编写服务端及客户端代码

在服务端新建RemoteAidlService继承Service,在AndroidManifest.xml文件中注册:

<service android:name=".RemoteAidlService">
    <intent-filter>
        <action android:name="com.example.service.RemoteAidlService"/>
        <category android:name="android.intent.category.DEFAULT"/>
    </intent-filter>
</service>

创建RemoteInterface.Stub对象,RemoteInterface.Stub类是RemoteInterface.aidl文件编译后生成的静态内部类,创建RemoteInterface.Stub对象需要实现RemoteInterface.aidl中定义的抽象方法,重写onBind()方法,返回创建的RemoteInterface.Stub对象:

public class RemoteAidlService extends Service {
    private static final String TAG = "AidlTest";
    private static final int START_ALL_CLIENT = 0;
    private static final int STOP_ALL_CLIENT = 1;
//    模拟服务端存储客户端传递的数据
    private List<Person> mPersonList = new ArrayList<>();
//    一个服务端可以对应多个客户端,即包含多个ClientCallback对象,
//    使用RemoteCallbackList可以在客户端意外断开连接时移除ClientCallback,防止DeadObjectException
    private RemoteCallbackList<ClientCallback> mCallbackList = new RemoteCallbackList<>();
//    通过修改值确定是否在regist后start客户端,默认不启动
    private boolean isAutoStartAfterRegist = false;

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

    @Override
    public void onDestroy() {
        super.onDestroy();
//        服务结束是注意移除所有数据
        mCallbackList.kill();
    }

    /**
     * RemoteInterface.Stub为Android根据aidl文件生成的实现类,
     * 实现了RemoteInterface接口,间接实现了IBinder接口,
     * 客户端绑定时将mRemoteInterface对象返回给客户端,
     * 在服务端定义,在客户端调用
     */
    private RemoteInterface.Stub mRemoteInterface = new RemoteInterface.Stub() {
        @Override
        public Person getPersonById(int id) throws RemoteException {
//            返回固定值
            Person person = new Person();
            person.setId(id);
            person.setName("小红");
            person.setAge(18);
            person.setPhone("120");
            Log.d(TAG, "Service getPersonById()");
            return person;
        }

        @Override
        public void addPerson(Person person) throws RemoteException {
            mPersonList.add(person);
            Log.d(TAG, "Service addPerson(),person="
                    + (person == null ? null : person.toString()));
        }

        @Override
        public void registClientCallback(ClientCallback callback) throws RemoteException {
//            向服务端注册回调
            mCallbackList.register(callback);
            Log.d(TAG, "Service registClientCallback()");
            if (isAutoStartAfterRegist) {
                mHandler.sendEmptyMessageDelayed(START_ALL_CLIENT, 3 * 1000);
            }
        }

        @Override
        public void unRegistClientCallback(ClientCallback callback) throws RemoteException {
//            服务端取消注册回调
            mCallbackList.unregister(callback);
            Log.d(TAG, "Service unRegistClientCallback()");
        }

    };

    Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case START_ALL_CLIENT:
                    startAllClient();
                    mHandler.sendEmptyMessageDelayed(STOP_ALL_CLIENT, 3 * 1000);
                    break;
                case STOP_ALL_CLIENT:
                    stopAllClient();
                    break;
            }
        }
    };

    /**
     * 调用所有客户端的start()方法
     */
    public void startAllClient() {
        Log.d(TAG, "Service startAllClient()");
//        从列表中取数据时先调用beginBroadcast()方法获取总数,循环取出数据后finishBroadcast()
        int size = mCallbackList.beginBroadcast();
        for (int i = 0;i < size;i++){
            try {
                mCallbackList.getBroadcastItem(i).start();
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
        mCallbackList.finishBroadcast();
    }

    /**
     * 调用所有客户端的stop()方法
     */
    public void stopAllClient() {
        Log.d(TAG, "Service stopAllClient()");
        int size = mCallbackList.beginBroadcast();
        for (int i = 0;i < size;i++){
            try {
                mCallbackList.getBroadcastItem(i).stop();
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
        mCallbackList.finishBroadcast();
    }
}

客户端创建AidlActivity,里边有四个按钮,分别对应RemoteInterface.aidl定义的四个方法:getPersonById()、addPerson()、registClientCallback()、unRegistClientCallback(),点击按钮调用相应方法。同时实现了ClientCallback.Stub类,向服务端注册后服务端可以调用客户端相应方法。

public class AidlActivity extends AppCompatActivity implements View.OnClickListener {

    private static final String TAG = "AidlTest";
    private boolean isConnect;

//    服务端的RemoteInterface对象,绑定服务时创建
    private RemoteInterface mRemoteInterface = null;

//    客户端的ClientCallback对象
//    在服务端注册后服务端可以调用客户端方法
    private ClientCallback.Stub mClientCallback = new ClientCallback.Stub() {
        @Override
        public void start() throws RemoteException {
            Log.d(TAG, "Client start");
        }

        @Override
        public void stop() throws RemoteException {
            Log.d(TAG, "Client stop");
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_aidl);
        findViewById(R.id.bt_get).setOnClickListener(this);
        findViewById(R.id.bt_add).setOnClickListener(this);
        findViewById(R.id.bt_regist).setOnClickListener(this);
        findViewById(R.id.bt_unregist).setOnClickListener(this);

        connectService();
    }

    ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            isConnect = true;
//            绑定服务后从服务端获取RemoteInterface对象
            mRemoteInterface = RemoteInterface.Stub.asInterface(service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            isConnect = false;
          //            与服务端意外断开时自动重连
            connectService();
        }
    };

    private void connectService() {
        Intent intent = new Intent();
        intent.setAction("com.example.service.RemoteAidlService");
//        Android 5.0以上需要设置包名
        intent.setPackage("com.example.service");
        bindService(intent, mConnection, BIND_AUTO_CREATE);
    }

    private void disConnectService() {
        unbindService(mConnection);
        isConnect = false;
    }

    @Override
    public void onClick(View v) {
        if (!isConnect) return;
        switch (v.getId()) {
            case R.id.bt_get:
                Person person = getPerson(10);
                Log.d(TAG, "Client getPerson return, person=" +
                        (person == null ? null : person.toString()));
                break;
            case R.id.bt_add:
                Person person1 = new Person();
                person1.setId(100);
                person1.setName("小花");
                person1.setAge(16);
                person1.setPhone("110");
                addPerson(person1);
                break;
            case R.id.bt_regist:
                registCallback();
                break;
            case R.id.bt_unregist:
                unRegistCallback();
                break;
        }
    }

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

    /**
     * 从服务端获取数据
     */
    private Person getPerson(int id) {
        Log.d(TAG,"Client getPerson()");
        if (!isConnect) return null;
        Person person = null;
        try {
            person = mRemoteInterface.getPersonById(id);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
        return person;
    }

    /**
     * 向服务端添加数据
     */
    private void addPerson(Person person) {
        Log.d(TAG,"Client addPerson()");
        if (!isConnect) return;
        try {
            mRemoteInterface.addPerson(person);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    /**
     * 向服务端注册回调,注册后服务端才能调用客户端方法
     */
    private void registCallback() {
        Log.d(TAG,"Client registCallback()");
        if (!isConnect) return;
        try {
            mRemoteInterface.registClientCallback(mClientCallback);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    /**
     * 取消注册
     */
    private void unRegistCallback() {
        Log.d(TAG,"Client unRegistCallback()");
        if (!isConnect) return;
        try {
            mRemoteInterface.unRegistClientCallback(mClientCallback);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }
}

先运行服务端,再运行客户端,分别点击客户端的四个按钮,观察Log输出如下

getPersonById()
addPerson()
registCallback()
unRegistCallback()

将的RemoteAidlService的isAutoStartAfterRegist属性改为true后点击注册按钮,Log如下,注册后3秒自动调用客户端的start()方法,再3秒后调用客户端的stop()方法。

isAutoStartAfterRegist=true

结语

Demo见顶部链接,文章参考:

Android:学习AIDL,这一篇文章就够了(上)

你真的理解AIDL中的in,out,inout么?

Android进程间通信之----Aidl传递对象

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

推荐阅读更多精彩内容