季春初始,天气返暖,新冠渐去,正值学习好时机。在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 implements Parcelable { 2 3 private String name; 4 5 public User(String name) { 6 this.name = name; 7 } 8 9 protected User(Parcel in) { 10 name = in.readString(); 11 } 12 13 public String getName() { 14 return name; 15 } 16 17 public void setName(String name) { 18 this.name = name; 19 } 20 21 public static final CreatorCREATOR = new Creator () { 22 @Override 23 public User createFromParcel(Parcel in) { 24 return new User(in); 25 } 26 27 @Override 28 public User[] newArray(int size) { 29 return new User[size]; 30 } 31 }; 32 33 @Override 34 public int describeContents() { 35 return 0; 36 } 37 38 @Override 39 public void writeToParcel(Parcel dest, int flags) { 40 dest.writeString(name); 41 } 42 43 public void readFromParcel(Parcel in) { 44 this.name = in.readString(); 45 } 46 }
1 // UserControl.aidl 2 package com.haybl.aidl_test; 3 4 import com.haybl.aidl_test.User; 5 6 // Declare any non-default types here with import statements 7 8 interface UserController { 9 10 ListgetUserList(); 11 12 void addUserInOut(inout User user); 13 }
2)服务端
复制上述两个AIDL文件至服务端代码。创建 Service,在其中创建上面生成的 Binder 对象实例,实现接口定义的方法在 onBind() 中返回。
1 @Override 2 public IBinder onBind(Intent intent) { 3 Log.d(TAG, "onBind"); 4 return stub; 5 } 6 7 private UserController.Stub stub = new UserController.Stub() { 8 @Override 9 public ListgetUserList() throws RemoteException { 10 Log.d(TAG, "getUserList"); 11 return mUserList; 12 } 13 14 @Override 15 public void addUserInOut(User user) throws RemoteException { 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 void bindService() { 2 Log.d(TAG, "bindService"); 3 Intent intent = new Intent(); 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 = new ServiceConnection() { 11 @Override 12 public void onServiceConnected(ComponentName name, IBinder service) { 13 Log.d(TAG, "onServiceConnected, name = " + name.getPackageName()); 14 mUserController = UserController.Stub.asInterface(service); 15 isConnected = true; 16 } 17 18 @Override 19 public void onServiceDisconnected(ComponentName name) { 20 isConnected = false; 21 Log.d(TAG, "onServiceDisconnected, name = " + name.getPackageName()); 22 } 23 };
4.代码分析
原理分析分析离不开代码,aidl在build之后会生成一个对应的接口java文件,aidl文件本身的作用就是生成这个java文件,后续的操作都在这个java接口上进行的。直接放生成的文件,根据其中的注释可以很好的理解的原理:
1 package com.haybl.aidl_test; 2 3 /** 4 * 所有在Binder中传输的接口都必须实现IInterface接口 5 */ 6 public interface UserController extends android.os.IInterface { 7 /** 8 * 本地IPC实施存根类:为内部静态类,继承android.os.Binder、实现com.haybl.aidl_test.UserController(本接口) 9 */ 10 public static abstract class Stub extends android.os.Binder implements com.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 public Stub() { 29 this.attachInterface(this, DESCRIPTOR); 30 } 31 32 /** 33 * 将IBinder对象转换为com.haybl.aidl_test.UserController接口。 34 * 用于将服务端的Binder对象转换为客户端所需要的接口对象,该过程区分进程, 35 * 如果进程一样,就返回服务端Stub对象本身,否则就返回封装后的Stub.Proxy对象。 36 */ 37 public static com.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 instanceof com.haybl.aidl_test.UserController))) { 49 return ((com.haybl.aidl_test.UserController) iin); 50 } 51 return new com.haybl.aidl_test.UserController.Stub.Proxy(obj); 52 } 53 54 /** 55 * android.os.IInterface接口方法实现 56 */ 57 @Override 58 public android.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 * @param code 要执行的动作标志。在IBinder.FIRST_CALL_TRANSACTION 和 IBinder.LAST_CALL_TRANSACTION之间 69 * @param data 从调用方接收到的数据。 70 * @param reply 如果调用方需要返回结果,则应将其从此处返回。 71 * @param flags 附加操作标志。正常RPC为0,one-way类型的RPC为1。 72 * 73 * @return return 是否成功 true 返回成功 74 * false 客户端的请求失败(可以用来做权限控制) 75 */ 76 @Override 77 public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException { 78 switch (code) { 79 // IBinder协议事务代码,写入标准接口描述符DESCRIPTOR。 80 case INTERFACE_TRANSACTION: { 81 reply.writeString(DESCRIPTOR); 82 return true; 83 } 84 // getUserList 方法 85 case TRANSACTION_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 case TRANSACTION_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 implements com.haybl.aidl_test.UserController { 122 private android.os.IBinder mRemote; 123 124 Proxy(android.os.IBinder remote) { 125 mRemote = remote; 126 } 127 128 @Override 129 public android.os.IBinder asBinder() { 130 return mRemote; 131 } 132 133 /** 134 * 返回描述符DESCRIPTOR,在Binder的onTransact方法中需要写入此描述:case INTERFACE_TRANSACTION 135 */ 136 public java.lang.String getInterfaceDescriptor() { 137 return DESCRIPTOR; 138 } 139 140 @Override 141 public java.util.List getUserList() throws android.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 @Override 159 public void addUserInOut(com.haybl.aidl_test.User user) throws android.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() throws android.os.RemoteException; 194 195 public void addUserInOut(com.haybl.aidl_test.User user) throws android.os.RemoteException; 196 }
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 string sServiceName; 27 } ST_CONFLICT_PROGRAM_INFO;
从数据结构中可以看出,getConflict返回的这个数据体就包含了所有冲突的节目List<>,其中的每一个节目又有一个数组来保存其冲突情况(ST_CONFLICT_PROGRAM_INFO)。拿到数据后先安装Android的做法定义出Java版本的数据,因为这样才能给上层应用调用。如下:
1 /* 2 * @author Haybl 3 * @version 1.0 4 */ 5 import android.os.Parcel; 6 import android.os.Parcelable; 7 8 import java.util.ArrayList; 9 /** 10 * ConflictProgram Information 11 */ 12 public class ConflictProgram implements Parcelable { 13 /** state of LCN resolved */ 14 public int resolveState; 15 /** number of total list */ 16 public int listNum; 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 = new List[Constants.MAX_CONFLICT_PROGRAM_LIST_NUM]; 21 22 public ConflictProgram() { 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 public ConflictProgram(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 @Override 43 public void writeToParcel(Parcel dest, int flags) { 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 @Override 53 public int describeContents() { 54 return 0; 55 } 56 57 public static final Creator CREATOR = new Creator () { 58 @Override 59 public ConflictProgram createFromParcel(Parcel in) { 60 return new ConflictProgram(in); 61 } 62 63 @Override 64 public ConflictProgram[] newArray(int size) { 65 return new ConflictProgram[size]; 66 } 67 }; 68 }
1 /* @author Haybl 2 * @version 1.0 3 */ 4 import android.os.Parcel; 5 import android.os.Parcelable; 6 7 /** 8 * ConflictProgram Information 9 */ 10 public class ConflictProgramInfo implements Parcelable { 11 /** program information of program number */ 12 public int number; 13 /** program information of program index */ 14 public int index; 15 /** ServiceID */ 16 public int serviceID; 17 /** program information of program service type */ 18 public int serviceType; 19 /** program information of program service name */ 20 public String serviceName; 21 22 public static final CreatorCREATOR = new Creator () { 23 public ConflictProgramInfo createFromParcel(Parcel in) { 24 return new ConflictProgramInfo(in); 25 } 26 27 public ConflictProgramInfo[] newArray(int size) { 28 return new ConflictProgramInfo[size]; 29 } 30 }; 31 32 public ConflictProgramInfo(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, int serviceType, 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 public ConflictProgramInfo() { 50 number = 0; 51 index = 0; 52 serviceID = 0; 53 serviceType = 0; 54 serviceName = ""; 55 } 56 57 public ConflictProgramInfo(int index) { 58 number = 0; 59 index = 0; 60 serviceID = 0; 61 serviceType = 0; 62 serviceName = ""; 63 } 64 65 @Override 66 public int describeContents() { 67 return 0; 68 } 69 70 @Override 71 public void writeToParcel(Parcel dest, int flags) { 72 dest.writeInt(number); 73 dest.writeInt(index); 74 dest.writeInt(serviceID); 75 dest.writeInt(serviceType); 76 dest.writeString(serviceName); 77 } 78 }
可以看到两个数据类都实现了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 * @param listindex int 5 * @param selectindex int 6 * @return boolean 7 */ 8 public boolean setConflictLCNListIndex(int listindex, int selectindex) { 9 try { 10 Log.d(TAG, "setConflictLCNListIndex(), listindex = " + listindex + ", selectindex = " + selectindex); 11 return mService.setConflictLCNListIndex(listindex, selectindex); 12 } catch (RemoteException e) { 13 e.printStackTrace(); 14 } 15 return false; 16 }
MService层: ChannelService.java中添加对应接口(注意方法名称校验添加)。此处即为服务端,实现了IChannel.aidl中所定义的接口,其具体实现又依赖于api层逻辑。
1 @Override 2 public boolean setConflictLCNListIndex(int listindex, int selectindex) throws RemoteException { 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 return result; 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) throws TvCommonException; 2 3 public final ConflictProgram getConflictProgramList() throws CommonException { 4 Parcel reply = Parcel.obtain(); 5 native_getConflictProgramList(reply); 6 7 DtvConflictProgram result = new DtvConflictProgram(); 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 = new ConflictProgramInfo(); 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 return result; 36 } 37 38 public native final boolean native_getConflictProgramList(Parcel reply) throws CommonException;
可以看到此处就用到了第一章节提到的Parcel数据容器,至于其中的数据为何这样解析,需要获取到如何封装的数据,稍后会给出。
JNI层:com_android_api_impl_ScanManagerImpl.cpp中添加对应接口。此处直接调用Supernova层的接口(C++)。为何能直接调用?因为在第一次编译Android源码的时候,需要将Sn软链接过来:ln -s ../../../xxx,即可直接调用。
1 /* 2 * Class: com_android_api_impl_ScanManagerImpl 3 * Method: setConflictLCNServiceListIndex 4 * Signature: (II)Z 5 */ 6 jboolean com_android_api_impl_ScanManagerImpl_setConflictLCNServiceListIndex(JNIEnv *env, jobject thiz, jint listindex, jint selectindex) { 8 ALOGI("setConflictLCNServiceListIndex"); 9 spms = 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 BpInterface 9 * Bp端通过remote->transact()将client端请求发给Bn端,Bn端则通过onTransact()处理接收到的请求 10 */ 11 return mScanManagerImpl->setConflictLCNServiceListIndex(listindex, selectindex); 12 }
过程 - Binder客户端:
1 virtual bool setConflictLCNServiceListIndex(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<bool>(reply.readInt32()); 12 return ret; 13 }
Binder - 服务端:
1 case SCANMANAGER_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 bool mstar_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网络电视总结》