Android进程间通信

1、进程和线程的概念:

在我的理解中,进程是一段被操作系统执行的指令集,操作系统在对资源>进行分配和调度时,进程是基本单位,进程其实就是一个程序。而线程是操作系统调度的最小单元。进程可以包含多个线程,多个线程可共享进程中的资源,另外线程的创建代价比线程要小

2、多进程的应用场景:

通常需要用到多进程的是,有常驻后台的应用,例如音乐播放器的后台播放服务,健身跑步的计算步数和跑步路径模块等,这些模块需要脱离界面运行,而且需要长时间的在系统中运行,所以放在独立的进程中执行是比较好的选择。另外一种就是,如果一些应用很大,需要拆分模块,为了增加应用的内存用量,就需要开启多个进程来扩大应用的内存用量,同时,将应用模块化,也更有利于解耦

3、实现多进程的方式:

在android中实现多进程有两种方式,一种是在AndroidManifest文件中给组件设置android:process属性,这样,这个组件就运行在设置的进程中了,另外一种就是利用jni在native层fork一个新的进程出来

4、android:process属性的设置方式:

设置方式有两种,一种是以“:xxx”的方式设置,这种方式代表该进程是应用的私有进程,其他应用的组件不可在该进程运行,另外一种就是以“xxx.xx.xx”类似包名的方式来设置,这种方式设置的进程是全局的进程,其他应用的组件也可以在该进程上面运行,但是shareUID必须一样才可以

5、在多进程开发中会遇到的问题:

1、静态成员和单例模式完全失效:因为系统会为每个进程分配独立的虚拟机,不同的虚拟机会有不同的内存空间,这会导致不同的虚拟机访问同一个类的对象会产生多个副本,我们修改其中一个副本只会影响当前进程,因此,静态成员和单例模式就会完全失效

2、线程同步机制会完全失效:既然是不同的内存空间,那锁的就不是同一个对象,因此无法保证线程同步

3、Sharedpreferences的可靠性下降:因为SharedPreferences不支持两个进程同时执行写操作

3、Application会被多次创建:不同的进程有不同的虚拟机,当启动一个新的进程时,相当于一个应用的启动过程,那么当然,就会创建一个新的Application

6、使用Serializable时需要注意的问题:

在实现Serializable的过程中,虽然serialVersionUID不写是可以,但是这样可能会造成在反序列化的过程失败,因为这个serialVersionUID在序列化的过程中会被存储进去,在反序列化的时候会先比较对象中的serialVersionUID是否与类中的serialVersionUID相同,如果在类中没有设置这个值,那么系统会自动计算hash值,而如果这个类的结构发生了改变,那么这个自动计算的hash值就会重新计算,就会造成反序列化的失败。因此,设置这个值的意义在于尽量让反序列化可以成功,就算有时候这个类已经发生了部分改变。另外一个就是静态成员不参与序列化,使用transient关键字标记的成员不参与序列化

7、Serializable与Parcelable的区别:

Parcelable是为了Android平台而定制的,它的操作稍微复杂,但是效率很高,因此在内存的序列化上推荐使用Parcelable,而Serializable因为在序列化时需要进行大量的IO操作,因此虽然操作简单,但是开销很大,因此,在将对象序列化到存储设备中或者在网络中传输就推荐使用Serializable

8、实现多进程间通信的方式

实现多进程间的通信有很多种方式,1、利用Bundle进行数据的传递,它简单易用,但是只支持Bundle支持的数据类型,推荐在四大组件的进程通信可以使用它。2、文件共享,这种方式也比较简单易用,但是这种方式不适合多并发的场景,而且无法进行进程间的即时通信。3、Messenger。这种方式的底层是利用AIDL实现的,支持一对多的串行通信,支持低并发的即时通信。3、ContentProvider,ContentProvider的底层利用Binder实现,天生支持跨进程通信,但是它只适合用来作为数据共享。4、Socket,这种方式通过网络传输字节流也可以实现跨进程通信,但实现稍微负责,适合网络间的数据交换。5、AIDL,最后一种,也是最常用的一种方式,功能强大,下面会详细介绍

9、AIDL的一些理解

AIDL(Android Interface Difinition Language)是一种Android种独有的定义语言,它的主要作用是为了简化跨进程通信代码的编写,在编译阶段,我们编写的AIDL文件会生成相应的跨进程代码,简单来说,它就是一种简化的工具,没有AIDL,我们一样可以编写出跨进程代码,只是会稍微繁琐一点

AIDL只会接受基本的数据类型(通过实验short还不行),String和CharSequence类型,然后如果是自定义的对象,需要实现Parcelable接口,并且需要在AIDL文件中声明出来,这样才可以使用,当然也可以用集合(ArrayList,HashMap)作为参数,但是集合中的元素也必须是AIDL支持的类型。另外需要注意的就是AIDL文件中的方法参数,是有一个数据流向的,通过in,out,inout三个标识来确定,如果参数设置为in,那么数据的流向就是客户端->服务器(意思就是客户端传递给服务器的信息,服务器可以收到,但是服务器如果改动了这个传递的对象,客户端中的原对象是不会跟着发生改变的),如果参数设置为out,那么数据的流向就是服务端->客户端(意思就是客户端传递给服务端的参数,服务端是无法收到的,但是服务端改动了这个对象的内容,在客户端的原对象是会跟着改变的),如果参数设置为inout,那就代表着双向流动,但是一般情况不提倡这样设置,因为会增加开销

10、使用AIDL需要注意的地方:

