季春初始,天气返暖,新冠渐去,正值学习好时机。在Android系统中,AIDL一直在Framework和应用层上扮演着很重要的角色,今日且将其原理简单分析。(文2020.03.30)

一、开篇介绍

1.简单介绍

Android系统中对原理的分析基本离不开对源码的阅读,我理解的原理分析:

原理分析 = 基本概念 + 源码分析 + 实践

正如创始人Linus Torvalds的名言:RTFSC(read the f**king source code)。本文也是按照上述结构来介绍AIDL的。

接下来先简单提一提IPC、序列化和Parcel三个概念。

2.IPC

1)进程与线程

A. 线程是CPU调度的最小单元,同时线程是一种有限的系统资源。

B. 进程一般指一个执行单元,在PC和移动设备上指一个程序或一个应用

C. 一个进程可以包含多个线程,包含与被包含的关系。

D. Java默认只有一个线程,叫主线程,在android中也叫做UI线程

2)IPC

A. 定义:IPC(inter-process Commnication)跨进程的通信,多进程之间的通信。

B. 为什么需要进程通信:我们知道Android一般情况下一个应用是默认运行在一个进程中,但有可能一个应用中需要采用多进程模式(process属性)来实现(比如获取多份内存空间),或者两个应用之间需要获取彼此之间的数据,还有AMS(系统服务)对每个应用中四大组件的管理,系统服务是运行在一个单独的进程中,这些统统需要IPC。

3)Android中的IPC

Android IPC方式:文件共享、ContentProvider(底层是Binder实现)、Socket、Binder(AIDL、Messenger)。

3.序列化

序列化是指将一个对象转化为二进制或者是某种格式的字节流,将其转换为易于保存或网络传输的格式的过程,反序列化则是将这些字节重建为一个对象的过程。Serializable和Parcelable接口可以完成对象的序列化。如下图:

1)Serializable

Serializable是Java提供的序列化接口,使用时只需要实现Serializable接口并声明一个serialVersionUID(用于反序列化)

2)Parcelable

A. writeToParcel:将对象序列化为一个Parcel对象,将类的数据写入外部提供的Parcel中

B. describeContents:内容接口描述,默认返回0

C. 实例化静态内部对象CREATOR实现接口Parcelable.Creator,需创建一个数组(newArray(int size)) 供外部类反序列化本类数组使用;createFromParcel创建对象

D. readFromParcel:从流里读取对象,写的顺序和读的顺序必须一致。

Serializable使用简单,但是开销很大(大量I/O操作),Parcelable是Android中的序列化方式,使用起来麻烦,但是效率很高,是Android推荐的方式。Parcelable主要用在内存序列化上,如果要将对象序列化到存储设备或者通过网络传输也是可以的,但是会比较复杂,这两种情况建议使用Serializable。

4.Parcel

Parcel主要就是用来进行IPC通信,是一种容器,他可以包含数据或者是对象引用,并且能够用于Binder的传输。同时支持序列化以及跨进程之后进行反序列化,同时其提供了很多方法帮助开发者完成这些功能。Parcel的读写都是一个指针操作的,有writeInt(int val)、writeString(String val)、setDataPosition(int val)、readInt()、readString()、recycle()方法。

二、AIDL

1.定义

AIDL:Android interface definition Language,Android 接口定义语言。使用aidl可以发布以及调用远程服务,实现跨进程通信。将服务的aidl放到对应的src目录,工程的gen目录会生成相应的接口类。

2.语法

AIDL的语法十分简单,与Java语言基本保持一致,主要规则有以下几点:

1)AIDL文件以 .aidl 为后缀名

2)AIDL支持的数据类型分为如下几种:

A. 八种基本数据类型:byte、char、short、int、long、float、double、boolean

String,CharSequence

B. 实现了Parcelable接口的数据类型

C. List 类型。List承载的数据必须是AIDL支持的类型,或者是其它声明的AIDL对象

D. Map类型。Map承载的数据必须是AIDL支持的类型,或者是其它声明的AIDL对象

3)AIDL文件可以分为两类

A. 一类用来声明实现了Parcelable接口的数据类型,以供其他AIDL文件使用那些非默认支持的数据类型。

B. 另一类是用来定义接口方法,声明要暴露哪些接口给客户端调用,定向Tag就是用来标注这些方法的参数值。

4)定向Tag

定向Tag表示在跨进程通信中数据的流向,用于标注方法的参数值。

A. in 表示数据只能由客户端流向服务端

B. out 表示数据只能由服务端流向客户端

C. inout 则表示数据可在服务端与客户端之间双向流通。

如果AIDL方法接口的参数值类型是:基本数据类型、String、CharSequence或者其他AIDL文件定义的方法接口,那么这些参数值的定向 Tag 默认是且只能是 in,所以除了这些类型外,其他参数值都需要明确标注使用哪种定向Tag。

5)明确导包

在AIDL文件中需要明确标明引用到的数据类型所在的包名,如java的import导入。

3.使用步骤

1)创建 AIDL

创建要操作的实体类,实现 Parcelable 接口,以便序列化/反序列化

新建 aidl 文件夹,在其中创建接口 aidl 文件以及实体类的映射 aidl 文件

build project ,生成 Binder 的 Java 文件

这里定义了一个User对象,包含一个名字属性,并定义了一个控制接口,添加和查询。

