【漫画技术】Android跨进程通信



类型 描述 用时
选题 silencezwm 0.5小时
写作时间 2017年10月25日 5.5小时
审稿 silencezwm、Mya婷婷 2小时
校对上线 silencezwm 1小时

Tips:4个环节,共计约9小时的精心打磨完成上线,同时也非常感谢参与审稿的同学。


你好,欢迎来到【漫画技术】栏目
让你看的开心,学的舒心

看漫画,涨薪资(¥) >>>【小猪的传奇一生】:

小猪的传奇一生

该漫画描述了小猪仔出生后,就生活在猪圈中,快乐的成长着。有一天,小猪长大了,屠宰场的老板就会通过船将肥猪运输过来进行屠宰,然后将猪肉销往世界各地。

看完该漫画后,是不是觉得小猪仔的一生有点小悲凉,要怪就怪可恶的人类,无肉不欢,哈哈。

精彩的漫画背后,总隐藏着一丝丝技术的小知识。本文将为你介绍“Android跨进程通信”的相关知识,通过本文的学习,你可以了解到:

一、单进程通信与多进程通信之间的区别

二、跨进程通信常见的五种实现方式

三、跨进程通信的注意事项

一、单进程通信与多进程通信之间的区别

概念普及:IPC(Inter-Process Communication)机制,即进程间通信或者跨进程通信机制,是指两个进程之间进行数据交换的过程。

1.1、单进程通信

在很久以前,小猪仔从生到死都是在猪圈中生活的,没有外来者的入侵。同样的,在Android开发中,默认情况下,程序运行后,都只是运行在一个以项目包名创建的主进程中(就好比猪圈),例如

项目包名为:com.silencezwm.ipcdemo
默认进程名:com.silencezwm.ipcdemo

单进程通信就如:不同品类的猪仔(Android中不同的组件)在相同的猪圈(在同一进程中)中生活(运行)。

1.2、多进程通信

突然有一天,河流的右边来了一个商人,他发现河对岸有不少肥猪在游荡。于是,他发现了一个发家致富的机会,他在这里建了一个屠宰场。然后经常通过船将对岸的肥猪运送过来,进行屠宰,赚的盆满钵满。

这个就类似Android程序,本来只有一个进程在运行,但是因为产品提了个奇葩的需求,使得我们程序猿们不得不多开一个进程来实现该需求。

宝宝不开心

程序猿们一顿牢骚后,最后还是动手干活了。

他们在AndroidManifest.xml文件中相应的组件添加了android:process属性,并指定进程名。然后打开了两个Activity,但是BActivity被指定运行在新的进程,当程序跑起来后,此时可以看到有两个进程正在运行,如图:


两个进程正在运行

我们知道,在单进程中通信,组件间是可以随意进行通信,因为它们都处于同一个内存空间。那多进程之间是怎样通信的呢?

漫画中,屠宰场的老板通过船将河对岸的肥猪运送过来,就因为船的存在,该老板就可以跨越河流到达对岸。那么,Android跨进程通信中,我们也需要拥有同样功能的船,它就是Binder,通过Binder的中转,进程之间就能顺利的进行数据交换了。


跨进程通信

二、跨进程通信常见的五种实现方式

五种常见的实现方式可分为两大类:四大组件的跨进程通信和AIDL。

2.1、四大组件

Activity、Service、BroadcastReceiver、Content Provider四大组件只需要在AndroidManifest.xml文件相应的组件中添加android:process属性,并指定进程名。程序运行起来后,它们就会运行在不同的进程中,它们之间的通信,官方已经给我们做了非常好的封装,所以使用起来也非常方便,这里就不多做解释了。

2.2、AIDL

概念普及:AIDL(Android interface definition Language),即Android接口定义语言。

要想应用AIDL技术,就至少需要有两个进程存在,A进程通过定义的AIDL接口文件与B进程进行通信,具体的实现步骤如下:

1、准备两个进程:新建一个项目 IPCDemo,然后新建一个Module IPCClient,这样我们就准备好了两个进程,完成后的项目结构如图:


两个进程项目结构

2、创建AIDL文件:在服务端IPCDemo中新建一个AIDL接口文件:IMyAidlInterface.aidl,其中会默认实现basicTypes方法,然后我们再定义一个login方法,IMyAidlInterface.aidl完整代码如下;

