Android进程间通信(IPC)常用方式

进程间通信方式

Android开发中我们可以通过IntentContentProviders来实现进程间通信,如果不限于Android特有的话,我们还可以使用FileSocket等方式,反正只要进程间能交换信息就行了。

Intent,我们平时使用的时候好像都没感觉出是在进程间通信。其实Android中进程间的通信是非常频繁的,应用里打开一个新的Activity都涉及到了进程间通信,应用里调用打电话、调用浏览器等等都涉及到了。

实际上IntentContentProviders都是对Binder更高级别的抽象,方便我们平时使用。

常用方式

上面说到的一些方式都是系统经过高度封装的,而我们的业务需求可能比较特别,使用上面的方式可能不是特别适合,比如:“我们的音乐播放器希望在独立的进程中播放音乐”。

我们至少得控制音乐的开始、暂停、显示进程这些功能吧,那就需要进程间的通信了。这个时候使用系统经过高度封装的方式都好像显得不太灵活。根据官方文档我们发现有两种相对底层一些的方式,MessengerAIDL

在相对底层一点的进程间通信,Messenger是最简单的方式,Messenger会在单一线程中创建包含所有请求的队列,这样我们就不需要处理线程安全方面的事宜。

Messenger实际上是以AIDL作为其底层结构的。

Messenger的用法

  • 单向通信

    客户端进程相关代码:

public class MainActivity extends AppCompatActivity {

    private Messenger mService = null;