1 public class User implementsParcelable {2

3 privateString name;4

5 publicUser(String name) {6 this.name =name;7 }8

9 protectedUser(Parcel in) {10 name =in.readString();11 }12

13 publicString getName() {14 returnname;15 }16

17 public voidsetName(String name) {18 this.name =name;19 }20

21 public static final Creator CREATOR = new Creator() {22 @Override23 publicUser createFromParcel(Parcel in) {24 return newUser(in);25 }26

27 @Override28 public User[] newArray(intsize) {29 return newUser[size];30 }31 };32

33 @Override34 public intdescribeContents() {35 return 0;36 }37

38 @Override39 public void writeToParcel(Parcel dest, intflags) {40 dest.writeString(name);41 }42

43 public voidreadFromParcel(Parcel in) {44 this.name =in.readString();45 }46 }

View Code

1 //UserControl.aidl

2 packagecom.haybl.aidl_test;3

4 importcom.haybl.aidl_test.User;5

6 //Declare any non-default types here with import statements

7

8 interfaceUserController {9

10 ListgetUserList();11

12 voidaddUserInOut(inout User user);13 }

2)服务端

复制上述两个AIDL文件至服务端代码。创建 Service,在其中创建上面生成的 Binder 对象实例,实现接口定义的方法在 onBind() 中返回。

1 @Override2 publicIBinder onBind(Intent intent) {3 Log.d(TAG, "onBind");4 returnstub;5 }6

7 private UserController.Stub stub = newUserController.Stub() {8 @Override9 public List getUserList() throwsRemoteException {10 Log.d(TAG, "getUserList");11 returnmUserList;12 }13

14 @Override15 public void addUserInOut(User user) throwsRemoteException {16 if (user != null) {17 Log.d(TAG, "Server receive a new user by InOut = " +user.getName());18 //服务端改变数据,通过InOut Tag会同步到客户端。数据是双向流动的

19 user.setName("I'm changed");20 mUserList.add(user);21 } else{22 Log.d(TAG, "Server receive a null by InOut");23 }24 }25 };

3)客户端

实现 ServiceConnection 接口,在其中拿到 AIDL 类,bindService()调用 AIDL 类中定义好的操作请求

1   private voidbindService() {2 Log.d(TAG, "bindService");3 Intent intent = newIntent();4 //android 5.0以后直设置action不能启动相应的服务,需要设置packageName或者Component

5 intent.setPackage("com.haybl.aidl_server");6 intent.setAction("com.haybl.aidl_server.action");7 bindService(intent, connection, BIND_AUTO_CREATE);8 }9

10 private ServiceConnection connection = newServiceConnection() {11 @Override12 public voidonServiceConnected(ComponentName name, IBinder service) {13 Log.d(TAG, "onServiceConnected, name = " +name.getPackageName());14 mUserController =UserController.Stub.asInterface(service);15 isConnected = true;16 }17

18 @Override19 public voidonServiceDisconnected(ComponentName name) {20 isConnected = false;21 Log.d(TAG, "onServiceDisconnected, name = " +name.getPackageName());22 }23 };

4.代码分析

原理分析分析离不开代码,aidl在build之后会生成一个对应的接口java文件,aidl文件本身的作用就是生成这个java文件,后续的操作都在这个java接口上进行的。直接放生成的文件,根据其中的注释可以很好的理解的原理:

1 packagecom.haybl.aidl_test;2

3 /**

4 * 所有在Binder中传输的接口都必须实现IInterface接口5 */

6 public interface UserController extendsandroid.os.IInterface {7 /**

8 * 本地IPC实施存根类:为内部静态类,继承android.os.Binder、实现com.haybl.aidl_test.UserController(本接口)9 */

10 public static abstract class Stub extends android.os.Binder implementscom.haybl.aidl_test.UserController {11 /**

12 * Binder的唯一标识13 */

14 private static final java.lang.String DESCRIPTOR = "com.haybl.aidl_test.UserController";15

16 /**

17 * 接口中的方法标志,个数与定义的方法个数一致且一一对应,在onTransact()中使用18 */

19 static final int TRANSACTION_getUserList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);20 static final int TRANSACTION_addUserInOut = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);21

22 /**

23 * 构造方法,将其附加到接口24 *25 * attachInterface()方法将特定接口与Binder相关联。26 * 调用后,可通过queryLocalInterface()方法,在当请求符合描述符(DESCRIPTOR)时,返回该IInterface。27 */

28 publicStub() {29 this.attachInterface(this, DESCRIPTOR);30 }31

32 /**

33 * 将IBinder对象转换为com.haybl.aidl_test.UserController接口。34 * 用于将服务端的Binder对象转换为客户端所需要的接口对象,该过程区分进程,35 * 如果进程一样,就返回服务端Stub对象本身,否则就返回封装后的Stub.Proxy对象。36 */

37 public staticcom.haybl.aidl_test.UserController asInterface(android.os.IBinder obj) {38 if ((obj == null)) {39 return null;40 }41

42 /*

43 * 针对此obj Binder对象查询接口的本地实现。44 * 如果返回null,则需要实例化代理类,通过transact()方法封装调用。非同一进程45 * 如果提供的信息与请求的描述符匹配,则返回关联的IInterface。同一进程46 */

47 android.os.IInterface iin =obj.queryLocalInterface(DESCRIPTOR);48 if (((iin != null) && (iin instanceofcom.haybl.aidl_test.UserController))) {49 return((com.haybl.aidl_test.UserController) iin);50 }51 return newcom.haybl.aidl_test.UserController.Stub.Proxy(obj);52 }53

