您的位置:首页 > 移动开发 > Android开发

【朝花夕拾】性能优化篇之(七)Android跨进程通信--草稿

2019-01-02 10:00 295 查看

前言

       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,所以详略上是前半部分略,后半部分详,完全是按照笔者对知识点掌握程度来行文的。读者在阅读中,如果有些部分因为写得太简略而看得不过瘾,只能自己去查更详细的资料了;如果有些部分因为写得太详细而嫌啰嗦,完全可以跳着看;如果有些知识点因为笔者经验和水平问题写得有误或者阐述欠妥,请不吝赐教,万分感激!!!

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: