季春初始,天气返暖,新冠渐去,正值学习好时机。在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对象,包含一个名字属性,并定义了一个控制接口,添加和查询。

 public class User implements Parcelable {

     private String name;

     public User(String name) {
this.name = name;
} protected User(Parcel in) {
name = in.readString();
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public static final Creator<User> CREATOR = new Creator<User>() {
@Override
public User createFromParcel(Parcel in) {
return new User(in);
} @Override
public User[] newArray(int size) {
return new User[size];
}
}; @Override
public int describeContents() {
return 0;
} @Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
} public void readFromParcel(Parcel in) {
this.name = in.readString();
}
}
 // UserControl.aidl
package com.haybl.aidl_test; import com.haybl.aidl_test.User; // Declare any non-default types here with import statements interface UserController { List<User> getUserList(); void addUserInOut(inout User user);
}

2)服务端

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

 1   @Override
public IBinder onBind(Intent intent) {
Log.d(TAG, "onBind");
return stub;
} private UserController.Stub stub = new UserController.Stub() {
@Override
public List<User> getUserList() throws RemoteException {
Log.d(TAG, "getUserList");
return mUserList;
} @Override
public void addUserInOut(User user) throws RemoteException {
if (user != null) {
Log.d(TAG, "Server receive a new user by InOut = " + user.getName());
// 服务端改变数据,通过InOut Tag会同步到客户端。数据是双向流动的
user.setName("I'm changed");
mUserList.add(user);
} else {
Log.d(TAG, "Server receive a null by InOut");
}
}
};

3)客户端

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

   private void bindService() {
Log.d(TAG, "bindService");
Intent intent = new Intent();
// android 5.0以后直设置action不能启动相应的服务,需要设置packageName或者Component
intent.setPackage("com.haybl.aidl_server");
intent.setAction("com.haybl.aidl_server.action");
bindService(intent, connection, BIND_AUTO_CREATE);
} private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.d(TAG, "onServiceConnected, name = " + name.getPackageName());
mUserController = UserController.Stub.asInterface(service);
isConnected = true;
} @Override
public void onServiceDisconnected(ComponentName name) {
isConnected = false;
Log.d(TAG, "onServiceDisconnected, name = " + name.getPackageName());
}
};

4.代码分析

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

 package com.haybl.aidl_test;

 /**
* 所有在Binder中传输的接口都必须实现IInterface接口
*/
public interface UserController extends android.os.IInterface {
/**
* 本地IPC实施存根类:为内部静态类,继承android.os.Binder、实现com.haybl.aidl_test.UserController(本接口)
*/
public static abstract class Stub extends android.os.Binder implements com.haybl.aidl_test.UserController {
/**
* Binder的唯一标识
*/
private static final java.lang.String DESCRIPTOR = "com.haybl.aidl_test.UserController"; /**
* 接口中的方法标志,个数与定义的方法个数一致且一一对应,在onTransact()中使用
*/
static final int TRANSACTION_getUserList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_addUserInOut = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1); /**
* 构造方法,将其附加到接口
*
* attachInterface()方法将特定接口与Binder相关联。
* 调用后,可通过queryLocalInterface()方法,在当请求符合描述符(DESCRIPTOR)时,返回该IInterface。
*/
public Stub() {
this.attachInterface(this, DESCRIPTOR);
} /**
* 将IBinder对象转换为com.haybl.aidl_test.UserController接口。
* 用于将服务端的Binder对象转换为客户端所需要的接口对象,该过程区分进程,
* 如果进程一样,就返回服务端Stub对象本身,否则就返回封装后的Stub.Proxy对象。
*/
public static com.haybl.aidl_test.UserController asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
} /*
* 针对此obj Binder对象查询接口的本地实现。
* 如果返回null,则需要实例化代理类,通过transact()方法封装调用。非同一进程
* 如果提供的信息与请求的描述符匹配,则返回关联的IInterface。同一进程
*/
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.haybl.aidl_test.UserController))) {
return ((com.haybl.aidl_test.UserController) iin);
}
return new com.haybl.aidl_test.UserController.Stub.Proxy(obj);
} /**
* android.os.IInterface接口方法实现
*/
@Override
public android.os.IBinder asBinder() {
return this;
} /**
* <服务端调用>
* android.os.Binder的onTransact方法实现
* 如果要调用此函数,需调用transact()。transact()实现对onTransact上调用。在远端,将调用到Binder中以进行IPC。
* 该方法是运行在服务端的Binder线程中的,当客户端发起远程请求后,在底层封装后会交由此方法来处理。
*
* @param code 要执行的动作标志。在IBinder.FIRST_CALL_TRANSACTION 和 IBinder.LAST_CALL_TRANSACTION之间
* @param data 从调用方接收到的数据。
* @param reply 如果调用方需要返回结果,则应将其从此处返回。
* @param flags 附加操作标志。正常RPC为0,one-way类型的RPC为1。
*
* @return return 是否成功 true 返回成功
* false 客户端的请求失败(可以用来做权限控制)
*/
@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
switch (code) {
// IBinder协议事务代码,写入标准接口描述符DESCRIPTOR。
case INTERFACE_TRANSACTION: {
reply.writeString(DESCRIPTOR);
return true;
}
// getUserList 方法
case TRANSACTION_getUserList: {
data.enforceInterface(DESCRIPTOR);
// 服务端调用具体实现 this.getUserList
java.util.List<com.haybl.aidl_test.User> _result = this.getUserList();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
// addUserInOut 方法
case TRANSACTION_addUserInOut: {
data.enforceInterface(DESCRIPTOR);
com.haybl.aidl_test.User _arg0;
if ((0 != data.readInt())) {
_arg0 = com.haybl.aidl_test.User.CREATOR.createFromParcel(data);
} else {
_arg0 = null;
}
// 服务端调用具体实现 this.addUserInOut
this.addUserInOut(_arg0);
reply.writeNoException();
if ((_arg0 != null)) {
reply.writeInt(1);
_arg0.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
} else {
reply.writeInt(0);
}
return true;
}
}
return super.onTransact(code, data, reply, flags);
} /*
* com.haybl.aidl_test.UserController代理类
* <客户端调用>
*/
private static class Proxy implements com.haybl.aidl_test.UserController {
private android.os.IBinder mRemote; Proxy(android.os.IBinder remote) {
mRemote = remote;
} @Override
public android.os.IBinder asBinder() {
return mRemote;
} /**
* 返回描述符DESCRIPTOR,在Binder的onTransact方法中需要写入此描述:case INTERFACE_TRANSACTION
*/
public java.lang.String getInterfaceDescriptor() {
return DESCRIPTOR;
} @Override
public java.util.List<com.haybl.aidl_test.User> getUserList() throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.List<com.haybl.aidl_test.User> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
// 远程调用
mRemote.transact(Stub.TRANSACTION_getUserList, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(com.haybl.aidl_test.User.CREATOR);
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
} @Override
public void addUserInOut(com.haybl.aidl_test.User user) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
if ((user != null)) {
_data.writeInt(1);
user.writeToParcel(_data, 0);
} else {
_data.writeInt(0);
}
/*
* 远程调用
* 客户端会被挂起等待服务端执行完成才继续其他代码执行,即同步调用
* 若使用oneway关键字修饰此接口或整个UserController,则不会被挂起,即异步调用
*/
mRemote.transact(Stub.TRANSACTION_addUserInOut, _data, _reply, 0);
_reply.readException(); // InOut定向Tag生成的对客户端对象修改的代码
if ((0 != _reply.readInt())) {
user.readFromParcel(_reply);
}
} finally {
_reply.recycle();
_data.recycle();
}
}
}
} /**
* 定义的方法,在实现Stub 的时候需要实现这些方法
*/
public java.util.List<com.haybl.aidl_test.User> getUserList() throws android.os.RemoteException; public void addUserInOut(com.haybl.aidl_test.User user) throws android.os.RemoteException;
}

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 工程师给出如下接口:

 // 获取冲突节目列表