    // 绑定远程服务成功后相应回调方法
    private ServiceConnection mServiceConnection = new ServiceConnection() {

        // 绑定成功后会调用该方法
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {

            mService = new Messenger(service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            mService = null;
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Intent intent = new Intent(this, RemoteService.class);
        // 绑定服务
        bindService(intent, mServiceConnection, BIND_AUTO_CREATE);
    }

    /**
     * 点击按钮向远程服务发送消息
     * @param view
     */
    public void onClick(View view) {
        // 获取一个what值为0的消息对象
        Message msg = Message.obtain(null, 0);
        try {
            // 将消息对象通过Messenger传递到远程服务器
            mService.send(msg);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void onStop() {
        super.onStop();

        unbindService(mServiceConnection);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52

布局XML代码就不贴了,很简单,就一个按钮。

上面的代码也很简单,就得我们平常绑定服务的做法是一样的,唯一的区别就是在绑定成功回调方法onServiceConnected()中我们根据返回的IBinder实例化了一个Messenger对象,当我们点击按钮的时候,通过该Messenger对象发送一个消息到远程服务端。

扫描二维码关注公众号,回复: 1143980 查看本文章

服务端进程代码:

/**
 * 远程服务端
 */
public class RemoteService extends Service {

    // 用来处理客户端传过来的消息
    class ServerSideHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case 0:
                    Log.e("Fiend", "我是远程服务端,我收到客户端传递过来的信息了。");
                    break;
            }
        }
    }

    // 实例化一个Messenger对象,并传入Handler
    final Messenger mMessenger = new Messenger(new ServerSideHandler());

    /**
     * 客户端绑定服务端的时候将调用该方法
     * @param intent
     * @return
     */
    @Override
    public IBinder onBind(Intent intent) {
        return mMessenger.getBinder();
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

服务端的代码也很简单,创建一个Handler来处理消息,实例化一个Messenger与该Handler关联,最后通过onBind()方法将MessengerIBinder给返回。在客户端通过该IBinder重建一个Messenger

我们来看一下运行结果:

调用远程结果

确实成功了,而且也确实是在两个进程间。想要让服务运行在别的进程只需要声明的时候指定它的android:process属性就可以了。

但是我们只是客户端向服务端发送了信息,那服务端如何向客户端发送信息呢?

  • 双向通信

    客户端改动地方:

/**
     * 点击按钮向远程服务发送消息
     * @param view
     */
    public void onClick(View view) {
        // 获取一个what值为0的消息对象
        Message msg = Message.obtain(null, 0);

        // 将客户端的Messenger对象放到消息中传递到服务端
        msg.replyTo = new Messenger(new Handler() {
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what) {
                    case 1:
                        Log.e("Fiend", "我是客户端,收到服务端的回复了");
                        break;
                }
            }
        });

        try {
            // 将消息对象通过Messenger传递到远程服务器
            mService.send(msg);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

客户端代码只需要在发送消息之前将本地的一个Messenger对象放到消息里一起传递到远程服务端即可。

服务端改动地方:

    // 用来处理客户端传过来的消息
    class ServerSideHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case 0:
                    Log.e("Fiend", "我是远程服务端,我收到客户端传递过来的信息了。");
                    try {
                        // 通过客户端的Messenger回复一个what值为1的消息
                        msg.replyTo.send(Message.obtain(null, 1));
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                    break;
            }
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

服务端改动的代码也非常简单,只是在收到客户端消息的时候, 通过客户端的Messenger回复一个消息。这样就实现了本地客户端与远程服务端的通信了。

对于大多数的应用来说,Messenger就能满足IPC的需求了,完全没必要使用AIDL,而且MessengerAIDL简单得多。如果对于服务需要执行多线程处理的,则应使用AIDL,否则使用Messenger就可以了。

AIDL的用法

使用AIDL和使用Messenger的步骤基本上是类似的。使用AIDL需要自己定义好一个接口作为客户端和服务端通信的规则,手工写一个这样的接口比较复杂,所以Android给我们提供了一个工具来自动生成。

想要自动生成通信的接口,则需要创建一个以.aidl结尾的文件,然后按平常我们定义接口的方式做就好了。下面以Android Studio来讲解生成过程。

  1. 新建一个项目,名字随便取:AIDLExample
  2. 将工程目录结构以Android的形式展示: 
    切换视图
  3. 点击项目,右键,新建一个AIDL文件: 
    newaidl
  4. 打开新建的AIDL文件IMyAidlInterface.aidl,编写通信规则: 
    AIDL内容
  5. 编写完IMyAidlInterface.aidl后,需要重新Build一下项目,然将工程目录结构以Project的形式展示,就可以找到生成的真正接口: 
    这里写图片描述

至此AIDL接口就定义好了,剩下的步骤比较简单,和之前讲过的类似。

我们先来编写服务端,直接新建一个Service并在配置文件中将其配置为android:process=":remote",确保它运行在另一个进程中。

// 服务端
public class MyService extends Service {

    IMyAidlInterface.Stub iBinder = new IMyAidlInterface.Stub() {
        // 我们在aidl文件中定义的通信规则
        @Override
        public String getMsg() throws RemoteException {
            return "我来自MyService";
        }
    };

    @Override
    public IBinder onBind(Intent intent) {
        return iBinder;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

代码很简单,在绑定的时候将带有我们自己定义的规则的IBinder返回给客户端。XXX.Stub iBinder = new XXX.Stub() {···}这样的写法是固定的,记住就好了,将XXX替换成你的AIDL接口名称就可以了。

我们来看一下客户端代码:

// 客户端
public class MainActivity extends AppCompatActivity {

    private IMyAidlInterface mService;
    private boolean isBound;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 绑定服务端
        Intent intent = new Intent(this, MyService.class);
        bindService(intent, mServiceConnection, BIND_AUTO_CREATE);

    }

    // 绑定回调
    private ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            // 获取AIDL接口对象,这样就可以用来通信了
            mService = IMyAidlInterface.Stub.asInterface(service);
            isBound = true;
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            mService = null;
            isBound = false;
        }
    };

    // 按钮点击回调方法
    public void btnClick(View view) {
        if (isBound) {
            try {
                // 调用服务端方法
                String result = mService.getMsg();
                Log.e("Fiend", "客户端:" + result);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        } else {
            Log.e("Fiend", "还没有绑定成功");
        }
    }

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

        if (isBound) {
            unbindService(mServiceConnection);
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57

看起来代码有点多,其实并没有什么陌生的内容,都是我们平时非常熟悉的一些代码。应用启动后就绑定远程服务端,点击按钮调用远程服务端的方法,获取到后将结果打印出来。结果如下:

result

成功调用另一个进程中的方法。

onServiceConnected()方法里的这句代码mService = IMyAidlInterface.Stub.asInterface(service);,属于固定写法,和之前的服务端写法一样,记住就好了。

上面这种IPC方式是属于同步的,所谓同步是指,客户端调用后会等待服务端返回后才会继续向下执行。我们来修改一下客户端代码:

    public void btnClick(View view) {
        if (isBound) {
            try {
                Log.e("Fiend", "开始调用服务端方法");
                // 调用服务端方法
                String result = mService.getMsg();
                Log.e("Fiend", "客户端:" + result);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        } else {
            Log.e("Fiend", "还没有绑定成功");
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

没有改什么实质性的,只是在调用服务端方法之前打印了一个Log,方便我们之前对比时间用。

改一下服务端的代码:

    IMyAidlInterface.Stub iBinder = new IMyAidlInterface.Stub() {
        // 我们在aidl文件中定义的通信规则
        @Override
        public String getMsg() throws RemoteException {
            // 5秒后再返回结果
            SystemClock.sleep(5 * 1000);
            return "我来自MyService";
        }
    };
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

同样没有修改多少,只是延迟5秒再返回结果。我们来看一下打印结果:

time

从截图可以看出,客户端确实等服务端返回后再继续执行的,所以是同步。因此,时刻记住客户端调用的时候在工作线程调用,否则有可能阻塞主线程。那想要异步该如何做?

异步调用

想要以AIDL方式异步调用,需要用到关键字oneway,它可以作用在接口上也可以作用在方法上。异步方法必须返回void

  • 异步接口
// 所有方法都是异步的
oneway interface IAsynchronousInterface {
    void method1();
    void method2();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 异步方法
interface IAsynchronousInterface {
    // 这个方法是异步执行的
    oneway void method1();
    void method2();
}
  • 1
  • 2
  • 3
  • 4
  • 5

异步已经可以了,那结果如何返回呢?通常异步都是以回调接口的方式,在这里也是一样的。我们修改上面的之前演示的示例,增加一个回调接口,方便服务端调用客户端的方法,也就是所谓的反向调用。

增加一个回调AIDL接口定义:

callback

增加回调接口必须重新建立一个.aidl结尾的文件,IMyAidlInterfaceCallback.aidl具体内容如下:

// 用于服务端回调
interface IMyAidlInterfaceCallback {

    // 结果处理
    void handleResult(String result);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

修改IMyAidlInterface.aidl的内容:

import com.fiend.aidlexample.IMyAidlInterfaceCallback;

// 和我们平常定义一个接口语法一样
oneway interface IMyAidlInterface {

    // 定义了一个方法(所谓的通信规则)
    void getMsg(IMyAidlInterfaceCallback callback);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

getMsg()方法的返回改为void,并将新定义的回调接口作为参数。这里必须显示import接口,否则编译会报错。

修改服务端部分代码:

    IMyAidlInterface.Stub iBinder = new IMyAidlInterface.Stub() {
        @Override
        public void getMsg(IMyAidlInterfaceCallback callback) throws RemoteException {
            // 5秒后再返回结果
            SystemClock.sleep(5 * 1000);
            // 通过回调接口返回结果
            callback.handleResult("我是异步返回,我来自MyService");
        }
//        // 我们在aidl文件中定义的通信规则
//        @Override
//        public String getMsg() throws RemoteException {
//            // 5秒后再返回结果
//            SystemClock.sleep(5 * 1000);
//            return "我来自MyService";
//        }
    };
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

注释掉的部分是我们之前的做法,现在是通过回调接口返回结果。

修改客户端部分代码:

    /**
     * 回调接口
     */
    private IMyAidlInterfaceCallback.Stub mCallback = new IMyAidlInterfaceCallback.Stub() {
        @Override
        public void handleResult(String result) throws RemoteException {
            Log.e("Fiend", "客户端:" + result);
        }
    };

    public void btnClick(View view) {
        if (isBound) {
            try {
                Log.e("Fiend", "开始调用服务端方法");
//                String result = mService.getMsg();
//                Log.e("Fiend", "客户端:" + result);
                // 调用服务端方法,将回调接口传过去
                mService.getMsg(mCallback);
                Log.e("Fiend", "结束调用服务端方法");
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        } else {
            Log.e("Fiend", "还没有绑定成功");
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

增加了一个回调接口,修改了调用服务端方法,之前是调用getMsg()并返回结果,现在是调用getMsg(mCallback)把回调方法传过去,没有返回值。返回结果在回调接口中处理。

我们来看一下运行结果:

result

通过返回时间对比,可以看到,调用完远程服务方法就立刻返回了。而需要返回的数据是在5秒后通过回调接口返回的。

至此,我们就实现了AIDL方式的异步调用了。

AIDL支持的数据类型

AIDL默认支持这么几种数据类型:

  • Java基本数据类型,如intlongboolean等(除了short)
  • String类型
  • CharSequence
  • List类型,所有List中的元素必须是AIDL支持的类型,如List<String>
  • Map类型,所有Map中的元素必须是AIDL支持的类型,如Map<String, Integer>

ListMap的接收方类型必须为ArrayListHashMap

如果默认的类型不能满足你的需要,还可以自定义类型,自定义类型必须支持序列化,也就是实现Parcelable接口。具体可以参考官网

以上我们介绍的AIDL用法都是在同一个工程里,只是将Service指定运行在了不同的进程中,因此我们的.aidl文件可以只写一份,但是,如果我们的Service是在另一个应用(apk)中,那么另一个应用中也必须有和我们项目中相同的.aidl文件,连包名也必须一样。

总结

Android中实现进程间通信在高层次抽象可以很方便的使用Intent等方式来操作,相对底层的方式我们可以使用MessengerAIDL,大多数情况下我们使用Messenger就可以达到我们想要的效果了,而且使用也比AIDL简单,所以尽量用Messenger,实在不行再考虑AIDL,在介绍AIDL的时候对于支持的数据类型并没有深入的讲解与演示,可以上官网看看。

参考文献

  • 官方文档
  • Android Binder by Thorsten Schreiber from Ruhr-Universität Bochum
  • Deep Dive into Android IPC/Binder Framework at Android Builders Summit 2013
  • Efficient Android Threading by Anders Goransson
版权声明:本文为博主原创文章,转载请注明出处。 https://blog.csdn.net/fiendvip/article/details/50954393

猜你喜欢

转载自blog.csdn.net/ma969070578/article/details/80476117