54 /**

55 * android.os.IInterface接口方法实现56 */

57 @Override58 publicandroid.os.IBinder asBinder() {59 return this;60 }61

62 /**

63 * 64 * android.os.Binder的onTransact方法实现65 * 如果要调用此函数,需调用transact()。transact()实现对onTransact上调用。在远端,将调用到Binder中以进行IPC。66 * 该方法是运行在服务端的Binder线程中的,当客户端发起远程请求后,在底层封装后会交由此方法来处理。67 *68 *@paramcode 要执行的动作标志。在IBinder.FIRST_CALL_TRANSACTION 和 IBinder.LAST_CALL_TRANSACTION之间69 *@paramdata 从调用方接收到的数据。70 *@paramreply 如果调用方需要返回结果,则应将其从此处返回。71 *@paramflags 附加操作标志。正常RPC为0,one-way类型的RPC为1。72 *73 *@returnreturn 是否成功 true 返回成功74 * false 客户端的请求失败(可以用来做权限控制)75 */

76 @Override77 public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throwsandroid.os.RemoteException {78 switch(code) {79 //IBinder协议事务代码,写入标准接口描述符DESCRIPTOR。

80 caseINTERFACE_TRANSACTION: {81 reply.writeString(DESCRIPTOR);82 return true;83 }84 //getUserList 方法

85 caseTRANSACTION_getUserList: {86 data.enforceInterface(DESCRIPTOR);87 //服务端调用具体实现 this.getUserList

88 java.util.List _result = this.getUserList();89 reply.writeNoException();90 reply.writeTypedList(_result);91 return true;92 }93 //addUserInOut 方法

94 caseTRANSACTION_addUserInOut: {95 data.enforceInterface(DESCRIPTOR);96 com.haybl.aidl_test.User _arg0;97 if ((0 !=data.readInt())) {98 _arg0 =com.haybl.aidl_test.User.CREATOR.createFromParcel(data);99 } else{100 _arg0 = null;101 }102 //服务端调用具体实现 this.addUserInOut

103 this.addUserInOut(_arg0);104 reply.writeNoException();105 if ((_arg0 != null)) {106 reply.writeInt(1);107 _arg0.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);108 } else{109 reply.writeInt(0);110 }111 return true;112 }113 }114 return super.onTransact(code, data, reply, flags);115 }116

117 /*

118 * com.haybl.aidl_test.UserController代理类119 * 120 */

121 private static class Proxy implementscom.haybl.aidl_test.UserController {122 privateandroid.os.IBinder mRemote;123

124 Proxy(android.os.IBinder remote) {125 mRemote =remote;126 }127

128 @Override129 publicandroid.os.IBinder asBinder() {130 returnmRemote;131 }132

133 /**

134 * 返回描述符DESCRIPTOR,在Binder的onTransact方法中需要写入此描述:case INTERFACE_TRANSACTION135 */

136 publicjava.lang.String getInterfaceDescriptor() {137 returnDESCRIPTOR;138 }139

140 @Override141 public java.util.List getUserList() throwsandroid.os.RemoteException {142 android.os.Parcel _data =android.os.Parcel.obtain();143 android.os.Parcel _reply =android.os.Parcel.obtain();144 java.util.List_result;145 try{146 _data.writeInterfaceToken(DESCRIPTOR);147 //远程调用

148 mRemote.transact(Stub.TRANSACTION_getUserList, _data, _reply, 0);149 _reply.readException();150 _result =_reply.createTypedArrayList(com.haybl.aidl_test.User.CREATOR);151 } finally{152 _reply.recycle();153 _data.recycle();154 }155 return_result;156 }157

158 @Override159 public void addUserInOut(com.haybl.aidl_test.User user) throwsandroid.os.RemoteException {160 android.os.Parcel _data =android.os.Parcel.obtain();161 android.os.Parcel _reply =android.os.Parcel.obtain();162 try{163 _data.writeInterfaceToken(DESCRIPTOR);164 if ((user != null)) {165 _data.writeInt(1);166 user.writeToParcel(_data, 0);167 } else{168 _data.writeInt(0);169 }170 /*

171 * 远程调用172 * 客户端会被挂起等待服务端执行完成才继续其他代码执行,即同步调用173 * 若使用oneway关键字修饰此接口或整个UserController,则不会被挂起,即异步调用174 */

175 mRemote.transact(Stub.TRANSACTION_addUserInOut, _data, _reply, 0);176 _reply.readException();177

178 //InOut定向Tag生成的对客户端对象修改的代码

179 if ((0 !=_reply.readInt())) {180 user.readFromParcel(_reply);181 }182 } finally{183 _reply.recycle();184 _data.recycle();185 }186 }187 }188 }189

190 /**

191 * 定义的方法,在实现Stub 的时候需要实现这些方法192 */

193 public java.util.List getUserList() throwsandroid.os.RemoteException;194

195 public void addUserInOut(com.haybl.aidl_test.User user) throwsandroid.os.RemoteException;196 }

View Code

5.注意

1)客户端与服务端aidl包名要一致