bool ScanManagerImplService::Client::getConflictProgramList(Parcel *reply) //设置单个冲突节目
bool ScanManagerImplService::Client::setConflictLCNServiceListIndex(uint8_t listindex, uint8_t selectindex)

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

 /// Define conflict program struct
typedef struct
{
/// state of LCN resolved
U8 u8ResolveState;
/// number of total list
U16 u16ListNum;
/// the service of one list that gets allocated LCN. Be pair with VctConfProg.
U16 u16AllocLCNService[MAX_CONFLICT_PROGRAM_LIST_NUM];
/// Program information
list<ST_CONFLICT_PROGRAM_INFO> ConfProgList[MAX_CONFLICT_PROGRAM_LIST_NUM];
} ST_CONFLICT_PROGRAM; /// Define conflict program info struct
typedef struct
{
/// program information number
U16 u16Number;
/// program index of DB
U32 u32DbIndex;
/// Service ID
U16 u16ServiceID;
/// program information service type
U8 u8ServiceType;
/// program information service name
string sServiceName;
} ST_CONFLICT_PROGRAM_INFO;

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

 /*
* @author Haybl
* @version 1.0
*/
import android.os.Parcel;
import android.os.Parcelable; import java.util.ArrayList;
/**
* ConflictProgram Information
*/
public class ConflictProgram implements Parcelable {
/** state of LCN resolved */
public int resolveState;
/** number of total list */
public int listNum;
/** the service of one list that gets allocated LCN. Be pair with VctConfProg */
public int[] allocLCNService = new int[Constants.MAX_CONFLICT_PROGRAM_LIST_NUM];
/** Program information */
public List[] confProgList = new List[Constants.MAX_CONFLICT_PROGRAM_LIST_NUM]; public ConflictProgram() {
resolveState = 0;
listNum = 0;
for(int i = 0; i < this.allocLCNService.length; ++i) {
this.allocLCNService[i] = 0;
}
for (int i = 0; i < confProgList.length; ++i) {
confProgList[i] = new ArrayList<ConflictProgramInfo>();
}
} public ConflictProgram(Parcel in) {
resolveState = in.readInt();
listNum = in.readInt();
allocLCNService = in.createIntArray();
for(int i = 0; i < this.confProgList.length; ++i) {
in.createTypedArrayList(ConflictProgramInfo.CREATOR);
}
} @Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(resolveState);
dest.writeInt(listNum);
dest.writeIntArray(allocLCNService);
for(int i = 0; i < this.confProgList.length; ++i) {
dest.writeTypedList(this.confProgList[i]);
}
} @Override
public int describeContents() {
return 0;
} public static final Creator<ConflictProgram> CREATOR = new Creator<ConflictProgram>() {
@Override
public ConflictProgram createFromParcel(Parcel in) {
return new ConflictProgram(in);
} @Override
public ConflictProgram[] newArray(int size) {
return new ConflictProgram[size];
}
};
}
 /* @author Haybl
* @version 1.0
*/
import android.os.Parcel;
import android.os.Parcelable; /**
* ConflictProgram Information
*/
public class ConflictProgramInfo implements Parcelable {
/** program information of program number */
public int number;
/** program information of program index */
public int index;
/** ServiceID */
public int serviceID;
/** program information of program service type */
public int serviceType;
/** program information of program service name */
public String serviceName; public static final Creator<ConflictProgramInfo> CREATOR = new Creator<ConflictProgramInfo>() {
public ConflictProgramInfo createFromParcel(Parcel in) {
return new ConflictProgramInfo(in);
} public ConflictProgramInfo[] newArray(int size) {
return new ConflictProgramInfo[size];
}
}; public ConflictProgramInfo(Parcel in) {
number = (int) in.readInt();
index = in.readInt();
serviceID = (int) in.readInt();
serviceType = in.readInt();
serviceName = in.readString();
} public ConflictProgramInfo(int number, int index, int serviceID, int serviceType, String serviceName) {
super();
this.number = number;
this.index = index;
this.serviceID = serviceID;
this.serviceType = serviceType;
this.serviceName = serviceName;
} public ConflictProgramInfo() {
number = 0;
index = 0;
serviceID = 0;
serviceType = 0;
serviceName = "";
} public ConflictProgramInfo(int index) {
number = 0;
index = 0;
serviceID = 0;
serviceType = 0;
serviceName = "";
} @Override
public int describeContents() {
return 0;
} @Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(number);
dest.writeInt(index);
dest.writeInt(serviceID);
dest.writeInt(serviceType);
dest.writeString(serviceName);
}
}

  可以看到两个数据类都实现了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中的方法,这些方法都在服务端实现。

     /**
* Set conflict to resolve LCN .
*
* @param listindex int
* @param selectindex int
* @return boolean
*/
public boolean setConflictLCNListIndex(int listindex, int selectindex) {
try {
Log.d(TAG, "setConflictLCNListIndex(), listindex = " + listindex + ", selectindex = " + selectindex);
return mService.setConflictLCNListIndex(listindex, selectindex);
} catch (RemoteException e) {
e.printStackTrace();
}
return false;
}

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

 1     @Override
