类型 | 描述 | 用时 |
---|---|---|
选题 | silencezwm | 0.5小时 |
写做时间 | 2017年10月25日 | 5.5小时 |
审稿 | silencezwm、Mya婷婷 | 2小时 |
校对上线 | silencezwm | 1小时 |
Tips:4个环节,共计约9小时的精心打磨完成上线,同时也很是感谢参与审稿的同窗。java
看漫画,涨薪资(¥) >>>【小猪的传奇一辈子】:
该漫画描述了小猪仔出生后,就生活在猪圈中,快乐的成长着。有一天,小猪长大了,屠宰场的老板就会经过船将肥猪运输过来进行屠宰,而后将猪肉销往世界各地。
看完该漫画后,是否是以为小猪仔的一辈子有点小悲凉,要怪就怪可恶的人类,无肉不欢,哈哈。android
精彩的漫画背后,总隐藏着一丝丝技术的小知识。本文将为你介绍“Android跨进程通讯”的相关知识,经过本文的学习,你能够了解到:服务器
1、单进程通讯与多进程通讯之间的区别2、跨进程通讯常见的五种实现方式app
3、跨进程通讯的注意事项ide
概念普及:IPC(Inter-Process Communication)机制,即进程间通讯或者跨进程通讯机制,是指两个进程之间进行数据交换的过程。
在好久之前,小猪仔从生到死都是在猪圈中生活的,没有外来者的入侵。一样的,在Android开发中,默认状况下,程序运行后,都只是运行在一个以项目包名建立的主进程中(就比如猪圈),例如学习
项目包名为:com.silencezwm.ipcdemo 默认进程名:com.silencezwm.ipcdemo
单进程通讯就如:不一样品类的猪仔(Android中不一样的组件)在相同的猪圈(在同一进程中)中生活(运行)。ui
忽然有一天,河流的右边来了一个商人,他发现河对岸有很多肥猪在游荡。因而,他发现了一个发家致富的机会,他在这里建了一个屠宰场。而后常常经过船将对岸的肥猪运送过来,进行屠宰,赚的盆满钵满。this
这个就相似Android程序,原本只有一个进程在运行,可是由于产品提了个奇葩的需求,使得咱们程序猿们不得很少开一个进程来实现该需求。spa
程序猿们一顿牢骚后,最后仍是动手干活了。.net
他们在AndroidManifest.xml文件中相应的组件添加了android:process属性,并指定进程名。而后打开了两个Activity,可是BActivity被指定运行在新的进程,当程序跑起来后,此时能够看到有两个进程正在运行,如图:
咱们知道,在单进程中通讯,组件间是能够随意进行通讯,由于它们都处于同一个内存空间。那多进程之间是怎样通讯的呢?
漫画中,屠宰场的老板经过船将河对岸的肥猪运送过来,就由于船的存在,该老板就能够跨越河流到达对岸。那么,Android跨进程通讯中,咱们也须要拥有一样功能的船,它就是Binder,经过Binder的中转,进程之间就能顺利的进行数据交换了。
五种常见的实现方式可分为两大类:四大组件的跨进程通讯和AIDL。
Activity、Service、BroadcastReceiver、Content Provider四大组件只须要在AndroidManifest.xml文件相应的组件中添加android:process属性,并指定进程名。程序运行起来后,它们就会运行在不一样的进程中,它们之间的通讯,官方已经给咱们作了很是好的封装,因此使用起来也很是方便,这里就很少作解释了。
概念普及:AIDL(Android interface definition Language),即Android接口定义语言。
要想应用AIDL技术,就至少须要有两个进程存在,A进程经过定义的AIDL接口文件与B进程进行通讯,具体的实现步骤以下:
一、准备两个进程:新建一个项目 IPCDemo,而后新建一个Module IPCClient,这样咱们就准备好了两个进程,完成后的项目结构如图:
二、建立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); }
三、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; }
四、新建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); } } }
五、拷贝aidl目录文件:至此,服务端的代码编写完成,接下来咱们只须要完成客户端的调用代码便可。至关简单,首先把服务端aidl整个文件夹拷贝到客户端src/main目录下(至于拷贝的缘由,稍后会进行阐述),而后build项目。此时,客户端、服务端就同时拥有AIDL相同的代码;
六、绑定服务端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(); } } }
七、结果验证:收获的季节来了,先将服务端程序跑起来,以后将客户端程序跑起来,点击登陆按钮,不出意外的话,咱们已经绑定了服务端的Service,并调用了login方法,将数据传递到服务端了,来看看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.一、客户端与服务端aidl文件以及包名必须一致,不然没法正常通讯。3.二、在绑定服务端Service的时候,intent最好设置目标Service所在的包名,如intent.setPackage("com.silencezwm.ipcdemo"),当SDK版本大于14的时候,你会碰到这个错误 java.lang.IllegalArgumentException: Service Intent must be explicit:。
3.三、跨进程传递实体类必须进行序列化,不信你试试看。
3.四、Parcel所占用的内存,会随着你传递的数据量大小而相应变化。
好啦,本篇“Android跨进程通讯”的相关介绍就到这里了,感谢你的到来!