package com.silencezwm.ipcdemo;

/**
 *  定义的AIDL接口文件
 */
interface IMyAidlInterface {
    /**
     *  AIDL默认实现的方法
     */
    void basicTypes(int anInt, long aLong, boolean aBoolean, float                     aFloat,double aDouble, String aString);

    /**
     *  定义了一个登录方法,包含用户名、密码两个参数
     */
    void login(String username, String password);

}

3、build项目:此时,Android Studio会自动为你生成一个继承自IInterface的java文件,IMyAidlInterface.java完整代码如下;

package com.silencezwm.ipcdemo;

/**
 * 定义的AIDL接口文件
 */
public interface IMyAidlInterface extends android.os.IInterface {
    /**
     * Local-side IPC implementation stub class.
     */
    public static abstract class Stub extends android.os.Binder implements com.silencezwm.ipcdemo.IMyAidlInterface {
        private static final java.lang.String DESCRIPTOR = "com.silencezwm.ipcdemo.IMyAidlInterface";

    /**
     * Construct the stub at attach it to the interface.
     */
    public Stub() {
        this.attachInterface(this, DESCRIPTOR);
    }

    /**
     * Cast an IBinder object into an   com.silencezwm.ipcdemo.IMyAidlInterface interface,
     * generating a proxy if needed.
     */
    public static com.silencezwm.ipcdemo.IMyAidlInterface asInterface(android.os.IBinder obj) {
        if ((obj == null)) {
            return null;
        }
        android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
        if (((iin != null) && (iin instanceof com.silencezwm.ipcdemo.IMyAidlInterface))) {
            return ((com.silencezwm.ipcdemo.IMyAidlInterface) iin);
        }
        return new com.silencezwm.ipcdemo.IMyAidlInterface.Stub.Proxy(obj);
    }

    @Override
    public android.os.IBinder asBinder() {
        return this;
    }

    @Override
    public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
        switch (code) {
            case INTERFACE_TRANSACTION: {
                reply.writeString(DESCRIPTOR);
                return true;
            }
            case TRANSACTION_basicTypes: {
                data.enforceInterface(DESCRIPTOR);
                int _arg0;
                _arg0 = data.readInt();
                long _arg1;
                _arg1 = data.readLong();
                boolean _arg2;
                _arg2 = (0 != data.readInt());
                float _arg3;
                _arg3 = data.readFloat();
                double _arg4;
                _arg4 = data.readDouble();
                java.lang.String _arg5;
                _arg5 = data.readString();
                this.basicTypes(_arg0, _arg1, _arg2, _arg3, _arg4, _arg5);
                reply.writeNoException();
                return true;
            }
            case TRANSACTION_login: {
                data.enforceInterface(DESCRIPTOR);
                java.lang.String _arg0;
                _arg0 = data.readString();
                java.lang.String _arg1;
                _arg1 = data.readString();
                this.login(_arg0, _arg1);
                reply.writeNoException();
                return true;
            }
        }
        return super.onTransact(code, data, reply, flags);
    }

    private static class Proxy implements com.silencezwm.ipcdemo.IMyAidlInterface {
        private android.os.IBinder mRemote;

        Proxy(android.os.IBinder remote) {
            mRemote = remote;
        }

        @Override
        public android.os.IBinder asBinder() {
            return mRemote;
        }

        public java.lang.String getInterfaceDescriptor() {
            return DESCRIPTOR;
        }

        /**
         * AIDL默认实现的方法
         */
        @Override
        public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) throws android.os.RemoteException {
            android.os.Parcel _data = android.os.Parcel.obtain();
            android.os.Parcel _reply = android.os.Parcel.obtain();
            try {
                _data.writeInterfaceToken(DESCRIPTOR);
                _data.writeInt(anInt);
                _data.writeLong(aLong);
                _data.writeInt(((aBoolean) ? (1) : (0)));
                _data.writeFloat(aFloat);
                _data.writeDouble(aDouble);
                _data.writeString(aString);
                mRemote.transact(Stub.TRANSACTION_basicTypes, _data, _reply, 0);
                _reply.readException();
            } finally {
                _reply.recycle();
                _data.recycle();
            }
        }

