【朝花夕拾】性能优化篇之(七)Android跨进程通信--草稿
前言
Android系统的运行由大量相互独立的进程相互协助来完成的,所以Android进程间通信问题,是做好Android开发高级工程师必须要跨过的一道坎,也是面试时经常被问及到的知识点。但是,我们是否真的清楚,Android中都有哪些方式实现跨进程通信呢?这些方式都有哪些优缺点?如何选择这些通信方式?Binder是什么?为什么要引入Binder?Binder是这么样实现跨进程通信的?AIDL是什么?AIDL和Binder又有什么关系呢?......
一、基础知识简介
在介绍Android跨进程通信之前,笔者先简单啰嗦一下进程隔离、跨进程通信。
1、进程隔离
在操作系统中,进程与进程间的内存和数据都是不共享的。两个进程就好像大海中相互独立的两个岛屿,各自生活在互相平行的两个世界中,互不干扰,各自为政。这样做的目的,是为了避免进程间相互操作数据的现象发生,从而引起各自的安全问题。为了实现进程隔离,采用了虚拟地址空间,两个进程各自的虚拟地址不同,从逻辑上来实现彼此间的隔离。
1 package com.songwei.aidldemoserver; 2 3 import android.app.Service; 4 import android.content.Intent; 5 import android.os.IBinder; 6 import android.os.RemoteException; 7 import android.util.Log; 8 9 public class AidlService extends Service { 10 private final static String TAG = "aidlDemo"; 11 12 public AidlService() { 13 } 14 15 @Override 16 public void onCreate() { 17 super.onCreate(); 18 Log.i(TAG, "server:[onCreate]"); 19 } 20 21 @Override 22 public IBinder onBind(Intent intent) { 23 // TODO: Return the communication channel to the service. 24 Log.i(TAG, "server:[onBind]"); 25 return new MyBinder(); 26 } 27 28 @Override 29 public boolean onUnbind(Intent intent) { 30 Log.i(TAG, "server:[onUnbind]"); 31 return super.onUnbind(intent); 32 } 33 34 @Override 35 public void onDestroy() { 36 super.onDestroy(); 37 Log.i(TAG, "server:[onDestroy]"); 38 } 39 40 class MyBinder extends IDemoService.Stub { 41 private String mName = ""; 42 43 public void setName(String name) throws RemoteException{ 44 Log.i(TAG, "server:[setName]"); 45 mName = name; 46 } 47 48 @Override 49 public String getName() throws RemoteException { 50 Log.i(TAG, "server:[getName]"); 51 return mName; 52 } 53 } 54 }View Code
为了下文分析流程及生命周期,在其中各个方法中都添加了Log。
同时,在Server端的AndroidManifest.xml文件中添加该Service的注册信息。
1 <service 2 android:name=".AidlService" 3 android:exported="true"> 4 <intent-filter> 5 <action android:name="com.songwei.aidl" /> 6 </intent-filter> 7 </service>
这里有几点需要注意:
(1)exported属性值,如果有“intent-filter”,则默认值为true,否则为false。所以这里其实可以去掉,因为有“intent-filter”,其默认值就是true。
(2)由于笔者在后面启动该service的时候用的action的方式,所以这里就有了“intent-filter”里面的action。如果用其他方式启动,这个service的注册信息就需要相应的改动了,有一定开发经验的读者应该都知道,就不展开讲了,主要是怕读者容易忽略这里,所以特别提醒一下。
4、编译Sever端和Client端App,生成IDemoService.java文件。
当编译的时候,AS会自动为我们生成IDemoService.java文件,如图6.1和图6.2中④处所示。当你打开该文件的时候,是不是看到了如下场景?
1 /* 2 * This file is auto-generated. DO NOT MODIFY. 3 * Original file: D:\\ASWorkspace\\testDemo\\aidldemoclient\\src\\main\\aidl\\com\\songwei\\aidldemoserver\\IDemoService.aidl 4 */ 5 package com.songwei.aidldemoserver; 6 // Declare any non-default types here with import statements 7 8 public interface IDemoService extends android.os.IInterface { 9 /** 10 * Local-side IPC implementation stub class. 11 */ 12 public static abstract class Stub extends android.os.Binder implements com.songwei.aidldemoserver.IDemoService { 13 private static final java.lang.String DESCRIPTOR = "com.songwei.aidldemoserver.IDemoService"; 14 15 /** 16 * Construct the stub at attach it to the interface. 17 */ 18 public Stub() { 19 this.attachInterface(this, DESCRIPTOR); 20 } 21 22 /** 23 * Cast an IBinder object into an com.songwei.aidldemoserver.IDemoService interface, 24 * generating a proxy if needed. 25 */ 26 public static com.songwei.aidldemoserver.IDemoService asInterface(android.os.IBinder obj) { 27 if ((obj == null)) { 28 return null; 29 } 30 android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); 31 if (((iin != null) && (iin instanceof com.songwei.aidldemoserver.IDemoService))) { 32 return ((com.songwei.aidldemoserver.IDemoService) iin); 33 } 34 return new com.songwei.aidldemoserver.IDemoService.Stub.Proxy(obj); 35 } 36 37 @Override 38 public android.os.IBinder asBinder() { 39 return this; 40 } 41 42 @Override 43 public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException { 44 switch (code) { 45 case INTERFACE_TRANSACTION: { 46 reply.writeString(DESCRIPTOR); 47 return true; 48 } 49 case TRANSACTION_setName: { 50 data.enforceInterface(DESCRIPTOR); 51 java.lang.String _arg0; 52 _arg0 = data.readString(); 53 this.setName(_arg0); 54 reply.writeNoException(); 55 return true; 56 } 57 case TRANSACTION_getName: { 58 data.enforceInterface(DESCRIPTOR); 59 java.lang.String _result = this.getName(); 60 reply.writeNoException(); 61 reply.writeString(_result); 62 return true; 63 } 64 } 65 return super.onTransact(code, data, reply, flags); 66 } 67 68 private static class Proxy implements com.songwei.aidldemoserver.IDemoService { 69 private android.os.IBinder mRemote; 70 71 Proxy(android.os.IBinder remote) { 72 mRemote = remote; 73 } 74 75 @Override 76 public android.os.IBinder asBinder() { 77 return mRemote; 78 } 79 80 public java.lang.String getInterfaceDescriptor() { 81 return DESCRIPTOR; 82 } 83 84 @Override 85 public void setName(java.lang.String name) throws android.os.RemoteException { 86 android.os.Parcel _data = android.os.Parcel.obtain(); 87 android.os.Parcel _reply = android.os.Parcel.obtain(); 88 try { 89 _data.writeInterfaceToken(DESCRIPTOR); 90 _data.writeString(name); 91 mRemote.transact(Stub.TRANSACTION_setName, _data, _reply, 0); 92 _reply.readException(); 93 } finally { 94 _reply.recycle(); 95 _data.recycle(); 96 } 97 } 98 99 @Override 100 public java.lang.String getName() throws android.os.RemoteException { 101 android.os.Parcel _data = android.os.Parcel.obtain(); 102 android.os.Parcel _reply = android.os.Parcel.obtain(); 103 java.lang.String _result; 104 try { 105 _data.writeInterfaceToken(DESCRIPTOR); 106 mRemote.transact(Stub.TRANSACTION_getName, _data, _reply, 0); 107 _reply.readException(); 108 _result = _reply.readString(); 109 } finally { 110 _reply.recycle(); 111 _data.recycle(); 112 } 113 return _result; 114 } 115 } 116 117 static final int TRANSACTION_setName = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); 118 static final int TRANSACTION_getName = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1); 119 } 120 121 public void setName(java.lang.String name) throws android.os.RemoteException; 122 123 public java.lang.String getName() throws android.os.RemoteException; 124 }View Code 惊不惊喜?意不意外?这下是不是有种似曾相识的赶脚?这就是一个很普通的java中的接口文件而已,结构也非常简单。
1 package com.songwei.aidldemoclient; 2 3 import android.content.ComponentName; 4 import android.content.Intent; 5 import android.content.ServiceConnection; 6 import android.os.IBinder; 7 import android.os.RemoteException; 8 import android.support.v7.app.AppCompatActivity; 9 import android.os.Bundle; 10 import android.util.Log; 11 import android.view.View; 12 import android.widget.Button; 13 14 import com.songwei.aidldemoserver.IDemoService; 15 16 public class ClientActivity extends AppCompatActivity { 17 18 private final static String TAG = "aidlDemo"; 19 private Button mBindBtn, mUnBindBtn; 20 private IDemoService mDemoService; 21 private boolean mIsBinded = false; 22 private ServiceConnection mConn = new ServiceConnection() { 23 //当与远程Service绑定后,会回调该方法。 24 @Override 25 public void onServiceConnected(ComponentName componentName, IBinder binder) { 26 Log.i(TAG, "client:[onServiceConnected]componentName=" + componentName); 27 mIsBinded = true; 28 //得到一个远程Service中的Binder代理,而不是该Binder实例 29 mDemoService = IDemoService.Stub.asInterface(binder); 30 try { 31 //远程控制设置name值 32 mDemoService.setName("Andy Song"); 33 //远程获取设置的name值 34 String myName = mDemoService.getName(); 35 Log.i(TAG, "client:[onServiceConnected]myName=" + myName); 36 } catch (RemoteException e) { 37 e.printStackTrace(); 38 } 39 } 40 41 //该回调方法一般不会调用,如果在解绑的时候,发现该方法没有调用,不要惊慌,因为该方法的调用时机是Service被意外销毁时,比如内存不足时。 42 @Override 43 public void onServiceDisconnected(ComponentName name) { 44 Log.i(TAG, "client:[onServiceDisconnected]"); 45 mIsBinded = false; 46 mDemoService = null; 47 } 48 }; 49 50 @Override 51 protected void onCreate(Bundle savedInstanceState) { 52 super.onCreate(savedInstanceState); 53 setContentView(R.layout.activity_main); 54 mBindBtn = (Button) findViewById(R.id.btn_bind); 55 mBindBtn.setOnClickListener(new View.OnClickListener() { 56 @Override 57 public void onClick(View v) { 58 //Android5.0及以后,出于对安全的考虑,Android系统对隐式启动Service做了限制,需要带上包名或者类名,这一点需要注意。 59 Intent intent = new Intent(); 60 intent.setAction("com.songwei.aidl"); 61 intent.setPackage("com.songwei.aidldemoserver"); 62 bindService(intent, mConn, BIND_AUTO_CREATE); 63 } 64 }); 65 mUnBindBtn = (Button) findViewById(R.id.btn_unbind); 66 mUnBindBtn.setOnClickListener(new View.OnClickListener() { 67 @Override 68 public void onClick(View v) { 69 //解除绑定,当调用unbindService时,一定要判断当前service是否是binded的,如果没有,就会报错。 70 if (mIsBinded) { 71 unbindService(mConn); 72 mDemoService = null; 73 mIsBinded = false; 74 } 75 } 76 }); 77 } 78 }View Code 代码中对一些关键和容易忽略的地方做了注释,可以结合起来进行理解。
6、运行
运行的时候,需要先启动Service端进程,才能在Client端中点击“绑定”的时候绑定成功。完成一次“绑定”和“解绑”,得到的log如下所示:
1 01-08 15:29:43.109 13532-13532/com.songwei.aidldemoserver I/aidlDemo: server:[onCreate] 2 01-08 15:29:43.110 13532-13532/com.songwei.aidldemoserver I/aidlDemo: server:[onBind] 3 01-08 15:29:43.113 13299-13299/com.songwei.aidldemoclient I/aidlDemo: client:[onServiceConnected]componentName=ComponentInfo{com.songwei.aidldemoserver/com.songwei.aidldemoserver.AidlService} 4 01-08 15:29:43.114 13532-13547/com.songwei.aidldemoserver I/aidlDemo: server:[setName] 5 01-08 15:29:43.114 13532-13546/com.songwei.aidldemoserver I/aidlDemo: server:[getName] 6 01-08 15:29:43.114 13299-13299/com.songwei.aidldemoclient I/aidlDemo: client:[onServiceConnected]myName=Andy Song 7 01-08 15:36:07.570 13299-13299/com.songwei.aidldemoclient I/aidlDemo: client:[onServiceDisconnected]
可以结合前面的ClientActivity.java和AidlService.java代码中的添加的log,来理解一下这个流程。当然,最好是能够按照上面的步骤,亲自动手实现一遍,比看10遍更有效果。
本节对介绍了AIDL最基本的知识,出于项目性质的原因,可能有些Android开发人员工作了好几年都不一定需要完整写一个AIDL实现两个App通信的功能。笔者就是这样,在前几年的工作中,虽然很早就知道AIDL这个东西,但确实是项目中就没有写过这个功能,直到最近两年。所以即便是读者您有不少开发经验了,也可能和笔者一样是个AIDL的初级开发者,这也是我花这么长的篇幅写这个基础内容的原因。
另外,有需要源码可以下载这个demo,网盘地址:https://pan.baidu.com/s/1CyE_8-T9TDQLVQ1TDAEX2A 提取码:4auk 。
七、AIDL的深入使用和理解
前面一节讲了AIDL最进本的知识,这一节中将会结合更复杂的场景,更深入地介绍AIDL。
1、Client端是如何实现调用Server端方法的
2、AIDL支持的数据类型
3、AIDL数据类序列化问题
4、AIDL回调的使用
结语
本文主要是笔者用来整理跨进程通信的知识点,以及复习近两年才真正会用的AIDL,所以详略上是前半部分略,后半部分详,完全是按照笔者对知识点掌握程度来行文的。读者在阅读中,如果有些部分因为写得太简略而看得不过瘾,只能自己去查更详细的资料了;如果有些部分因为写得太详细而嫌啰嗦,完全可以跳着看;如果有些知识点因为笔者经验和水平问题写得有误或者阐述欠妥,请不吝赐教,万分感激!!!
- 【朝花夕拾】Android性能篇之(七)Android跨进程通信总结
- 【朝花夕拾】Android性能篇----草稿
- 【朝花夕拾】Android性能篇之(六)Android进程管理机制
- Android——AIDL进程通信记录(复杂数据类型)
- 【Android开发艺术探索】IPC机制(四)-使用AIDL进行跨进程通信
- [原] android Service 跨进程通信
- Android进程间的通信之AIDL
- Android 进阶7:进程通信之 AIDL 的使用
- Android跨进程通信的4种方式
- AIDL 的理解与使用(一种android内部进程通信接口的描述语言)
- Android通过AIDL实现下载进程通信
- android Service 跨进程通信
- Android进程之间通信Aidl
- Android与服务进程内通信
- Android性能优化篇 [ 谷歌官方 ]
- android 用aidl实现进程间的通信
- Android跨进程通信
- Android跨进程通信之AIDL快速入门
- Android 进程通信机制之 AIDL
- Android AIDL使用详解 实现进程间的通信