2)定向Tag:

A. InOut 类型,服务端对数据的改变同时也同步到了客户端,因此可以说两者之间数据是双向流动的

B. In 类型的表现形式是:数据只能由客户端传向服务端,服务端对数据的修改不会影响到客户端

C. Out类型的表现形式是:数据只能由服务端传向客户端,即使客户端向方法接口传入了一个对象,该对象中的属性值也是为空的,即不包含任何数据,服务端获取到该对象后,对该对象的任何操作,就会同步到客户端这边

3)oneway

此关键字用于修改远程调用的行为。对客户端不会有任何影响,调用仍是同步调用。使用oneway时,远程服务端不会阻塞,它只是发送事务数据并立即返回(异步调用);不使用则为同步调用。并且方法必须是void类型的。

4)服务端方法是运行在Binder线程池中,要考虑好线程同步。

6.其他

1)双向通信,服务端向客户端主动发起(可采用观察者模式产生回调实现,RemoteCallbackList取消回调)

2)Binder连接池(aidl很多的情况下)

后续找时间研究这两个方面的内容,提到AIDL就不得不提Binder,更何况要对其原理进行分析,接下来看看Binder的简单介绍。

三、Binder

1.简介

1)Binder是一个很深入的话题,很复杂

2)Binder是android中的一个类,实现了IBinder接口

3)Framework角度,Binder是ServiceManger连接各种Manger(AM、WM)和MangerService的桥梁

4)应用层角度,Binder是客户端和服务端进行通信的媒介,通过bindService可获得一个Binder对象,进而获得服务端提供的各种服务接口(包括普通服务和AIDL服务)

5)IPC角度看Binder是一种跨进程通信方式

6)Binder还是一种虚拟的物理设备,驱动为 /dev/binder。如下图:

2.IPC再现——IPC原理

IPC原理图 - 图源

*ioctl(input/output control)是一个专用于设备输入输出操作的系统调用。

3.Binder原理

Binder原理图 - 图源

1)Binder通信采用C/S架构,包含Client、Server、ServiceManager以及binder驱动四个组件,其中ServiceManager用于管理系统中的各种服务(native C++层)。

2)Binder 驱动:binder驱动与硬件设备没有关系,但是它的工作方式与设备驱动程序是一样的,工作在内核态,提供open(),mmap(),ioctl等标准文件操作,用户可以通过/dev/binder来访问它,驱动负责进程之间binder通信的建立,传递,计数管理以及数据的传递交互等底层支持。

3)ServiceManager:将Binder名字转换为client中对该binder的引用,使得client可以通过binder名字获得server中binder实体的引用。

4)Client和Server在Binder驱动和Service Manager提供的基础设施上,进行Client-Server之间的通信。

4.源码目录

对于Binder的理解大多来自其他大佬的博客,其中的原理和相关角色都介绍得很详细、系统。贴一下源码目录:

/framework/base/core/java/(Java)/framework/base/core/jni/(JNI)/framework/native/libs/binder (Native)/framework/native/cmds/servicemanager/(Native)/kernel/drivers (Driver)

四、Android TV实践

上述对Binder的叙述很少,只是简单罗列了下相关概念。主要核心还是在实际场景中如何运用上面的知识去理解所遇到的问题、或者解决新的需求。由于项目是在Mstar芯片上的Android系统电视,并且此次所要解决的问题涵盖了系统的多个层次,所以有必要记录一下。虽然是电视系统,但还是基于Android的,其中的原理都是一样的,只是Mstar在某些地方加入或者修改了自己的东西。

1.问题场景

在Mstar的芯片方案上,电视系统在欧洲某国家出现了自动搜台后有LCN(Logic Channel Number,逻辑节目号)冲突的节目,即LCN重复。查看打印信息,在搜台完成出现冲突节目后,会发送一个STATUS_LCN_CONFLICT,而在实际的分支代码中去除了对这个事件的处理,所以弹出的进度条提示没有消失,致使界面卡死,并且冲突节目的问题也没有解决。

1)初步处理:首先直接引入Mstar对冲突事件的处理,跑出来看到最后搜完台会弹窗让用户选择是否自动解决冲突节目,否则不解决,弹窗消失;是则调用resolveLCNConflictAuto()接口自动解决。

2)需求更改:上述初步处理已基本解决问题。接着更改了修改需求:在弹出选择是否自动处理冲突节目时,若选否,需要将冲突节目列出,并由用户手动选择保留的节目直至所有冲突节目选择完成。

3)需求处理:查看Mstar中Framework的代码,发现没有提供可以满足需求的接口。需找Supernova Engineer协助解决(沟通过程略 ......)。

2.解决流程

Supernova 工程师给出如下接口:

1 //获取冲突节目列表

2 bool ScanManagerImplService::Client::getConflictProgramList(Parcel *reply)3

4 //设置单个冲突节目

5 bool ScanManagerImplService::Client::setConflictLCNServiceListIndex(uint8_t listindex, uint8_t selectindex)