        /**
         * 定义了一个登录方法,包含用户名、密码两个参数
         */
        @Override
        public void login(java.lang.String username, java.lang.String password) throws android.os.RemoteException {
            android.os.Parcel _data = android.os.Parcel.obtain();
            android.os.Parcel _reply = android.os.Parcel.obtain();
            try {
                _data.writeInterfaceToken(DESCRIPTOR);
                _data.writeString(username);
                _data.writeString(password);
                mRemote.transact(Stub.TRANSACTION_login, _data, _reply, 0);
                _reply.readException();
            } finally {
                _reply.recycle();
                _data.recycle();
            }
        }
    }

    static final int TRANSACTION_basicTypes = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    static final int TRANSACTION_login = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
}

    /**
    * AIDL默认实现的方法
    */
    public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) throws android.os.RemoteException;

    /**
    * 定义了一个登录方法,包含用户名、密码两个参数
    */
    public void login(java.lang.String username, java.lang.String password) throws android.os.RemoteException;
}

4、新建Service:继续在服务器端src/main/java/包名目录下新建一个Service,并在配置文件中注册,然后设置其action值为com.silencezwm.ipcdemo,以供客户端进行调用,并在Service内部创建一个内部类继承静态抽象类IMyAidlInterface.Stub,AidlService.java完整代码如下:

package com.silencezwm.ipcdemo;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;

public class AidlService extends Service {

    private static final String TAG = AidlService.class.getName();

    public AidlService() {

    }

    @Override
    public IBinder onBind(Intent intent) {
        return new MyBinder();
    }

    class MyBinder extends IMyAidlInterface.Stub {

        @Override
        public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {
            Log.d(TAG, "=====:basicTypes");
        }

        @Override
        public void login(String username, String password) throws RemoteException {
            Log.d(TAG, "=====:login" + username + "==" + password);
        }
    }
}

5、拷贝aidl目录文件:至此,服务端的代码编写完成,接下来我们只需要完成客户端的调用代码即可。相当简单,首先把服务端aidl整个文件夹拷贝到客户端src/main目录下(至于拷贝的原因,稍后会进行阐述),然后build项目。此时,客户端、服务端就同时拥有AIDL相同的代码;

6、绑定服务端Service:在客户端你想要的地方通过服务端Service所在地的包名以及action来进行绑定,然后将Service连接成功后返回的IBinder对象,通过IMyAidlInterface.Stub.asInterface方法转换为我们定义的aidl对象,然后根据该对象调用我们所定义的方法即可完成整个通信过程,客户端MainActivity.java调用代码如下:

package com.silencezwm.ipcclient;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;

import com.silencezwm.ipcdemo.IMyAidlInterface;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private Button mBtnLogin;

    private IMyAidlInterface mIMyAidlInterface;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mBtnLogin = (Button) findViewById(R.id.btn_login);
        mBtnLogin.setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.btn_login:
                Intent intent = new Intent();
                // 服务端AndroidManifest.xml文件该Service所配置的action
                intent.setAction("com.silencezwm.ipcdemo");
                // Service所在的包名
                intent.setPackage("com.silencezwm.ipcdemo");
                bindService(intent, new ConnectCallBack(), Context.BIND_AUTO_CREATE);
                break;
        }
    }

    class ConnectCallBack implements ServiceConnection{

        // 服务连接成功回调
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            mIMyAidlInterface = IMyAidlInterface.Stub.asInterface(iBinder);
            login();
        }

        // 服务断开连接回调
        @Override
        public void onServiceDisconnected(ComponentName componentName) {
            mIMyAidlInterface = null;
        }
    }

    private void login() {
        try {
            mIMyAidlInterface.login("silencezwm", "123456");
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

}

7、结果验证:收获的季节来了,先将服务端程序跑起来,之后将客户端程序跑起来,点击登录按钮,不出意外的话,我们已经绑定了服务端的Service,并调用了login方法,将数据传递到服务端了,来看看Log的打印信息:

Log信息
回顾一下

回顾AIDL整个实现过程,其实并不复杂。此时,聪明的人往往都会有一个疑问:

客户端的数据到底是如何传递到服务端的呢?

接下来,我们就来一探究竟,以下的代码主要涉及到自动生成的IMyAidlInterface.java文件。

在客户端调用代码中,我们知道,一旦绑定Service成功后,会返回一个IBinder对象,调用IMyAidlInterface.Stub.asInterface(iBinder)方法将该对象转换为了我们所定义的AIDL接口对象,该方法具体做了什么呢?来看看:

private static final java.lang.String DESCRIPTOR = "com.silencezwm.ipcdemo.IMyAidlInterface";
    
public Stub() {
    this.attachInterface(this, DESCRIPTOR);
}

public static com.silencezwm.ipcdemo.IMyAidlInterface asInterface(android.os.IBinder obj) {
    if ((obj == null)) {
        return null;
    }
    android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
    if (((iin != null) && (iin instanceof com.silencezwm.ipcdemo.IMyAidlInterface))) {
        return ((com.silencezwm.ipcdemo.IMyAidlInterface) iin);
    }
    return new com.silencezwm.ipcdemo.IMyAidlInterface.Stub.Proxy(obj);
}

上面这段代码中,首先Stub构造方法被调用,跟着attachInterface方法被调用:

private IInterface mOwner;
private String mDescriptor;
...

public void attachInterface(IInterface owner, String descriptor) {
    mOwner = owner;
    mDescriptor = descriptor;
}

相当好理解,就是进行接口对象和字符串标识符的赋值。接下来在asInterface方法中,会根据标识符去IBinder的本地去查找是否有该对象,也就是调用obj.queryLocalInterface(DESCRIPTOR)方法,继续源码中Binder.java

public IInterface queryLocalInterface(String descriptor) {
    if (mDescriptor.equals(descriptor)) {
        return mOwner;
    }
    return null;
}

意思就是如果本地存在这个标识符的IInterface对象,那就直接返回之前构造方法中初始化的mOwner对象,否则返回null,因为我们这里涉及到了跨进程通信,所以这里会直接返回null。代码继续往下走,很显然,以下这段代码会调用:

return new com.silencezwm.ipcdemo.IMyAidlInterface.Stub.Proxy(obj);

代码字面意思就是返回IBinder的代码对象,如下:

private android.os.IBinder mRemote;

Proxy(android.os.IBinder remote) {
    mRemote = remote;
}

到这里,我们就拿到了一个IBinder的代理对象,通过代理对象,我们就可以调用之前所定义的login方法啦,代码:

@Override
public void login(java.lang.String username, java.lang.String password) throws android.os.RemoteException {
    android.os.Parcel _data = android.os.Parcel.obtain();
    android.os.Parcel _reply = android.os.Parcel.obtain();
    try {
        _data.writeInterfaceToken(DESCRIPTOR);
        _data.writeString(username);
        _data.writeString(password);
        mRemote.transact(Stub.TRANSACTION_login, _data, _reply, 0);
        _reply.readException();
    } finally {
        _reply.recycle();
        _data.recycle();
    }
}

这里就涉及到了一个重要的类Parcel,Parcel天生具备跨进程传输数据能力。在文章开头的漫画中,可不是直接把猪仔赶上船就行的,万一猪仔乱跑掉河里去了怎么办,所以屠宰场老板就准备了些猪笼。首先将猪仔赶进猪笼中,待船靠岸后,打开猪笼,将猪仔放出来即可。我们这里的Parcel就好比猪笼,我们把需要传递的数据写入Parcel中,然后到达目标进程后,将Parcel中的数据读出即可,所以可以将Parcel称为数据传输载体。Parcel支持的数据类型非常之多,足以满足我们日常开发所需。

现在你知道客户端的数据是如何传递到服务端了吗?

三、跨进程通信的注意事项

3.1、客户端与服务端aidl文件以及包名必须一致,否则无法正常通信。

3.2、在绑定服务端Service的时候,intent最好设置目标Service所在的包名,如intent.setPackage("com.silencezwm.ipcdemo"),当SDK版本大于14的时候,你会碰到这个错误 java.lang.IllegalArgumentException: Service Intent must be explicit:。

3.3、跨进程传递实体类必须进行序列化,不信你试试看。

3.4、Parcel所占用的内存,会随着你传递的数据量大小而相应变化。

好啦,本篇“Android跨进程通信”的相关介绍就到这里了,感谢你的到来!



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

推荐阅读更多精彩内容