美文网首页Android开发经验谈Android开发Android技术知识
Android进程间通信之Service篇,Messenger与

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

作者: cx666 | 来源:发表于2017-07-31 00:34 被阅读0次

    本文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传递对象

    相关文章

      网友评论

        本文标题:Android进程间通信之Service篇,Messenger与

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