图中注释已经说明了,实际解决问题需要这两个接口,所以后续的工作基本围绕这两个接口来展开。按照Android的体系结构,首先定义出数据Model,在应用层根据数据结构,编写符合需求的逻辑,还包括实现UI效果等;接着在Framework层添加应用层需要的接口(此问题需按照Mstar的层次逻辑添加),并解析相关数据;最后打通Supernova中代码逻辑。可以看到,基本思路就是应用层往下走的方向,因为对应用层更熟悉一些,所以更容易入手问题。相当于我假设已经拿到这些数据(还有数据解决方法),进而实现我的界面逻辑;接下来再思考怎样拿到这些数据,怎样与其他更底层的逻辑交互。首先来看下接口中需要用到的数据格式是怎么样的:

1 ///Define conflict program struct

2 typedef struct

3 {4 ///state of LCN resolved

5 U8 u8ResolveState;6 ///number of total list

7 U16 u16ListNum;8 ///the service of one list that gets allocated LCN. Be pair with VctConfProg.

9 U16 u16AllocLCNService[MAX_CONFLICT_PROGRAM_LIST_NUM];10 ///Program information

11 listConfProgList[MAX_CONFLICT_PROGRAM_LIST_NUM];12 } ST_CONFLICT_PROGRAM;13

14 ///Define conflict program info struct

15 typedef struct

16 {17 ///program information number

18 U16 u16Number;19 ///program index of DB

20 U32 u32DbIndex;21 ///Service ID

22 U16 u16ServiceID;23 ///program information service type

24 U8 u8ServiceType;25 ///program information service name

26 stringsServiceName;27 } ST_CONFLICT_PROGRAM_INFO;

从数据结构中可以看出,getConflict返回的这个数据体就包含了所有冲突的节目List<>,其中的每一个节目又有一个数组来保存其冲突情况(ST_CONFLICT_PROGRAM_INFO)。拿到数据后先安装Android的做法定义出Java版本的数据,因为这样才能给上层应用调用。如下:

1 /*

2 * @author Haybl3 * @version 1.04 */

5 importandroid.os.Parcel;6 importandroid.os.Parcelable;7

8 importjava.util.ArrayList;9 /**

10 * ConflictProgram Information11 */

12 public class ConflictProgram implementsParcelable {13 /**state of LCN resolved*/

14 public intresolveState;15 /**number of total list*/

16 public intlistNum;17 /**the service of one list that gets allocated LCN. Be pair with VctConfProg*/

18 public int[] allocLCNService = new int[Constants.MAX_CONFLICT_PROGRAM_LIST_NUM];19 /**Program information*/

20 public List[] confProgList = newList[Constants.MAX_CONFLICT_PROGRAM_LIST_NUM];21

22 publicConflictProgram() {23 resolveState = 0;24 listNum = 0;25 for(int i = 0; i < this.allocLCNService.length; ++i) {26 this.allocLCNService[i] = 0;27 }28 for (int i = 0; i < confProgList.length; ++i) {29 confProgList[i] = new ArrayList();30 }31 }32

33 publicConflictProgram(Parcel in) {34 resolveState =in.readInt();35 listNum =in.readInt();36 allocLCNService =in.createIntArray();37 for(int i = 0; i < this.confProgList.length; ++i) {38 in.createTypedArrayList(ConflictProgramInfo.CREATOR);39 }40 }41

42 @Override43 public void writeToParcel(Parcel dest, intflags) {44 dest.writeInt(resolveState);45 dest.writeInt(listNum);46 dest.writeIntArray(allocLCNService);47 for(int i = 0; i < this.confProgList.length; ++i) {48 dest.writeTypedList(this.confProgList[i]);49 }50 }51

52 @Override53 public intdescribeContents() {54 return 0;55 }56

57 public static final Creator CREATOR = new Creator() {58 @Override59 publicConflictProgram createFromParcel(Parcel in) {60 return newConflictProgram(in);61 }62

63 @Override64 public ConflictProgram[] newArray(intsize) {65 return newConflictProgram[size];66 }67 };68 }

View Code

1 /*@author Haybl2 * @version 1.03 */

4 importandroid.os.Parcel;5 importandroid.os.Parcelable;6

7 /**

8 * ConflictProgram Information9 */

10 public class ConflictProgramInfo implementsParcelable {11 /**program information of program number*/

12 public intnumber;13 /**program information of program index*/

14 public intindex;15 /**ServiceID*/

16 public intserviceID;17 /**program information of program service type*/

18 public intserviceType;19 /**program information of program service name*/

20 publicString serviceName;21

22 public static final Creator CREATOR = new Creator() {23 publicConflictProgramInfo createFromParcel(Parcel in) {24 return newConflictProgramInfo(in);25 }26

27 public ConflictProgramInfo[] newArray(intsize) {28 return newConflictProgramInfo[size];29 }30 };31

32 publicConflictProgramInfo(Parcel in) {33 number = (int) in.readInt();34 index =in.readInt();35 serviceID = (int) in.readInt();36 serviceType =in.readInt();37 serviceName =in.readString();38 }39

40 public ConflictProgramInfo(int number, int index, int serviceID, intserviceType, String serviceName) {41 super();42 this.number =number;43 this.index =index;44 this.serviceID =serviceID;45 this.serviceType =serviceType;46 this.serviceName =serviceName;47 }48

49 publicConflictProgramInfo() {50 number = 0;51 index = 0;52 serviceID = 0;53 serviceType = 0;54 serviceName = "";55 }56

57 public ConflictProgramInfo(intindex) {58 number = 0;59 index = 0;60 serviceID = 0;61 serviceType = 0;62 serviceName = "";63 }64

65 @Override66 public intdescribeContents() {67 return 0;68 }69

70 @Override71 public void writeToParcel(Parcel dest, intflags) {72 dest.writeInt(number);73 dest.writeInt(index);74 dest.writeInt(serviceID);75 dest.writeInt(serviceType);76 dest.writeString(serviceName);77 }78 }

