Android中的IPC方式

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的方式就简单介绍到这里,有什么问题可以留言。

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

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,900评论 25 707
  • 用两张图告诉你,为什么你的 App 会卡顿? - Android - 掘金 Cover 有什么料? 从这篇文章中你...
    hw1212阅读 12,708评论 2 59
  • 使用Bundle 四大组件中的三大组件(Activity、Service、Receiver)都是支持在Intent...
    最最最最醉人阅读 625评论 0 3
  • 灰蒙蒙的天空,灰蒙蒙的心,我以为也就如此吧! 似风,似云,似雨,似雪,你模糊的背影! 三千六百五十个轮回,我终于等...
    烟雨香茗阅读 175评论 2 4
  • 国庆长假,我在纠结中踏上了前往“有福之州”——福州的列车。 离开牙牙学语的稚子,...
    汐_3dd8阅读 294评论 0 0