public boolean setConflictLCNListIndex(int listindex, int selectindex) throws RemoteException {
boolean result = false;
try {
if (Manager.getScanManager() != null) {
result = Manager.getScanManager().setConflictLCNServiceListIndex(listindex, selectindex);
}
} catch (CommonException e) {
e.printStackTrace();
}
return result;
}

II.  ../api/

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

 public native final boolean setConflictLCNServiceListIndex(int listindex, int selectindex) throws TvCommonException;

     public final ConflictProgram getConflictProgramList() throws CommonException {
Parcel reply = Parcel.obtain();
native_getConflictProgramList(reply); DtvConflictProgram result = new DtvConflictProgram();
int ret = reply.readInt(); // not 0: get conflict program list success; 0: failure.
if (ret == 0) {
result.listNum = 0;
} else {
result.resolveState = reply.readInt();
result.listNum = reply.readInt();
// the max conflict program list num is 30
for (int i = 0; i < result.listNum && i < Constants.MAX_CONFLICT_PROGRAM_LIST_NUM; i++) {
result.allocLCNService[i] = reply.readInt();
}
for (int i = 0; i < result.listNum; i++) {
int listIdsize = reply.readInt();
if (listIdsize == 0) {
continue;
}
for (int j = 0; j < listIdsize; j++) {
ConflictProgramInfo cpi = new ConflictProgramInfo();
cpi.number = reply.readInt();
cpi.index = reply.readInt();
cpi.serviceID = reply.readInt();
cpi.serviceType = reply.readInt();
cpi.serviceName = reply.readString();
result.confProgList[i].add(cpi);
}
}
}
reply.recycle();
return result;
} 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,即可直接调用。

 /*
* Class: com_android_api_impl_ScanManagerImpl
* Method: setConflictLCNServiceListIndex
* Signature: (II)Z
*/
jboolean com_android_api_impl_ScanManagerImpl_setConflictLCNServiceListIndex(JNIEnv *env, jobject thiz, jint listindex, jint selectindex) {
ALOGI("setConflictLCNServiceListIndex");
sp<ScanManagerImpl> ms = getScanManagerImpl(env, thiz);
if (ms == NULL) {
ALOGI("setConflictLCNServiceListIndex:ms == NULL");
jniThrowException(env, "com/android/api/common/exception/IpcException", "can not connect to server");
return ;
}
ALOGI("setConflictLCNServiceListIndex:ms != NULL");
return ms->setConflictLCNServiceListIndex(listindex, selectindex);
}