View Code

可以看到两个数据类都实现了Parcelable接口,需要在各层次之间传递(跨进程),必须得实现此接口,还需要对应的定义出AIDL文件(很简单,不贴代码了)。接着进入解决流程:

1)Android 6.0:

由于电视系统架构在了M和O两个Android版本之上,并且考虑到版本之间的差异,需要依据具体版本具体实现接口添加。首先看下Android M的流程:

A.应用层:应用层只是基本逻辑添加,较为简单,主要注意自定义UI风格、循环处理冲突节目以及数据下发格式三个方面,此处略。

B. Framework层:分为tv及api两层

I.  ../tv2/

java层:IChannel.aidl(接口声明),ChannelManager.java中添加对应接口,此处为第一次Binder机制,以AIDL形式呈现。IChannel定义了对节目操作的接口,ChannelManager作为客户端调用IChannel中的方法,这些方法都在服务端实现。

1 /**

2 * Set conflict to resolve LCN .3 *4 *@paramlistindex int5 *@paramselectindex int6 *@returnboolean7 */

8 public boolean setConflictLCNListIndex(int listindex, intselectindex) {9 try{10 Log.d(TAG, "setConflictLCNListIndex(), listindex = " + listindex + ", selectindex = " +selectindex);11 returnmService.setConflictLCNListIndex(listindex, selectindex);12 } catch(RemoteException e) {13 e.printStackTrace();14 }15 return false;16 }

MService层: ChannelService.java中添加对应接口(注意方法名称校验添加)。此处即为服务端,实现了IChannel.aidl中所定义的接口,其具体实现又依赖于api层逻辑。

1 @Override2 public boolean setConflictLCNListIndex(int listindex, int selectindex) throwsRemoteException {3 boolean result = false;4 try{5 if (Manager.getScanManager() != null) {7 result =Manager.getScanManager().setConflictLCNServiceListIndex(listindex, selectindex);9 }10 } catch(CommonException e) {11 e.printStackTrace();12 }13 returnresult;14 }

II.  ../api/

java层:ScanManager.java,ScanManagerImpl.java 中添加对应接口,并在包中导入定义的数据类型Bean。ScanManagerImpl中多为jni调用(native修饰),对于setConflictLCNServiceListIndex接口,由于没有返回节目数据,可直接调用jni的native实现;对于getConflictProgramList接口,需要对数据手动编码实现反序列化。

1 public native final boolean setConflictLCNServiceListIndex(int listindex, int selectindex) throwsTvCommonException;2

3 public final ConflictProgram getConflictProgramList() throwsCommonException {4 Parcel reply =Parcel.obtain();5 native_getConflictProgramList(reply);6

7 DtvConflictProgram result = newDtvConflictProgram();8 int ret = reply.readInt(); //not 0: get conflict program list success; 0: failure.

9 if (ret == 0) {10 result.listNum = 0;11 } else{12 result.resolveState =reply.readInt();13 result.listNum =reply.readInt();14 //the max conflict program list num is 30

15 for (int i = 0; i < result.listNum && i < Constants.MAX_CONFLICT_PROGRAM_LIST_NUM; i++) {16 result.allocLCNService[i] =reply.readInt();17 }18 for (int i = 0; i < result.listNum; i++) {19 int listIdsize =reply.readInt();20 if (listIdsize == 0) {21 continue;22 }23 for (int j = 0; j < listIdsize; j++) {24 ConflictProgramInfo cpi = newConflictProgramInfo();25 cpi.number =reply.readInt();26 cpi.index =reply.readInt();27 cpi.serviceID =reply.readInt();28 cpi.serviceType =reply.readInt();29 cpi.serviceName =reply.readString();30 result.confProgList[i].add(cpi);31 }32 }33 }34 reply.recycle();35 returnresult;36 }37

38 public native final boolean native_getConflictProgramList(Parcel reply) throws CommonException;

View Code

可以看到此处就用到了第一章节提到的Parcel数据容器,至于其中的数据为何这样解析,需要获取到如何封装的数据,稍后会给出。

JNI层:com_android_api_impl_ScanManagerImpl.cpp中添加对应接口。此处直接调用Supernova层的接口(C++)。为何能直接调用?因为在第一次编译Android源码的时候,需要将Sn软链接过来:ln -s ../../../xxx,即可直接调用。

1 /*

2 * Class: com_android_api_impl_ScanManagerImpl3 * Method: setConflictLCNServiceListIndex4 * Signature: (II)Z5 */

6 jboolean com_android_api_impl_ScanManagerImpl_setConflictLCNServiceListIndex(JNIEnv *env, jobject thiz, jint listindex, jint selectindex) {8 ALOGI("setConflictLCNServiceListIndex");9 sp ms =getScanManagerImpl(env, thiz);10 if (ms ==NULL) {11 ALOGI("setConflictLCNServiceListIndex:ms == NULL");12 jniThrowException(env, "com/android/api/common/exception/IpcException", "can not connect to server");13 return 0;14 }15 ALOGI("setConflictLCNServiceListIndex:ms != NULL");16 return ms->setConflictLCNServiceListIndex(listindex, selectindex);17 }

