Android中的IPC方式

作者: ScottStone | 来源:发表于2018-11-28 14:43 被阅读24次

    IPC是Inter-ProcessCommunication的缩写,进程间通信、跨进程通信,是指两个进程之间进行数据交换的过程。说起进程间通信,我们首先脑补下什么是进程,什么是线程,进程和线程是截然不同的概念。按照操作系统中的描述,线程是CPU调度的最小单元,同时线程是一种有限的系统资源。而进程一般指一个执行单元,在PC和移动设备上指一个程序或者一个应用。一个进程可以包含多个线程,因此进程和线程是包含与被包含的关系。最简单的情况下,一个进程中可以只有一个线程,即主线程,在Android里面主线程也叫UI线程,在UI线程里才能操作界面元素。通常情况下一个进程中需要执行大量耗时的任务,如果这些任务放在主线程中去执行就会造成界面长时间无法响应,严重影响用户体验,这种情况在PC系统和移动系统中都存在,在Android中有一个特殊的名字叫做ANR(ApplicationNotResponding),即应用无响应。解决这个问题就需要用到线程,把一些耗时的任务放在线程中即可。

    对于Android来说,它是一种基于Linux内核的移动操作系统,它的进程间通信方式并不能完全继承自Linux,它有自己的进程间通信方式。在Android中最有特色的进程间通信方式就是Binder了,通过Binder可以轻松地实现进程间通信。除了Binder,Android还支持Socket,通过Socket也可以实现任意两个终端之间的通信,当然同一个设备上的两个进程通过Socket通信自然也是可以的。

    具体IPC方式有很多,比如可以通过在Intent中附加extras来传递信息,或者通过共享文件的方式来共享数据,还可以采用Binder方式来跨进程通信,另外,ContentProvider天生就是支持跨进程访问的,因此我们也可以采用它来进行IPC。此外,通过网络通信也是可以实现数据传递的,所以Socket也可以实现IPC。下面就具体介绍几种主要的IPC方式。

    1.使用Bundle

    四大组件中的三大组件(Activity、Service、BroadcastReceiver)都是支持在Intent中传递Bundle数据的,由于Bundle实现了Parcelable接口,所以它可以方便地在不同的进程间传输。这里传输的数据必须可以被序列化,比如基本数据类型和实现了Parcelable、Serializable接口的类都可以传输。

    2.使用文件共享

    这个看起来比较好理解,就是在SD卡中存储个文件用来共享,但是这个明显的有并发问题。众所周知,SharedPreferences是Android提供的一个轻量级的存储方案,在xml中以键值对的形式存储。同样的,在多进程的情况下,读写并不是可靠的。

    3.使用Messenger

    Messenger很多地方翻译为信使,这个也算贴切。Messenger是一中轻量级的IPC方式,底层实现也是基于AIDL。

    Messenger的使用方法很简单,它对AIDL做了封装,使得我们可以更简便地进行进程间通信。同时,由于它一次处理一个请求,因此在服务端我们不用考虑线程同步的问题,这是因为服务端中不存在并发执行的情形。实现一个Messenger有如下几个步骤,分为服务端和客户端。

    A.服务端进程首先,我们需要在服务端创建一个Service来处理客户端的连接请求,同时创建一个Handler并通过它来创建一个Messenger对象,然后在Service的onBind中返回这个Messenger对象底层的Binder即可。

    B.客户端进程客户端进程中,首先要绑定服务端的Service,绑定成功后用服务端返回的IBinder对象创建一个Messenger,通过这个Messenger就可以向服务端发送消息了,发消息类型为Message对象。如果需要服务端能够回应客户端,就和服务端一样,我们还需要创建一个Handler并创建一个新的Messenger,并把这个Messenger对象通过Message的replyTo参数传递给服务端,服务端通过这个replyTo参数就可以回应客户端。这听起来可能还是有点抽象,不过看了下面的两个例子,读者肯定就都明白了。首先,我们来看一个简单点的例子,在这个例子中服务端无法回应客户端。

    /**
     * Created by Stone on 2018/8/16.
     */
    public class MessengerService extends Service {
    
    
        private static final String TAG = "MessengerService";
    
        private static class MessengerHandler extends Handler
    
        {
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what) {
                    case 1:
                        Log.i(TAG, "receive msg from Client:" + msg.getData().getString("msg"));
                        
                        break;
                    default:
                        super.handleMessage(msg);
                }
            }
        }
    
        private final Messenger mMessenger = new Messenger(new MessengerHandler());
    
        @Nullable
        @Override
        public IBinder onBind(Intent intent) {
            return mMessenger.getBinder();
        }
    }
    
    public class MessengerActivity extends AppCompatActivity {
    
        private static final String TAG = " MessengerActivity";
        private TextView tvResult;
        private Messenger mService;
        private ServiceConnection mConnection = new ServiceConnection() {
            public void onServiceConnected(ComponentName className, IBinder service) {
                mService = new Messenger(service);
                Message msg = Message.obtain(null, 1);
                Bundle data = new Bundle();
                data.putString("msg", " hello, this is client.");
                msg.setData(data);
                try {
                    mService.send(msg);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
            public void onServiceDisconnected(ComponentName className) {
            }
        };
        
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_messenger);
            tvResult = findViewById(R.id.tv_result);
            Intent intent = new Intent(this, MessengerService.class);
            bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
        }
    
        @Override
        protected void onDestroy() {
            unbindService(mConnection);
            super.onDestroy();
        }
    }
    

    问题来了,实际使用中不可能只有客户端访问服务端,还有服务端响应客户端,那我们怎么改进下呢?直接上代码:

    /**
     * Created by Stone on 2018/8/16.
     */
    public class MessengerService extends Service {
    
    
        private static final String TAG = "MessengerService";
    
        private static class MessengerHandler extends Handler
    
        {
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what) {
                    case 1:
                        Log.i(TAG, "receive msg from Client:" + msg.getData().getString("msg"));
                        Messenger client = msg.replyTo;
                        Message relpyMessage = Message.obtain(null, 2);
                        Bundle bundle = new Bundle();
                        bundle.putString(" reply", "消息已收到");
                        relpyMessage.setData(bundle);
                        try {
                            client.send(relpyMessage);
                        } catch (RemoteException e) {
                            e.printStackTrace();
                        }
                        break;
                    default:
                        super.handleMessage(msg);
                }
            }
        }
    
        private final Messenger mMessenger = new Messenger(new MessengerHandler());
    
        @Nullable
        @Override
        public IBinder onBind(Intent intent) {
            return mMessenger.getBinder();
        }
    }
    
    public class MessengerActivity extends AppCompatActivity {
    
        private static final String TAG = " MessengerActivity";
        private TextView tvResult;
        private Messenger mService;
        private ServiceConnection mConnection = new ServiceConnection() {
            public void onServiceConnected(ComponentName className, IBinder service) {
                mService = new Messenger(service);
                Message msg = Message.obtain(null, 1);
                Bundle data = new Bundle();
                data.putString("msg", " hello, this is client.");
                msg.setData(data);
                //添加处理回复的Messenger
                msg.replyTo = mGetReplyMessenger;
                try {
                    mService.send(msg);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
            public void onServiceDisconnected(ComponentName className) {
            }
        };
        private Messenger mGetReplyMessenger = new Messenger(new MessengerHandler());
        private  class MessengerHandler extends Handler {
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what) {
                    case 2:
                        Log.i(TAG, " receive msg from Service:" + msg.getData().getString(" reply"));
                        tvResult.append("\n receive msg from Service:" + msg.getData().getString(" reply"));
                        break;
                    default:
                        super.handleMessage(msg);
                }
            }
        }
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_messenger);
            tvResult = findViewById(R.id.tv_result);
            Intent intent = new Intent(this, MessengerService.class);
            bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
        }
    
        @Override
        protected void onDestroy() {
            unbindService(mConnection);
            super.onDestroy();
        }
    }
    

    从上面代码可以看到,在收到客户端的消息后,服务端把返回的消息放到了msg.replyTo中,这也是一个Messenger,具体接受的Messenger在MessengerActivity中定义,当然也是一组完整的Messenger跟Handler,具体可以看代码。这里IPC的类型必须是Message支持的类型。最后给出Messenger的工作原理:

    Messenger原理

    4.使用AIDL

    简单的使用可参考Android Binder---AIDL ,这里接着Android Binder---AIDL介绍的AIDL继续往下写。考虑到用户每次都是访问getStudent方法比较费劲,如果StudentList有更新再通知用户去获取岂不是很省力?这里就要用到观察者模式了。我们创建一个新的AIDL接口,这个用来监听StudentList的变化。

    // IOnNewStudentArrivedListener.aidl
    package com.stone.demoandroid.entity;
    import com.stone.demoandroid.entity.Student;
    // Declare any non-default types here with import statements
    
    interface IOnNewStudentArrivedListener {
        /**
         * Demonstrates some basic types that you can use as parameters
         * and return values in AIDL.
         */
        void onNewStudentArrived(in Student newStudent);
    }
    

    同样的,之前的Manager也要有所变化添加两个方法:registerListener、unregisterListener

    // IStudentManager.aidl
    package com.stone.demoandroid.entity;
    
    // Declare any non-default types here with import statements
    import com.stone.demoandroid.entity.Student;
    import com.stone.demoandroid.entity.IOnNewStudentArrivedListener;
    interface IStudentManager {
        /**
         * Demonstrates some basic types that you can use as parameters
         * and return values in AIDL.
         */
    
        List<Student> getStudentList();
        void addStudent(in Student student);
        void registerListener( IOnNewStudentArrivedListener listener);
        void unregisterListener( IOnNewStudentArrivedListener listener);
    }
    

    Service跟Activity的代码太长了就不全部贴出来了,有兴趣的同学可以直接去Demo下载查看。这里有一点需要提一下,CopyOnWriteArrayList不能处理跨进程的Listener,需要用RemoteCallbackList,因为CopyOnWriteArrayList在服务端使用的Listener并不是客户端定义的那个,看名字也知道只是一个Copy。还有就是并没有处理ANR和Binder意外死亡的问题,有兴趣额同学可以处理下。

    5.使用ContentProvider

    ContentProvider是Android中提供的专门用于不同应用间进行数据共享的方式,从这一点来看,它天生就适合进程间通信。和Messenger一样,ContentProvider的底层实现同样也是Binder,由此可见,Binder在Android系统中是何等的重要。虽然ContentProvider的底层实现是Binder,但是它的使用过程要比AIDL简单许多,这是因为系统已经为我们做了封装,使得我们无须关心底层细节即可轻松实现IPC。具体的实现这里就不赘述了,四大组件之一,用法一搜一堆,有兴趣的可以看下Demo

    6.使用Socket

    Socket也称为“套接字”,是网络通信中的概念,它分为流式套接字和用户数据报套接字两种,分别对应于网络的传输控制层中的TCP和UDP协议。本科的时候选修过JavaSocket,作业有实现TCP和UDP协议,考试竟然是上机,自己实现一个局域网的聊天工具。

    具体的实现,大家可以看下Demo

    选择合适的IPC(任玉刚. Android开发艺术探索. 电子工业出版社. ),这里借大佬总结的帖在这里希望对大家有帮助:

    image

    主要的几个IPC的方式就简单介绍到这里,有什么问题可以留言。

    相关文章

      网友评论

        本文标题:Android中的IPC方式

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