在使用AIDL的过程中,会有一种情形,就是当服务端处理了一些业务,需要主动通知客户端,而不是等待客户端发起请求才去响应,那么这个时候就需要观察者模式了,这个时候我们需要注册监听服务端的状态,这个时候服务端需要保存客户端的监听,因此,可能会有并发的情形,因此推荐使用RemoteCallbackList进行保存。另外就是有时候服务端进程被kill掉,这时候会造成链接失败,因此我们需要通过linkToDeath来绑定服务端,如果服务端进程杀死,客户端可以收到响应,可以进行重连操作。

11、AIDL的实际操作:

在实际开发过程中,我们如果有很多模块都需要用到AIDL的话,那么不可能每个模块开启一个服务进程来处理,这样造成的系统资源损耗是巨大的,因此我们需要利用Binder连接池来作为中间媒介,来链接各个AIDL的处理,下面的例子来演示相关实现

11.1 第一步

首先,创建两个AIDL文件IPlayMedia.aidl和IMonitorDevice.aidl,代码如下:

interface IPlayMedia {
    void play(in String path);
    void puase();
    void stop();
    String getCurrentMusicName();
}

interface IMonitorDevice {
    void monitor(int a);
}

11.2 第二步

实现AIDL接口,代码如下:

// IPlayMedia接口实现
public class PlayMediaImpl extends IPlayMedia.Stub{

    @Override
    public void play(String path) throws RemoteException {
        Log.e("TAG","播放音乐:"+path);
    }

    @Override
    public void puase() throws RemoteException {
        Log.e("TAG","暂停播放");
    }

    @Override
    public void stop() throws RemoteException {
        Log.e("TAG","停止播放");
    }

    @Override
    public String getCurrentMusicName() throws RemoteException {
        Log.e("TAG","获取当前歌曲名称");
        return "七里香";
    }
}

// IMonitorDevice的实现
public class MonitorDeviceImpl extends IMonitorDevice.Stub {

    @Override
    public void monitor(int a) throws RemoteException {
        Log.e("TAG","监控方法实现");
    }
}

11.3 第三步

接下来需要创建一个BinderPool连接池AIDL接口,这个接口是直接与服务端进程的服务交互的

interface BinderPool {
    IBinder queryBinder(int binderCode);
}

11.4 第四步

接下来创建一个服务AIDLService,设置android:process属性,让其可以在独立的进程运行,先不做任何的实现,接着,创建BinderPoolUtils工具类,代码如下:

public class BinderPoolUtils {

    public static final int BINDER_PLAY_MEDIA = 1;
    public static final int BINDER_MONITOR_DEVICE = 2;
    public static volatile BinderPoolUtils mInstance = null;

    private Context mContext;
    private test.com.testpoj.BinderPool mBinderPool;
    private CountDownLatch mConnectBinderPoolDownLatch;

    /**
     * 单例模式
     */
    public static BinderPoolUtils getInstance(Context context) {
        if (mInstance == null) {
            synchronized (BinderPoolUtils.class) {
                if (mInstance == null) {
                    mInstance = new BinderPoolUtils(context);
                }
            }
        }
        return mInstance;
    }

    /**
     * 查询对应的Binder对象
     */
    public IBinder queryBinder(int binderCode) {
        IBinder binder = null;
        try {
            if (mBinderPool != null) {
                binder = mBinderPool.queryBinder(binderCode);
            }
        } catch (RemoteException e) {
            e.printStackTrace();
        }
        return binder;
    }

    /**
     * 构造方法私有化
     */
    private BinderPoolUtils(Context context) {
        mContext = context;
        connectBinderPoolService();
    }

    /**
     * 绑定服务
     */
    private synchronized void connectBinderPoolService() {
        mConnectBinderPoolDownLatch = new CountDownLatch(1);
        Intent intent = new Intent(mContext, AIDLService.class);
        mContext.bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
        try {
            mConnectBinderPoolDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mBinderPool = test.com.testpoj.BinderPool.Stub.asInterface(service);
            try {
                mBinderPool.asBinder().linkToDeath(mBinderPoolDeathRecipient, 0);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
            mConnectBinderPoolDownLatch.countDown();
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

    private IBinder.DeathRecipient mBinderPoolDeathRecipient = new IBinder.DeathRecipient() {
        @Override
        public void binderDied() {
            mBinderPool.asBinder().unlinkToDeath(mBinderPoolDeathRecipient, 0);
            mBinderPool = null;
            connectBinderPoolService();
        }
    };

    /**
     * BinderPool链接池的实现
     */
    public static class BinderPoolImpl extends test.com.testpoj.BinderPool.Stub {

        @Override
        public IBinder queryBinder(int binderCode) throws RemoteException {
            IBinder binder = null;
            switch (binderCode) {
                case BINDER_PLAY_MEDIA:
                    binder = new PlayMediaImpl();
                    break;
                case BINDER_MONITOR_DEVICE:
                    binder = new MonitorDeviceImpl();
                    break;
                default:
                    break;
            }
            return binder;
        }
    }
}

有了上面的工具类,我们就可以在AIDLService中的onBinder方法中返回BinderPoolImpl的实现,然后我们调用的时候就可以这样来调用:

  BinderPoolUtils utils = BinderPoolUtils.getInstance(this);
  IBinder playBinder = utils.queryBinder(BinderPoolUtils.BINDER_PLAY_MEDIA)
  mPlayManager = IPlayMedia.Stub.asInterface(playBinder);

这样我们就可以调用IPlayMedia接口中的方法了

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

推荐阅读更多精彩内容