C. Supernova层:

ScanManagerImpl.cpp被Android层直接调用,发生的第二次Binder机制调用,作为客户端通过Binder与ScanManagerImplService.cpp通信。ScanManagerImplService作为服务,具体实现了上述两个接口。IScanManagerImpl.cpp中实现了远程调用。

首先调到到此方法,此处发起调用请求:

1 bool ScanManagerImpl::setConflictLCNServiceListIndex(int32_t listindex, int32_t selectindex)2 {3 ALOGV("ScanManagerImpl getConflictProgramList\n");4 /*

5 * mScanManagerImpl是一个BpBinder(IScanManagerImpl的一个代理BpScanManagerImpl)6 * BpBinder是与BnBinder通信用,也即是BpScanManagerImpl与BnScanManagerImpl通信7 * BnScanManagerImpl: public BnInterface8 * BpScanManagerImpl: public BpInterface9 * Bp端通过remote->transact()将client端请求发给Bn端,Bn端则通过onTransact()处理接收到的请求10 */

11 return mScanManagerImpl->setConflictLCNServiceListIndex(listindex, selectindex);12 }

过程 - Binder客户端:

1 virtual boolsetConflictLCNServiceListIndex(int32_t listindex, int32_t selectindex)2 {3 bool ret = false;4 printf("Send SCANMANAGER_SET_CONFLICT_SERVICE_LIST_INDEX\n");5 Parcel data, reply;6 data.writeInterfaceToken(IScanManagerImpl::getInterfaceDescriptor());7 data.writeInt32(listindex);8 data.writeInt32(selectindex);9 //发起远程调用

10 remote()->transact(SCANMANAGER_SET_CONFLICT_SERVICE_LIST_INDEX, data, &reply);11 ret = static_cast(reply.readInt32());12 returnret;13 }

Binder - 服务端:

1   caseSCANMANAGER_SET_CONFLICT_SERVICE_LIST_INDEX:2   printf("Receive SCANMANAGER_SET_CONFLICT_SERVICE_LIST_INDEX\n");3 CHECK_INTERFACE(IScanManagerImpl, data, reply);4      int32_t listindex =(int32_t)data.readInt32();5 int32_t selectindex =(int32_t)data.readInt32();6       //接收到请求,调用具体实现(ScanManagerImplService.cpp中实现)

7 reply->writeInt32(setConflictLCNServiceListIndex(listindex, selectindex));8 break;

具体实现在ScanManagerImplService.cpp中,终于解开神秘的面纱了,这里的方法调用了在扫描时存入的冲突数据(数据库形式),设置冲突也就相当于修改数据库了:

1 ST_CONFLICT_PROGRAM *pstConfProg = pMsrvD->GetConflictProgramList();2

3 pPlayer->SetConflictLCNServiceListIndex(listindex, selectindex);

自此Android 6.0的问题基本已解决完成,接下来看看8.0 的方法。

2)Android 8.0:

Android 8 中增加了一层hidl层,在Mstar平台上表现出也相对复杂些。

HIDL:HAL interface definition language(硬件抽象层接口定义语言),在此之前Android有AIDL,架构在Android binder 之上,用来定义Android 基于Binder通信的Client 与Service之间的接口。HIDL也是类似的作用,只不过定义的是Android Framework与Android HAL实现之间的接口。

8.0 jni以上都与Android6一致,在api中新增了hidl包装层。

B. Framework层:

II.  ../api/

hidl_wrapper:ScanManagerImpl.cpp、ScanManagerImpl.h中添加对应接口。.h声明接口,.cpp具体实现。具体实现中调用了vendor/mstar/中的hardware层的代码。

III.  vendor/mstar/

interfaces:IMstarInput.hal、Input.h、Input_ScanManagerImpl.cpp、mstarInput_ScanManagerImpl_d.h中添加对应接口。

1 boolmstar_input_ScanManagerImpl::setConflictLCNServiceListIndex(int32_t listindex, int32_t selectindex) {2 return g_pScanManagerImplImpl->setConflictLCNServiceListIndex(listindex, selectindex);3 }

tv_input:mstar_input_ScanManagerImpl.cpp、mstar_nput_ScanManagerImpl.h中添加对应接口。在mstar_input.cpp中统一注册并连接服务端。

Supernova层与6.0一致。关于HIDL的内容很多,这里只简单加了个接口,依葫芦画瓢,详细原理待后续研究 .....

3.原理分析

此处只分析在Android与Supernova之间的Binder原理,Android framework层的Binder机制表现为AIDL形式,使用方式及原理已在aidl处分析。

五、总结

1.为什么要使用Binder?

性能方面更加高效。Binder数据拷贝只需要一次,而管道、消息队列、Socket都需要2次,共享内存方式一次内存拷贝都不需要,但实现方式比较复杂。安全方面,Binder机制从协议本身就支持对通信双方做身份校检,因而大大提升了安全性。

2.为什么需要AIDL?

使用简单。客户端和服务端进行通讯,若直接使用Binder需先将请求转换成序列化的数据,然后调用transact()函数发送给服务端,并且控制参数顺序,服务端和客户端都必须一致,否则就会出错。这样的过程很麻烦,如果有上百个接口,并且都需要手动编写传输接口,那可就很麻烦了。AIDL调用服务端方法如调用自身方法一样简单快捷烦。