C. Supernova层:

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

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

 bool ScanManagerImpl::setConflictLCNServiceListIndex(int32_t listindex, int32_t selectindex) {
ALOGV("ScanManagerImpl getConflictProgramList\n");
/*
* mScanManagerImpl是一个BpBinder(IScanManagerImpl的一个代理BpScanManagerImpl)
* BpBinder是与BnBinder通信用,也即是BpScanManagerImpl与BnScanManagerImpl通信
* BnScanManagerImpl: public BnInterface<IScanManagerImpl>
* BpScanManagerImpl: public BpInterface<IScanManagerImpl>
* Bp端通过remote->transact()将client端请求发给Bn端,Bn端则通过onTransact()处理接收到的请求
*/
return mScanManagerImpl->setConflictLCNServiceListIndex(listindex, selectindex);
}

  过程 - Binder客户端:

     virtual bool setConflictLCNServiceListIndex(int32_t listindex, int32_t selectindex)
{
bool ret = false;
printf("Send SCANMANAGER_SET_CONFLICT_SERVICE_LIST_INDEX\n");
Parcel data, reply;
data.writeInterfaceToken(IScanManagerImpl::getInterfaceDescriptor());
data.writeInt32(listindex);
data.writeInt32(selectindex);
// 发起远程调用
remote()->transact(SCANMANAGER_SET_CONFLICT_SERVICE_LIST_INDEX, data, &reply);
ret = static_cast<bool>(reply.readInt32());
return ret;
}

    Binder - 服务端:

   case SCANMANAGER_SET_CONFLICT_SERVICE_LIST_INDEX:
  printf("Receive SCANMANAGER_SET_CONFLICT_SERVICE_LIST_INDEX\n");
    CHECK_INTERFACE(IScanManagerImpl, data, reply);
     int32_t listindex = (int32_t)data.readInt32();
int32_t selectindex = (int32_t)data.readInt32();
      // 接收到请求,调用具体实现(ScanManagerImplService.cpp中实现)
reply->writeInt32(setConflictLCNServiceListIndex(listindex, selectindex));
break;

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

 ST_CONFLICT_PROGRAM *pstConfProg  = pMsrvD->GetConflictProgramList();

 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中添加对应接口。

 bool mstar_input_ScanManagerImpl::setConflictLCNServiceListIndex(int32_t listindex, int32_t selectindex) {
return g_pScanManagerImplImpl->setConflictLCNServiceListIndex(listindex, selectindex);
}

  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网络电视总结》