3.面向接口编程

封装、继承、多态

4.接下来学习方向

1)Binder连接池及服务端主动通知客户端方法

2)HIDL原理

3)Binder启动

六、参考链接

1.https://www.jianshu.com/p/4920c7781afe

2.http://www.imooc.com/article/17958

3.https://www.jianshu.com/p/29999c1a93cd

4.https://blog.csdn.net/lei7143/article/details/80931412

5.https://www.jianshu.com/p/4920c7781afe

6.https://blog.csdn.net/xude1985/article/details/9232049

7.http://gityuan.com/2015/10/31/binder-prepare/

8.《MStar 开发指导》.P113

9.《Android开发艺术探索》.任玉刚 .P42

10.《MStar Android网络电视总结》

hidl 原理分析_AIDL原理分析相关推荐

  1. Android10.0 Binder通信原理(八)-Framework层分析

    摘要:本节主要来讲解Android10.0 Binder 在Framework的使用分析 阅读本文大约需要花费15分钟. 文章首发微信公众号:IngresGe 专注于Android系统级源码分析,An ...

  2. Android10.0 Binder通信原理(五)-Binder驱动分析

    摘要:本节主要来讲解Android10.0 Binder的驱动层分析 阅读本文大约需要花费35分钟. 文章首发微信公众号:IngresGe 专注于Android系统级源码分析,Android的平台设计 ...

  3. 编译原理(六)自底向上分析之LR分析法

    自底向上分析之LR分析法 说明:以老师PPT为标准,借鉴部分教材内容,AlvinZH学习笔记. 基本概念 1. LR分析:从左到右扫描(L)自底向上进行规约(R),是规范规约,也即最右推导(规范推导) ...

  4. ConcurrentHashMap实现原理及源码分析

    ConcurrentHashMap是Java并发包中提供的一个线程安全且高效的HashMap实现(若对HashMap的实现原理还不甚了解,可参考我的另一篇文章HashMap实现原理及源码分析),Con ...

  5. concurrenthashmap_ConcurrentHashMap实现原理及源码分析

    ConcurrentHashMap是Java并发包中提供的一个线程安全且高效的HashMap实现(若对HashMap的实现原理还不甚了解,可参考我的另一篇文章HashMap实现原理及源码分析),Con ...

  6. MyBatis 源码分析 - 缓存原理

    1.简介 在 Web 应用中,缓存是必不可少的组件.通常我们都会用 Redis 或 memcached 等缓存中间件,拦截大量奔向数据库的请求,减轻数据库压力.作为一个重要的组件,MyBatis 自然 ...

  7. SIFT原理与源码分析:DoG尺度空间构造

    <SIFT原理与源码分析>系列文章索引:http://blog.csdn.net/xiaowei_cqu/article/details/8069548 尺度空间理论 自然界中的物体随着观 ...

  8. java校验框架源码解析_Spring Boot原理剖析和源码分析

    Spring Boot原理剖析和源码分析 依赖管理 问题一:为什么导入dependency时不需要指定版本? spring-boot-starter-parent依赖 org.springframew ...

  9. 典型相关分析(cca)原理_CCA典型关联分析原理与Python案例

    文章来源于"脑机接口社区" CCA典型关联分析原理与Python案例​mp.weixin.qq.com Rose今天分享一下CCA的相关原理以及Python应用,CCA在EEG等脑 ...

最新文章

  1. 用手指触碰电子,用心灵感受震荡
  2. pcb天线和纯铜天线_一种2.4GHz水平极化全向天线设计
  3. 给插店一个成功案例给自己一次机会
  4. css less 不要作用到子对象_CSS-预处理语言Sass、Less简述
  5. JAVA开发面试常问问题总结3
  6. SQL注入学习part07:(SQL注入语句总结)
  7. 自己调用NTDLL函数
  8. Object-c 内存管理
  9. 沈志勇:中国式营销三部曲
  10. 查看python源码位置方法以及潜在误区
  11. [转]KSN报告:2014 - 2016年的PC勒索软件
  12. Authing 实力上榜安全牛《中国网络安全行业全景图》
  13. 先有鸡还有现有的蛋的终极答案
  14. 14个令人惊叹的Ionic应用程序模板
  15. 观影《铁拳男人》有感
  16. ORB_SLAM2及其他SLAM精度测评
  17. react 生命周期详解
  18. BTC反弹上攻失效 回踩重点关11000
  19. 一种二值图像封闭孔洞的高效填充算法
  20. python五子棋实验报告_Python 五子棋

热门文章

  1. Android实战简易教程-第七十五枪(WIFI直连工具类)
  2. STM32 Cube IDE HAL库驱动 W25Q128 进行读、写、擦除操作
  3. 5 蒙特卡洛方法 (Monte Carlo Method)
  4. UDP IPv4广播地址计算(附Node.js示例代码)
  5. 怎么批量创建文件夹并命名?
  6. 被「李笑来老师」拉黑之「JavaScript微博自动转发的脚本」
  7. 德语环境下 小数点格式化时数字格式异常问题
  8. 速方云怎么下载keep2share文件?
  9. 卷积滤波 英文_图形学之卷积滤波器
  10. Qt QPainter鼠标绘制线条、矩形、多边形