AIDL原理分析的更多相关文章

  1. AIDL使用以及原理分析

    AIDL使用以及IPC原理分析(进程间通信) 概要 为了大家能够更好的理解android的进程间通信原理,以下将会从以下几个方面讲解跨进程通讯信: 1. 必要了解的概念 2. 为什么要使用aidl进程 ...

  2. 老李推荐:第5章5节《MonkeyRunner源码剖析》Monkey原理分析-启动运行: 获取系统服务引用

    老李推荐:第5章5节<MonkeyRunner源码剖析>Monkey原理分析-启动运行: 获取系统服务引用   上一节我们描述了monkey的命令处理入口函数run是如何调用optionP ...

  3. 深入分析AIDL原理

    深入分析AIDL原理 分类: Android2011-11-18 17:29 6522人阅读 评论(1) 收藏 举报 descriptorcallbackservicenullinterfaceser ...

  4. Handler系列之原理分析

    上一节我们讲解了Handler的基本使用方法,也是平时大家用到的最多的使用方式.那么本节让我们来学习一下Handler的工作原理吧!!! 我们知道Android中我们只能在ui线程(主线程)更新ui信 ...

  5. Java NIO使用及原理分析(1-4)(转)

    转载的原文章也找不到!从以下博客中找到http://blog.csdn.net/wuxianglong/article/details/6604817 转载自:李会军•宁静致远 最近由于工作关系要做一 ...

  6. 原子类java.util.concurrent.atomic.*原理分析

    原子类java.util.concurrent.atomic.*原理分析 在并发编程下,原子操作类的应用可以说是无处不在的.为解决线程安全的读写提供了很大的便利. 原子类保证原子的两个关键的点就是:可 ...

  7. Android中Input型输入设备驱动原理分析(一)

    转自:http://blog.csdn.net/eilianlau/article/details/6969361 话说Android中Event输入设备驱动原理分析还不如说Linux输入子系统呢,反 ...

  8. 转载:AbstractQueuedSynchronizer的介绍和原理分析

    简介 提供了一个基于FIFO队列,可以用于构建锁或者其他相关同步装置的基础框架.该同步器(以下简称同步器)利用了一个int来表示状态,期望它能够成为实现大部分同步需求的基础.使用的方法是继承,子类通过 ...

  9. Camel运行原理分析

    Camel运行原理分析 以一个简单的例子说明一下camel的运行原理,例子本身很简单,目的就是将一个目录下的文件搬运到另一个文件夹,处理器只是将文件(限于文本文件)的内容打印到控制台,首先代码如下: ...

随机推荐

  1. Struts2-学习笔记系列(14)-拦截器

    6.1对action 的拦截 自定义拦截器: public class MyInterceptor extends AbstractInterceptor { private String name; ...

  2. springIoc中的单列对象的分析

    最近有个同事去面试,其中有一个问题是关于spring单例的.本篇博文就发表一下小编我自己的理解~~. 使用过spring的程序猿应该都知道,我们的bean(controller.service和Dao ...

  3. WPF 仿语音播放 自定义控件

    原理很简单,利用Path画一个图,然后用动画进行播放,播放时间由依赖属性输入赋值与控件内部维护的一个计时器进行控制. 控件基本是玩具,无法作为真实项目使用. 非专业UI,即使知道怎么画图也是画的不如意 ...

  4. AJ学IOS(54)多线程网络之NSOperation重要知识

    AJ分享,必须精品 一:队列的类型与队列添加任务 1: 主队列 [NSOperationQueue mainQueue] 添加到”主队列”中的操作,都会放到主线程中执行. 2:非主队列 [[NSOpe ...

  5. ASP.NET Core中的Action的返回值类型

    在Asp.net Core之前所有的Action返回值都是ActionResult,Json(),File()等方法返回的都是ActionResult的子类.并且Core把MVC跟WebApi合并之后 ...

  6. 如何将一个div水平垂直居中?6种方法做推荐

    方案一: div绝对定位水平垂直居中[margin:auto实现绝对定位元素的居中], 兼容性:,IE7及之前版本不支持 div{ width: 200px; height: 200px; backg ...

  7. MySQL的事务隔离级别是什么?

    我是平也,这有一个专注Gopher技术成长的开源项目「go home」 背景介绍 想必事务大家都已经非常熟悉了,它是一组SQL组成的一个执行单元,要么全执行要么全不执行,这也是它的一个特性--原子性. ...

  8. D. Points in rectangle

    D. Points in rectangle 单点时限: 2.0 sec 内存限制: 512 MB 在二维平面中有一个矩形,它的四个坐标点分别为(0,a),(a,0),(n,n−a),(n−a,n). ...

  9. 美化你的终端利器Iterm2

    Iterm2是特别好用的一款终端,支持自定义字体和高亮,让日常开发,充满愉悦. 安装iterm2(mac版) brew tap caskroom/cask brew cask install iter ...

  10. 引用传参与reference_wrapper

    本文是<functional>系列的第3篇. 引用传参 我有一个函数: void modify(int& i) { ++i; } 因为参数类型是int&,所以函数能够修改传 ...