你想要更好的理解Android是怎样工作的
。Intent、ContextProvider、Messenger
。访问系统的服务
。生命周期中的回调
。安全
你想要通过高效率和低延时的IPC框架打破应用程序模块化的业务逻辑
你想要添加新的系统服务,可以更好地暴露给开发人员
你只是感觉IPC和Binder是不可缺少的,有趣的
你没有其它的事要做啦
Slides and screencast from this class will be posted to: http://mrkn.co/bgnhg
亚历山大 加尔根塔
Developer and instructor of Android Internals and Security training at Marakana
马里亚纳(位于西太平洋)Android Internals and Security training的开发者和指导师
San Francisco Android User Group (sfandroid.org)的创始人和组织者
San Francisco Java User Group (sfjava.org) 的创始人和组织者
San Francisco HTML5 User Group (sfhtml5.org) 的合作发起人和组织者
AnDevCon, AndroidOpen, Android Builders Summit 等等的演讲者
Server-side Java and Linux, since 1997
Android/embedded Java and Linux, since 2009
曾就职于 SMS, WAP Push, MMS, OTA provisioning
Follow
。@agargenta
。+Aleksandar Gargenta
。http://marakana.com/s/author/1/aleksandar_gargenta
一个IPC组件,开发面向对象的操作系统服务
。没有另外一个面向对象内核
。代替运行在传统内核上,面向对象的操作系统环境,如:Linux
Android的关键
来自OpenBinder
专注于可伸缩性、稳定性、灵活性、低延迟和开销、简单的编程模型
出于安全性、稳定性和内存管理的考虑,Android的应用和系统服务运行在分离的进程中,但是它们之间需要通信和共享数据
。安全性:每一个进程就是一个沙盒,运行在一个不同的系统标识中 。稳定性:如果一个进程失常(例如:崩溃),它不影响其它的进程 。内存管理:“不需要”的进程会被移除,为新的释放资源(主要是内存) 。事实上,一个单独的Android应用可以让它的组件运行在不同的进程中
IPC来拯救
。如果我们需要避免传统IPC开销和服务拒绝的问题
Android的libc(a.k.a bionic)库不支持System V IPCs
。没有SysV信号量,共享内存、消息队列等等 。当一个进程终止时,“忘记”释放IPC共享资源,System V IPC会报内存资源泄露的错误 。bug,恶意代码或者一个正常的应用在低内存的情况下都会无条件终止
Binder来拯救
。其内置的“对象”引用的引用计数器,加上消亡提醒机制,让它适用于“敌对的”环境(低内存强杀) 。当一个Binder服务没有任何终端引用时,它的所有者可以自动提醒它去处理自己
很多其它的特性:
。"线程迁移" - 如,编程模式:
* 远程对象可以像本地的一样调用自动管理线程池方法
* “跳转”到其它的进程中
* 同步和异步(单向)的调用模式
。分辨发送者和接受者(通过UID/PID)- 对于安全很重要 。独特的跨进程边界对象映射 。一个远程对象的引用可以传递到的另外的进程中,并且可以用作一个标志令牌 。各个进程之间发送文件描述符的能力 。简单的Android接口定义语言(AIDL) 。内置支持很多编组的常见数据类型 。通过自动生成的代理和存根简化事务调用模型(只有Java) 。跨进程递归 - 例如:当调用本地对象上的方法时就跟递归的语义一样 。如果客户端和服务器运行在同样的进程中,就会是本地执行模式(不是IPC数据信号编集)
但是:
。不支持RPC(只有本地) 。客户端与服务之间是基于消息的通信 - 不适合流 。没有被POSIX或任何其他标准定义
大多数应用程序和核心系统服务依赖于Binder
。应用程序大多数生命周期的回调(例如:onResume(), onDestory(), etc.)会通过Binder被ActivityManagerService调用 。关闭binder,然后整个系统慢慢停止(无显示,无音频,无输入,无传感器,...) 。有些情况下使用Unix域中的socket(例如:RILD)
通过Intent和content provider Android支持一个简单形式的IPC
意图的消息传递是Android组件之间异步通信的一个框架
。这些组件可能运行在相同的或不同的应用中(例如:多进程) 。支持点对点和发布-订阅消息传递域 。意图本身代表一个包含操作的描述和传递给接受者的数据 。隐式意图能够给APIs解耦合
ContentResolver通过稳定的(CRUB)API 与 ContentProviders (典型的运行在不同的应用中的)同步通信
所以的Android组件都可以是发送者,但是大多数是接受者
所有通信发生在循环线程(又称主线程)中
但是:
。不是真实的面向对象 。只有基于intent的异步通信 。不适合低延时 。因为,API定义地松散,所以容易发生运行时错误 。所有的底层通信都是基于Binder的 。事实上,Intent和ContentProvider只是Binder的高级抽象 。基于系统服务方便扩展:ActivityManagerService和PackageManagerService
For example:
src/com/marakana/shopping/UpcLookupActivity.java
public class ProductLookupActivity extends Activity {
private static final int SCAN_REQ = 0; ...
public void onClick(View view) {
Intent intent = new Intent("com.google.zxing.client.android.SCAN"); //1
intent.setPackage("com.google.zxing.client.android"); //1
intent.putExtra("SCAN_MODE", "PRODUCT_MODE"); //2
super.startActivityForResult(intent, SCAN_REQ); //3
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) { //4
if (requestCode == SCAN_REQ && resultCode == RESULT_OK) { //5
String barcode = data.getStringExtra("SCAN_RESULT"); //6
String format = data.getStringExtra("SCAN_RESULT_FORMAT"); //6
...
super.startActivity(
new Intent(Intent.ACTION_VIEW,
Uri.parse("http://www.upcdatabase.com/item/" + barcode))); //7
 }
...
}
}
src/com/google/zxing/client/android/CaptureActivity.java:
...
public class CaptureActivity extends Activity {
...
private void handleDecodeExternally(Result rawResult, ...) {
Intent intent = new Intent(getIntent().getAction());
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
intent.putExtra(Intents.Scan.RESULT, rawResult.toString()); //8
intent.putExtra(Intents.Scan.RESULT_FORMAT,
rawResult.getBarcodeFormat().toString());
...
super.setResult(Activity.RESULT_OK, intent);
super.finish(); //9
}
}

src/com/marakana/android/download/client/DownloadClientActivity.java:
...
public class DownloadClientActivity extends Activity {
private static final int CALLBACK_MSG = 0;
...
@Override
public void onClick(View view) {
Intent intent = new Intent( "com.marakana.android.download.service.SERVICE"); //1
ArrayList<Uri> uris = ...
intent.putExtra("uris", uris); //2
Messenger messenger = new Messenger(new ClientHandler(this)); //3
intent.putExtra("callback-messenger", messenger); //4
super.startService(intent); //5
}
private static class ClientHandler extends Handler {
private final WeakReference<DownloadClientActivity> clientRef; //6
public ClientHandler(DownloadClientActivity client) {
this.clientRef = new WeakReference<DownloadClientActivity>(client);
}
@Override
public void handleMessage(Message msg) { //7
Bundle data = msg.getData();
DownloadClientActivity client = clientRef.get();
if (client != null && msg.what == CALLBACK_MSG && data != null) {
Uri completedUri = data.getString("completed-uri"); //8
// client now knows that completedUri is done
...
}
}
}
}
src/com/marakana/android/download/service/DownloadService.java:
...
public class MessengerDemoService extends IntentService {
private static final int CALLBACK_MSG = 0;
...
@Override
protected void onHandleIntent(Intent intent) { //1
ArrayList<Uri> uris = intent.getParcelableArrayListExtra("uris"); //2
Messenger messenger = intent.getParcelableExtra("callback-messenger"); //3
for (Uri uri : uris) {
// download the uri
...
if (messenger != null) {
Message message = Message.obtain(); //4
message.what = CALLBACK_MSG;
Bundle data = new Bundle(1);
data.putParcelable("completed-uri", uri); //5
message.setData(data); //4
try {
messenger.send(message); //6
} catch (RemoteException e) {
...
} finally {
message.recycle(); //4 }
}
}
}
}
Binder (Framework)
所有的IPC架构
Binder Driver
内核级别的驱动,处理各个进程之间的通信
Binder Protocol
底层协议(基于ioctl),用于与Binder驱动通信
IBinder Interface
定义良好的行为(例如:方法),Binder对象必须实现
AIDL
Android接口定义语言,用于描述IBinder接口的业务操作
Binder (Object)
通用IBinder接口的实现
Binder Token
一个抽象的32位数值,在系统的所有进程中唯一的标识一个Binder对象
Binder Service
真正实现Binder(对象)的业务操作
Binder Client
一个对象,使用Binder服务提供的行为
Binder Transaction
远程Binder对象调用一个行为(例如:一个方法),基于Binder协议,可能涉及发送、接受的数据
Parcel
"可以在IBinder中发送消息的容器(数据和对象的引用)",事务处理的数据单元——一个用作流出请求,另一个用作流入响应
Marshalling
将高级的应用程序数据结构(例如:请求、响应参数)转化成parcel对象的过程,目的是将它们嵌套进Binder的事务中

Unmarshalling
将Binder事务中获取到的parcel对象重构成高级应用的数据结构的过程(例如:请求、响应参数)
Proxy
一个AIDL接口的实现,编组、解组数据,映射调用事务的方法,将一个封装的IBinder引用指向Binder对象
Stub
一个AIDL接口局部的实现,当编组/解组数据时,映射事务到Binder Service调用
Context Manager (a.k.a. servicemanager)
一个特殊的已知处理的Binder对象,被用作为其它Binder注册、查询
Binder Communication and Discovery
警告:因为服务端可能从多个客户端得到并发的请求,所以,它需要保护(同步访问的)其变量的状态
struct binder_write_read {
signed long write_size; /* bytes to write */
signed long write_consumed; /* bytes consumed by driver */
unsigned long write_buffer;
signed long read_size; /* bytes to read */
signed long read_consumed; /* bytes consumed by driver */
unsigned long read_buffer;
};
write_buffer包含一系列操作驱动的命令
。Book-keeping命令,例如:增加、减少binder对象的引用,请求、清除死亡通知 。请求响应的命令,例如:BC_TRANSACTION
read_buffer包含用户操作的命令
。book-keeping命令
。操作响应的命令或执行嵌套(递归)请求处理
客户端通过事务与服务端通信,事务中包含一个binder token、方法体、原始缓存数据和PID/UID的发送者(驱动添加的)
客户端和服务端使用的大多数底层的操作和数据结构(例如:Parcel)是libbinder的抽象(在底层)
为了避免客户端和服务知道Binder协议和libbinder所有事情,它们使用代理和存根:
注意:基于java的代理和存根可以通过描述服务的aidl工具自动生成

注意:对系统服务来说,这是非常正确的,它们使用管理者只向客户端暴露了API的一个子集
注意:出于安全、健康的原因,Binder驱动一次只会接受一个CONTEXT_MGR注册,这就是为什么Android中的Servicemanager是第一批启动的服务
$ adb shell service list
Found 71 services:
0 sip: [android.net.sip.ISipService]
1 phone: [com.android.internal.telephony.ITelephony]
...
20 location: [android.location.ILocationManager]
...
55 activity: [android.app.IActivityManager]
56 package: [android.content.pm.IPackageManager]
...
67 SurfaceFlinger: [android.ui.ISurfaceComposer]
68 media.camera: [android.hardware.ICameraService]
69 media.player: [android.media.IMediaPlayerService]
70 media.audio_flinger: [android.media.IAudioFlinger]
src/com/example/app/IFooService.aidl
package com.example.app;
import com.example.app.Bar;
interface IFooService {
void save(inout Bar bar);
Bar getById(int id);
void delete(in Bar bar);
List<Bar> getAll();
}
gen/com/example/app/IFooService.java
package com.example.app;
public interface IFooService extends android.os.IInterface {
public static abstract class Stub extends android.os.Binder implements com.example.app.IFooService {
...
public static com.example.app.IFooService asInterface(
android.os.IBinder obj) {
...
return new com.example.app.IFooService.Stub.Proxy(obj);
}
...
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
switch (code)
...
case TRANSACTION_save: {
...
com.example.app.Bar _arg0;
...
_arg0 = com.example.app.Bar.CREATOR.createFromParcel(data); this.save(_arg0);
...
}
...
}
...
}
...
private static class Proxy implements com.example.app.IFooService {
private android.os.IBinder mRemote;
...
public void save(com.example.app.Bar bar) throws android.os.RemoteException {
...
android.os.Parcel _data = android.os.Parcel.obtain();
...
bar.writeToParcel(_data, 0);
...
mRemote.transact(Stub.TRANSACTION_save, _data, _reply, 0);
...
}
}
}
void save(com.example.app.Bar bar) throws android.os.RemoteException;
com.example.app.Bar getById(int id) throws android.os.RemoteException;
void delete(com.example.app.Bar bar) throws android.os.RemoteException;
java.util.List<Bar> getAll() throws android.os.RemoteException;
}
注意:每次Eclipse ADT在src/目录下发现.aidl文件,它就会自动调用
AIDL支持以下的类型:
。null
。boolean,boolean[],byte,byte[],char[],int,int[],long,long[],float,float[],
double,double[]
。java.lang.CharSequence,java.lang.String (sent as UTF-20)
。java.io.FileDescriptor - transferred as a dup of the original file descriptor (points to the same underlying stream and position)
。java.io.Serializable - not efficient (too verbose)
。java.util.Map<String, Object> - of supported types (always reconstructed as
java.util.HashMap)
。android.os.Bundle - a specialized Map-wrapper that only accepts AIDL-supported data types
。java.util.List - of supported types (always reconstructed as java.util.ArrayList)
。java.lang.Object[] - of supported types (including primitive wrappers)
。android.util.SparseArray,android.util.SparseBooleanArray
。android.os.IBinder,android.os.IInterface - transferred by (globally unique) reference (as a "strong binder",a.k.a. handle) that can be used to call-back into the sender
。android.os.Parcelable - 自定义类型:
src/com/example/app/Bar.java
package com.example.app;
import android.os.Parcel;
import android.os.Parcelable;
public class Bar implements Parcelable {
private int id;
private String data;
public Bar(int id, String data) { this.id = id;
this.data = data;
}
// getters and setters omitted
...
public int describeContents() {
return 0;
}
public void writeToParcel(Parcel parcel, int flags) {
parcel.writeInt(this.id);
parcel.writeString(this.data);
}
public void readFromParcel(Parcel parcel) {
this.id = parcel.readInt();
this.data = parcel.readString();
}
public static final Parcelable.Creator<Bar> CREATOR = new Parcelable.Creator<Bar>() {
public Bar createFromParcel(Parcel parcel) {
return new Bar(parcel.readInt(), parcel.readString());
}
public Bar[] newArray(int size) {
return new Bar[size];
}
};

}
注意:public void readFromParcel(Parcel) 方法不是 Parcelable 接口定义的。这里是出于Bar的易变性考虑 -- 比如:我们期望远程的那端能够在 void save(inout Bar bar) 方法中修改它。
同理,public static final Parcelable.Creator
这些自定义的类必须在它们自己的.aidl文件中申明
src/com/example/app/Bar.aidl
package com.example.app;
parcelable Bar;
注意:AIDL接口必须导入parcelable自定义的类,即使它们在同一个包中。对于前面的示例,src/com/example/app/IFooService.aidl 必须导入com.example.app.Bar。
AIDL定义的方法可以不传或传入多个参数,但是,必须返回一个值或者Void
所有的非基本类型的参数需要一个指定方向的标签,in、out、inout,这其中的一个
。基本数据类型的指向一直是in(可以省略) 。当编组数据的时候指向标签会传递给Binder,因此它会对性能有直接影响
所有的.aidl注释都应该被拷贝到生成的Java接口中(除了import和package语句前的)
只有以下的异常会默认支持:SecurityException, BadParcelableException, IllegalArgumentException, NullPointerException, IllegalStateException
.aidl文件中,不支持静态成员
Binder对象的引用是以下的一种:
。在同一个进程中,Binder对象的一个真实的虚拟地址 。在另外一个进程中的,Binder对象的一个抽象的32位handle
在每个的事务中,Binder驱动都会自动的将远程的binder handle映射成本地的地址,将本地的地址映射成远程的Binder handle
这种映射实现:
。Binder事务的目标
。IBinder对象的引用作为参数或返回值跨进程共享(嵌套在事务数据中)
为了能够工作
。binder驱动在进程之间维持本地地址和远程handle(每一个进程都作为一个二进制树),因此,binder可以传送 。嵌套在事务中的引用是基于客户端提供的偏移
Binder驱动不知道不与远程进程共享的Binder对象 。一旦一个新的Binder对象引用在事务中被发现,它就会记录在Binder驱动中 。任何时候,Binder引用与其它的进程共享,Binder驱动的引用计数器就会增长 。当进程消亡的时候,Binder驱动的计数器就会明确的减少或自动地减少 。当一个引用不再需要的时候,它的所有者就会提醒它可以释放掉,并且Binder会删除自己的映射
为了演示基于Binder的服务端和客户端(基于Fibonacci),我们将会创建三个独立的工程:
1.FibonacciCommon库工程 - 定义AIDL接口,自定义类型作为参数和返回值 2.FibonacciService工程 - 我们实现AIDL接口,并把它暴露给客户端 3.FibonacciClient工程 - 用它连接我们的AIDL服务
以下的代码是可用的
。一个ZIP归档文件 :https://github.com/marakana/FibonacciBinderDemo/zipball/master 。通过Git:git clone https://github.com/marakana/FibonacciBinderDemo.git
UI显示大概如下:
FibonacciCommon - Define AIDL Interface and Custom Types
我们开始创建一个新的Android(库)工程,这是服务端和客户端可以共享的API文件(作为参数和返回值的一个AIDL接口和自定义类型)
。工程名:FibonacciCommon 。构建目标:Android 2.2+(API 8+) 。包名:com.marakana.android.fibonaccicommon 。最低SDK版本:8+ 。不需要指定应用名或activity
要转换成库工程,我们需要访问properties → Android → Library,然后,选中Library
我们也可以手动地添加 android.library=true 到 FibonacciCommon/default.properties 中,然后刷新该工程
因为库工程从来不会进入到实际的应用(APKS),所以我们可以简化清单文件:
FibonacciCommon/AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.marakana.android.fibonaccicommon" android:versionCode="1"
android:versionName="1.0">
</manifest>
并且我们可以移除 FibonacciCommon/res/ 目录(例如:rm -fr FibonacciCommon/res/* )下的任何文件
我们现在已经准备好创建AIDL接口了
FibonacciCommon/src/com/marakana/android/fibonaccicommon/IFibonacciService.aidl
package com.marakana.android.fibonaccicommon;
import com.marakana.android.fibonaccicommon.FibonacciRequest;
import com.marakana.android.fibonaccicommon.FibonacciResponse;
interface IFibonacciService {
long fibJR(in long n);
long fibJI(in long n);
long fibNR(in long n);
long fibNI(in long n);
FibonacciResponse fib(in FibonacciRequest request);
}
FibonacciCommon/src/com/marakana/android/fibonaccicommon/FibonacciRequest.java
package com.marakana.android.fibonaccicommon;
import android.os.Parcel; import android.os.Parcelable;
public class FibonacciRequest implements Parcelable {
public static enum Type {
RECURSIVE_JAVA, ITERATIVE_JAVA, RECURSIVE_NATIVE, ITERATIVE_NATIVE
}
private final long n;
private final Type type;
public FibonacciRequest(long n, Type type) {
this.n = n;
if (type == null){
throw new NullPointerException("Type must not be null");
}
this.type = type;
}
public long getN() {
return n;
}
public Type getType() {
return type;
}
public int describeContents() {
return 0;
}
public void writeToParcel(Parcel parcel, int flags) {
parcel.writeLong(this.n);
parcel.writeInt(this.type.ordinal());
}
public static final Parcelable.Creator<FibonacciRequest> CREATOR = new Parcelable.Creator<FibonacciRequest>() {
public FibonacciRequest createFromParcel(Parcel in) {
long n = in.readLong();
Type type = Type.values()[in.readInt()];
return new FibonacciRequest(n, type);
}
public FibonacciRequest[] newArray(int size) {
return new FibonacciRequest[size];
}
};
}
FibonacciCommon/src/com/marakana/android/fibonaccicommon/FibonacciRequest.aidl
package com.marakana.android.fibonaccicommon;
parcelable FibonacciRequest;
FibonacciCommon/src/com/marakana/android/fibonaccicommon/FibonacciResponse.java
package com.marakana.android.fibonaccicommon;
import android.os.Parcel; import android.os.Parcelable;
public class FibonacciResponse implements Parcelable {
private final long result;
private final long timeInMillis;
public FibonacciResponse(long result, long timeInMillis) {
this.result = result;
this.timeInMillis = timeInMillis;
}
public long getResult() {
return result;
}
public long getTimeInMillis() {
return timeInMillis;
}
public int describeContents() {
return 0;
}
public void writeToParcel(Parcel parcel, int flags) {
parcel.writeLong(this.result); parcel.writeLong(this.timeInMillis);
}
public static final Parcelable.Creator<FibonacciResponse> CREATOR = new Parcelable.Creator<FibonacciResponse>() {
public FibonacciResponse createFromParcel(Parcel in) {
return new FibonacciResponse(in.readLong(), in.readLong());
}
public FibonacciResponse[] newArray(int size) {
return new FibonacciResponse[size];
}
};
}
FibonacciCommon/src/com/marakana/android/fibonaccicommon/FibonacciResponse.aidl
package com.marakana.android.fibonaccicommon;
parcelable FibonacciResponse;
FibonacciCommon/gen/com/marakana/android/fibonaccicommon/IFibonacciService.java
package com.marakana.android.fibonaccicommon;
public interface IFibonacciService extends android.os.IInterface {
public static abstract class Stub extends android.os.Binder
implements com.marakana.android.fibonacci.IFibonacciService {
...
public static com.marakana.android.fibonacci.IFibonacciService asInterface(android.os.IBinder obj) {
...
}
public android.os.IBinder asBinder() {
return this;
}
...
}
public long fibJR(long n) throws android.os.RemoteException;
public long fibJI(long n) throws android.os.RemoteException;
public long fibNR(long n) throws android.os.RemoteException;
public long fibNI(long n) throws android.os.RemoteException;
public com.marakana.android.fibonaccicommon.FibonacciResponse fib(
com.marakana.android.fibonaccicommon.FibonacciRequest request) throws android.os.RemoteException;
}
我们开始创建一个新的Android工程,工程中有AIDL服务的实现和访问服务实现的机制(例如:绑定)
。工程名:FibonacciService 。编译目标:Android 2.2+(API 8+) 。包名:com.marakana.android.fibonacciservice 。应用名称:Fibonacci Service 。最小SDK: 8+ 。不需要指定一个Activity
为了能够访问common APIS,我们需要将工程跟FibonacciCommon链接:
工程名称 → Android → Library → Add... → FibonacciCommon
。最终, FibonacciService/default.properties 有了 android.library.reference.1=../FibonacciCommon ,并且 FibonacciService/.project 也链接到了 FibonacciCommon
我们的服务端将会使用实现了斐波那契算法的 com.marakana.android.fibonaccinative.FibLib
我们将FibonacciNative工程从Java类(和jni/implementation)中拷贝出(或移出)
。不要忘记在FibonacciService/下运行ndk-build去生成需要的本地库
我们现在已经准备好实现AIDL接口,通过扩展自动生成的com.marakana.android.fibonaccicommon.IFibonacciService.Stub(继承于android.os.Binder)
FibonacciService/src/com/marakana/android/fibonacciservice/IFibonacciServiceImpl.java*
package com.marakana.android.fibonacciservice;
import android.os.SystemClock;
import android.util.Log;
import com.marakana.android.fibonaccicommon.FibonacciRequest;
import com.marakana.android.fibonaccicommon.FibonacciResponse;
import com.marakana.android.fibonaccicommon.IFibonacciService;
import com.marakana.android.fibonaccinative.FibLib;
public class IFibonacciServiceImpl extends IFibonacciService.Stub {
private static final String TAG = "IFibonacciServiceImpl";
public long fibJI(long n) {
Log.d(TAG, String.format("fibJI(%d)", n)); return FibLib.fibJI(n);
}
public long fibJR(long n) {
Log.d(TAG, String.format("fibJR(%d)", n)); return FibLib.fibJR(n);
}
public long fibNI(long n) {
Log.d(TAG, String.format("fibNI(%d)", n)); return FibLib.fibNI(n);
}
public long fibNR(long n) {
Log.d(TAG, String.format("fibNR(%d)", n)); return FibLib.fibNR(n);
}
public FibonacciResponse fib(FibonacciRequest request) {
Log.d(TAG,String.format("fib(%d, %s)", request.getN(), request.getType()));
long timeInMillis = SystemClock.uptimeMillis();
long result;
switch (request.getType()) {
case ITERATIVE_JAVA:
result = this.fibJI(request.getN()); break;
case RECURSIVE_JAVA:
result = this.fibJR(request.getN()); break;
case ITERATIVE_NATIVE:
result = this.fibNI(request.getN()); break;
case RECURSIVE_NATIVE:
result = this.fibNR(request.getN()); break;
default: return null;
}
timeInMillis = SystemClock.uptimeMillis() - timeInMillis;
return new FibonacciResponse(result, timeInMillis);
}
}
FibonacciService/src/com/marakana/android/fibonacciservice/FibonacciService.java
package com.marakana.android.fibonacciservice;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;
public class FibonacciService extends Service { //1
private static final String TAG = "FibonacciService";
private IFibonacciServiceImpl service; //2
@Override
public void onCreate() {
super.onCreate();
this.service = new IFibonacciServiceImpl(); //3
Log.d(TAG, "onCreate()'ed"); //5
}
@Override
public IBinder onBind(Intent intent) {
Log.d(TAG, "onBind()'ed"); //5
return this.service; //4
}
@Override
public boolean onUnbind(Intent intent) {
Log.d(TAG, "onUnbind()'ed"); //5
return super.onUnbind(intent);
}
@Override
public void onDestroy() {
Log.d(TAG, "onDestroy()'ed");
this.service = null; super.onDestroy();
}
}
1、我们通过继承android.app.Service创建另外一个“service”。FibonacciService 对象的目的是提供访问我们的基于Binder的IFibonacciServiceImpl 对象
2、我们在这里简单的申明了一个本地的IFibonacciServiceImpl 引用,该引用是一个单例(例如:所有的客户端将会共享一个实例)。因为,我们的IFibonacciServiceImpl 不需要初始化的位置,所以我们可以在这里初始化,但是,我们选择推迟到onCreate() 方法中
3、现在,我们初始化了提供给客户端(在onBind(Intent)方法中)的IFibonacciServiceImpl 。如果,我们的IFibonacciServiceImpl 需要访问Context,我们可以传递一个引用给它(例如:android.app.Service ,实现了android.content.Context )。很多基于Binder的服务端使用context去访问其它平台的方法
4、我们在这里提供客户端访问IFibonacciServiceImpl 对象的接口。我们选择,只拥有一个IFibonacciServiceImpl 实例对象(因此所有的客户端都可以共享它),但是,我们也可以给每一个客户端提供一个IFibonacciServiceImpl 实例对象
5、我们添加一些日志,这样跟踪服务的生命周期就会简单
最后,我们在AndroidManifest.xml 中注册FibonacciService ,所以,客户端可以找到它
FibonacciService/AndroidManifest.xml*
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.marakana.android.fibonacciservice" android:versionCode="1" android:versionName="1.0">
<uses-sdk android:minSdkVersion="8" />
<application android:icon="@drawable/icon" android:label="@string/app_name">
<service android:name=".FibonacciService">
<intent-filter>
<action android:name="com.marakana.android.fibonaccicommon.IFibonacciService" /> <!-- 1 -->
</intent-filter>
</service>
</application>
</manifest>
1、名称可以随便定义,但是,我们通常使用派生的AIDL接口的全名
我们开始创建一个新的Android工程,这个工程是我们之前实现的AIDL Service的客户端
。工程名:FibonacciClient 。构建的目标:Android 2.2+(API 8+) 。包名:com.marakana.android.fibonacciclient 。应用名:Fibonacci Client 。创建的Activity:FibonacciActivity
* Activity中的大部分代码来自于 FibonacciNative
。最小SDK版本:8+
我们需要将工程链接到FibonacciCommon上,以便能够访问公有的APIs:
project properties → Android → Library → Add... → FibonacciCommon
。结果,FibonacciClient/default.properties 中有android.library.reference.1=../FibonacciCommon 和FibonacciClient/.classpath ,并且FibonacciClient/.project 也链接到了FibonacciCommon 。作为一种替代,我们可以避免首先创建FibonacciCommon 。FibonacciService 和 FibonacciClient 都需要拷贝:IFibonacciService.aidl、 FibonacciRequest.aidl、 FibonacciResponse.aidl、 FibonacciResult.java、 FibonacciResponse.java++ 。但是,我们不喜欢重复的代码(即使在运行时二进制文件会重复)
我们的客户端将会使用 FibonacciNative 应用中的字符串和布局资源
FibonacciClient/res/values/strings.xml*
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="hello">Get Your Fibonacci Here!</string>
<string name="app_name">Fibonacci Client</string>
<string name="input_hint">Enter N</string>
<string name="input_error">Numbers only!</string>
<string name="button_text">Get Fib Result</string>
<string name="progress_text">Calculating...</string>
<string name="fib_error">Failed to get Fibonacci result</string>
<string name="type_fib_jr">fibJR</string>
<string name="type_fib_ji">fibJI</string>
<string name="type_fib_nr">fibNR</string>
<string name="type_fib_ni">fibNI</string>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent">
<TextView android:text="@string/hello" android:layout_height="wrap_content"
android:layout_width="fill_parent" android:textSize="25sp" android:gravity="center"/>
<EditText android:layout_height="wrap_content"
android:layout_width="match_parent" android:id="@+id/input" android:hint="@string/input_hint" android:inputType="number" android:gravity="right" />
<RadioGroup android:orientation="horizontal" android:layout_width="match_parent" android:id="@+id/type" android:layout_height="wrap_content">
<RadioButton android:layout_height="wrap_content"
android:checked="true" android:id="@+id/type_fib_jr" android:text="@string/type_fib_jr"
android:layout_width="match_parent" android:layout_weight="1" />
<RadioButton android:layout_height="wrap_content"
android:id="@+id/type_fib_ji" android:text="@string/type_fib_ji"
android:layout_width="match_parent" android:layout_weight="1" />
<RadioButton android:layout_height="wrap_content"
android:id="@+id/type_fib_nr" android:text="@string/type_fib_nr"
android:layout_width="match_parent" android:layout_weight="1" />
<RadioButton android:layout_height="wrap_content"
android:id="@+id/type_fib_ni" android:text="@string/type_fib_ni"
android:layout_width="match_parent" android:layout_weight="1" />
</RadioGroup>
<Button android:text="@string/button_text" android:id="@+id/button" android:layout_width="match_parent" android:layout_height="wrap_content" />
<TextView android:id="@+id/output" android:layout_width="match_parent" android:layout_height="match_parent" android:textSize="20sp"
android:gravity="center|top"/>
</LinearLayout>
FibonacciClient/src/com/marakana/android/fibonacciclient/FibonacciActivity.java
package com.marakana.android.fibonacciclient;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.ComponentName; import android.content.Intent;
import android.content.ServiceConnection; import android.os.AsyncTask;

import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.SystemClock;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener; import android.widget.Button;
import android.widget.EditText;
import android.widget.RadioGroup;
import android.widget.TextView;
import android.widget.Toast;
import com.marakana.android.fibonaccicommon.FibonacciRequest;
import com.marakana.android.fibonaccicommon.FibonacciResponse;
import com.marakana.android.fibonaccicommon.IFibonacciService;
public class FibonacciActivity extends Activity implements OnClickListener, ServiceConnection {
private static final String TAG = "FibonacciActivity";
private EditText input; // our input n
private Button button; // trigger for fibonacci calcualtion
private RadioGroup type; // fibonacci implementation type
private TextView output; // destination for fibonacci result
private IFibonacciService service; // reference to our service
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
super.setContentView(R.layout.main);
// connect to our UI elements
this.input = (EditText) super.findViewById(R.id.input);
this.button = (Button) super.findViewById(R.id.button);
this.type = (RadioGroup) super.findViewById(R.id.type);
this.output = (TextView) super.findViewById(R.id.output); // request button click call-backs via onClick(View) method
this.button.setOnClickListener(this);
// the button will be enabled once we connect to the service
this.button.setEnabled(false);
}
@Override
protected void onResume() {
Log.d(TAG, "onResume()'ed");
super.onResume();
// Bind to our FibonacciService service, by looking it up by its name
// and passing ourselves as the ServiceConnection object
// We'll get the actual IFibonacciService via a callback to
// onServiceConnected() below
if (!super.bindService(new Intent(IFibonacciService.class.getName()),
this, BIND_AUTO_CREATE)) {
Log.w(TAG, "Failed to bind to service");
}
}
 @Override
protected void onPause() {
Log.d(TAG, "onPause()'ed");
super.onPause();
// No need to keep the service bound (and alive) any longer than
// necessary
super.unbindService(this);
}
public void onServiceConnected(ComponentName name, IBinder service) {
Log.d(TAG, "onServiceConnected()'ed to " + name);
// finally we can get to our IFibonacciService
this.service = IFibonacciService.Stub.asInterface(service);
// enable the button, because the IFibonacciService is initialized
this.button.setEnabled(true); }
public void onServiceDisconnected(ComponentName name) {
Log.d(TAG, "onServiceDisconnected()'ed to " + name);
// our IFibonacciService service is no longer connected this.service = null;
// disabled the button, since we cannot use IFibonacciService
this.button.setEnabled(false); }
// handle button clicks
public void onClick(View view) {
// parse n from input (or report errors) final long n;
String s = this.input.getText().toString();
if (TextUtils.isEmpty(s)) {
return; }
try {
n = Long.parseLong(s);
} catch (NumberFormatException e){
this.input.setError(super.getText(R.string.input_error));
return;
}
// build the request object
final FibonacciRequest.Type type;
switch (FibonacciActivity.this.type.getCheckedRadioButtonId()) {
case R.id.type_fib_jr:
type = FibonacciRequest.Type.RECURSIVE_JAVA;
break;
case R.id.type_fib_ji:
type = FibonacciRequest.Type.ITERATIVE_JAVA;
break;
case R.id.type_fib_nr:
type = FibonacciRequest.Type.RECURSIVE_NATIVE;
break;
case R.id.type_fib_ni:
type = FibonacciRequest.Type.ITERATIVE_NATIVE;
break;
default:
return;
}
final FibonacciRequest request = new FibonacciRequest(n, type);
// showing the user that the calculation is in progress
final ProgressDialog dialog = ProgressDialog.show(this, "",
super.getText(R.string.progress_text), true);
// since the calculation can take a long time, we do it in a separate
// thread to avoid blocking the UI
new AsyncTask<Void, Void, String>() {
@Override
protected String doInBackground(Void... params) {
// this method runs in a background thread
try {
long totalTime = SystemClock.uptimeMillis();
FibonacciResponse response = FibonacciActivity.this.service.fib(request);
totalTime = SystemClock.uptimeMillis() - totalTime; // generate the result
return String.format(
"fibonacci(%d)=%d\nin %d ms\n(+ %d ms)", n, response.getResult(), response.getTimeInMillis(), totalTime - response.getTimeInMillis());
} catch (RemoteException e) {
Log.wtf(TAG, "Failed to communicate with the service", e); return null;
}
}
@Override
protected void onPostExecute(String result) {
// get rid of the dialog
dialog.dismiss();
if (result == null) {
// handle error
Toast.makeText(FibonacciActivity.this, R.string.fib_error, Toast.LENGTH_SHORT).show();
} else {
// show the result to the user FibonacciActivity.this.output.setText(result);
}
}
}.execute(); // run our AsyncTask
}
}
注意:我们应该尽量避免在Activity中调用匿名的AsyncTask。这里我们走了一个捷径。
我们的Activity应该已经在 AndroidManifest.xml 文件中注册过
FibonacciClient/AndroidManifest.xml*
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
android:versionCode="1" android:versionName="1.0" package="com.marakana.android.fibonacciclient">
<uses-sdk android:minSdkVersion="8" />
<application android:icon="@drawable/icon" android:label="@string/app_name">
<activity android:name="com.marakana.android.fibonacciclient.FibonacciActivity" android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
在 AIDL 接口中申明,Binder 允许客户端与服务端之间异步通信
当然,我们仍然关心结果,通常异步回调都是通过监听器回调的方式
客户端给提供了一个引用作为回调的监听器,然后,当监听器调用的时候,客户端和服务端的角色就会调换:客户端端的监听器变成了服务端的,服务端的监听器变成了客户端的
最好的解释是通过一个示例(基于 Fibonacci)
代码是可用的
。一个ZIP存档文件:https://github.com/marakana/FibonacciAsyncBinderDemo/zipball/master
。通过Git:git clone https://github.com/marakana/FibonacciAsyncBinderDemo.git
FibonacciCommon/src/com/marakana/android/fibonaccicommon/IFibonacciServiceResponseListener.aidl:
package com.marakana.android.fibonaccicommon;
import com.marakana.android.fibonaccicommon.FibonacciRequest;
import com.marakana.android.fibonaccicommon.FibonacciResponse;
oneway interface IFibonacciServiceResponseListener {
void onResponse(in FibonacciResponse response);
}
FibonacciCommon/src/com/marakana/android/fibonaccicommon/IFibonacciService.aidl:
package com.marakana.android.fibonaccicommon;
import com.marakana.android.fibonaccicommon.FibonacciRequest;
import com.marakana.android.fibonaccicommon.FibonacciResponse;
import com.marakana.android.fibonaccicommon.IFibonacciServiceResponseListener;
oneway interface IFibonacciService {
void fib(in FibonacciRequest request, in IFibonacciServiceResponseListener listener);
}
FibonacciService/src/com/marakana/android/fibonacciservice/IFibonacciServiceImpl.java:
package com.marakana.android.fibonacciservice;
import android.os.RemoteException;
import android.os.SystemClock; import android.util.Log;
import com.marakana.android.fibonaccicommon.FibonacciRequest;
import com.marakana.android.fibonaccicommon.FibonacciResponse;
import com.marakana.android.fibonaccicommon.IFibonacciService;
import com.marakana.android.fibonaccicommon.IFibonacciServiceResponseListener;
import com.marakana.android.fibonaccinative.FibLib;
public class IFibonacciServiceImpl extends IFibonacciService.Stub {
private static final String TAG = "IFibonacciServiceImpl";
@Override
public void fib(FibonacciRequest request,
IFibonacciServiceResponseListener listener) throws RemoteException {
long n = request.getN();
Log.d(TAG, "fib(" + n + ")");
long timeInMillis = SystemClock.uptimeMillis();
long result;
switch (request.getType()) {
case ITERATIVE_JAVA:
result = FibLib.fibJI(n);
break;
case RECURSIVE_JAVA:
result = FibLib.fibJR(n);
break;
case ITERATIVE_NATIVE:
result = FibLib.fibNI(n);
break;
case RECURSIVE_NATIVE:
result = FibLib.fibNR(n);
break;
default:
result = 0;
}
timeInMillis = SystemClock.uptimeMillis() - timeInMillis;
Log.d(TAG, String.format("Got fib(%d) = %d in %d ms", n, result,timeInMillis));
listener.onResponse(new FibonacciResponse(result, timeInMillis));
}
}
注意:服务不会因监听返回而阻塞,因为,监听器本身也是单向的
FibonacciClient/src/com/marakana/android/fibonacciclient/FibonacciActivity.java:
package com.marakana.android.fibonacciclient;
import android.app.Activity;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.RemoteException;
import android.os.SystemClock;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.RadioGroup;
import android.widget.TextView;
import com.marakana.android.fibonaccicommon.FibonacciRequest;
import com.marakana.android.fibonaccicommon.FibonacciResponse;
import com.marakana.android.fibonaccicommon.IFibonacciService;
import com.marakana.android.fibonaccicommon.IFibonacciServiceResponseListener;
public class FibonacciActivity extends Activity implements OnClickListener, ServiceConnection {
private static final String TAG = "FibonacciActivity";
// the id of a message to our response handler
private static final int RESPONSE_MESSAGE_ID = 1;
// the id of a progress dialog that we'll be creating
private static final int PROGRESS_DIALOG_ID = 1;
private EditText input; // our input n
private Button button; // trigger for fibonacci calcualtion
private RadioGroup type; // fibonacci implementation type
private TextView output; // destination for fibonacci result
private IFibonacciService service; // reference to our service
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
super.setContentView(R.layout.main);
// connect to our UI elements
this.input = (EditText) super.findViewById(R.id.input);
this.button = (Button) super.findViewById(R.id.button);
this.type = (RadioGroup) super.findViewById(R.id.type);
this.output = (TextView) super.findViewById(R.id.output); // request button click call-backs via onClick(View) method
this.button.setOnClickListener(this);
// the button will be enabled once we connect to the service
this.button.setEnabled(false);
}
@Override
protected void onResume() {
Log.d(TAG, "onResume()'ed");
super.onResume();
// Bind to our FibonacciService service, by looking it up by its name
// and passing ourselves as the ServiceConnection object
// We'll get the actual IFibonacciService via a callback to
// onServiceConnected() below
if (!super.bindService(new Intent(IFibonacciService.class.getName()),this, BIND_AUTO_CREATE)) {
Log.w(TAG, "Failed to bind to service");
}
}
@Override
protected void onPause() {
Log.d(TAG, "onPause()'ed");
super.onPause();
// No need to keep the service bound (and alive) any longer than
// necessary
super.unbindService(this);
}
public void onServiceConnected(ComponentName name, IBinder service) {
Log.d(TAG, "onServiceConnected()'ed to " + name);
// finally we can get to our IFibonacciService
this.service = IFibonacciService.Stub.asInterface(service);
// enable the button, because the IFibonacciService is initialized
this.button.setEnabled(true);
}
public void onServiceDisconnected(ComponentName name) {
Log.d(TAG, "onServiceDisconnected()'ed to " + name);
// our IFibonacciService service is no longer connected
this.service = null;
// disabled the button, since we cannot use IFibonacciService
this.button.setEnabled(false);
}
// handle button clicks
public void onClick(View view) {
// parse n from input (or report errors)
final long n;
String s = this.input.getText().toString();
if (TextUtils.isEmpty(s)) {
return;
}
try {
n = Long.parseLong(s);
} catch (NumberFormatException e)
{
this.input.setError(super.getText(R.string.input_error));
return;
}
// build the request object
final FibonacciRequest.Type type;
switch (FibonacciActivity.this.type.getCheckedRadioButtonId()) {
case R.id.type_fib_jr:
type = FibonacciRequest.Type.RECURSIVE_JAVA;
break;
case R.id.type_fib_ji:
type = FibonacciRequest.Type.ITERATIVE_JAVA;
break;
case R.id.type_fib_nr:
type = FibonacciRequest.Type.RECURSIVE_NATIVE;
break;
case R.id.type_fib_ni:
type = FibonacciRequest.Type.ITERATIVE_NATIVE;
break;
default:
return;
}
final FibonacciRequest request = new FibonacciRequest(n, type);
try {
Log.d(TAG, "Submitting request...");
long time = SystemClock.uptimeMillis();
// submit the request; the response will come to responseListener this.service.fib(request, this.responseListener);
time = SystemClock.uptimeMillis() - time;
Log.d(TAG, "Submited request in " + time + " ms");
// this dialog will be dismissed/removed by responseHandler super.showDialog(PROGRESS_DIALOG_ID);
} catch (RemoteException e) {
Log.wtf(TAG, "Failed to communicate with the service", e);
}
}
}
注意:我们的监听器不应该持有 Activity 的强引用(但是它确实是,因为它是匿名的内部类),但是,在当前情况下,为了简明起见,我们忽略了它的正确性
Binder事务处理的数据是通信中的部分拷贝——如果传送很多数据是不明智的
。事实上,binder通过事务强制限制了发送数据的大小
如果我们想共享文件中的数据,我们只需要发送文件的描述符
。这是我们要求媒体播放器去播放音频、视频文件的方式——我们只是发送文件的描述符到软件驱动中
如果我们想要发送的数据在内存中,我们可以分多次发送,而不是试图一次发送所有的数据
。让我们的设计复杂化
可选,我们可以利用Android's ashmem(Ashmem匿名共享内存)工具
。它的Java封装器android.os.MemoryFile 不是用于第三方app共享内存 。丢到本地(通过JNI),然后是直接使用ashmem
本地共享内存通过 frameworks/base/libs/binder/Parcel.cpp 来实现:
。void Parcel::writeBlob(size_t len, WritableBlob outBlob)
。status_t Parcel::readBlob(size_t len, ReadableBlob outBlob)
以下是简略的实现:
Client
size_t len = 4096;
int fd = ashmem_create_region("Parcel Blob", len);
ashmem_set_prot_region(fd, PROT_READ | PROT_WRITE);
void* ptr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
ashmem_set_prot_region(fd, PROT_READ);
writeFileDescriptor(fd, true);
// write into ptr for len as desired
...
munmap(ptr, len);
close(fd);
int fd = readFileDescriptor();
void* ptr = mmap(NULL, len, PROT_READ, MAP_SHARED, fd, 0); // read from ptr up to len as desired
...
munmap(ptr, len);
注意:为了方便删除错误处理。并且,writeFileDescriptor(...) readFileDescriptor(...) 是libbinder提供的
frameworks/base/libs/binder/ProcessState.cpp
...
static int open_driver() {
int fd = open("/dev/binder", O_RDWR); if (fd >= 0) {
...
size_t maxThreads = 15;
result = ioctl(fd, BINDER_SET_MAX_THREADS, &maxThreads); ...
} else {
...
}
return fd;
}
...
。避免阻塞Binder线程
。如果我们需要执行一个长连接的任务,最好创建我们自己的线程
Binder限制运行在每个进程中的并发事务的缓存为1M
。如果参数或返回值的大小超过了缓存,就会抛出TransactionTooLargeException
。因为指定进程中的所有事务的缓存都是共享的,所以很多中等大小的事务也会耗尽缓存
。当TransactionTooLargeException 异常抛出的时候,我们不知道是发送请求出了错,还是接收响应出了错
。让事务数据尽量小,或者使用共享内存(ashmem)
Binder确实需要直接处理“安全”问题,但是,它只会在“可信的”执行环境和DAC中运行
Binder驱动只允许注册一个 CONTEXT_MGR(例如:servicemanager )
drivers/staging/android/binder.c:
...
static long binder_ioctl(struct file * filp, unsigned int cmd, unsigned long arg) {
...
switch (cmd) {
...
case BINDER_SET_CONTEXT_MGR:
if (binder_context_mgr_node != NULL) {
printk(KERN_ERR "binder: BINDER_SET_CONTEXT_MGR already set\n");
ret = -EBUSY;
goto err;
}
...
binder_context_mgr_node = binder_new_node(proc, NULL, NULL); ...
}
...
...
frameworks/base/cmds/servicemanager/service_manager.c:
...
static struct {
unsigned uid;
const char * name;
} allowed[] = {
#ifdef LVMX
{ AID_MEDIA,"com.lifevibes.mx.ipc" },
#endif
{ AID_MEDIA, "media.audio_flinger" },
{ AID_MEDIA, "media.player" },
{ AID_MEDIA, "media.camera" },
{ AID_MEDIA, "media.audio_policy" },
{ AID_DRM, "drm.drmManager" },
{ AID_NFC, "nfc" },
{ AID_RADIO, "radio.phone" },
{ AID_RADIO, "radio.sms" },
{ AID_RADIO, "radio.phonesubinfo" },
{ AID_RADIO, "radio.simphonebook" },
/* TODO: remove after phone services are updated: */
{ AID_RADIO, "phone" },
{ AID_RADIO, "sip" },
{ AID_RADIO, "isms" },
{ AID_RADIO, "iphonesubinfo" },
{ AID_RADIO, "simphonebook" },
};
...
int svc_can_register(unsigned uid, uint16_t * name)
{
unsigned n;
if ((uid == 0) || (uid == AID_SYSTEM))
return 1;
for (n = 0; n < sizeof(allowed) / sizeof(allowed[0]); n++)
if ((uid == allowed[n].uid) && str16eq(name, allowed[n].name))
return 1;
return 0;
}
...
int do_add_service(struct binder_state * bs,
uint16_t * s, unsigned len, void * ptr, unsigned uid)
{
...
if (!svc_can_register(uid, s)) {
LOGE("add_service('%s',%p) uid=%d - PERMISSION DENIED\n",
str8(s), ptr, uid); return -1;
}
...
}
...
每个Binder事务发送者的UID和PID很容易被访问到:
。android.os.Binder.getCallingPid()
。android.os.Binder.getCallingUid()
一旦我们知道如何访问UID,我们就可以很容易的解决调用app的问题,通过PackageManager.getPackagesForUid(int uid)
一旦我们知道如何调用app,我们就可以很容易的检查app是否有权限,通过PackageManager.getPackageInfo(String packageName, int flags) (检查PackageManager.GET_PERMISSIONS 标记)
但是,更简单的方式是:
。Context.checkCallingOrSelfPermission(String permission),如果调用的进程授予了权限就会返回PackageManager.PERMISSION_GRANTED,否则返回PackageManager.PERMISSION_DENIED
。Context.enforceCallingPermission(String permission, String message)——如果调用者没有请求的权限就会抛出SecurityException异常
以下显示应用框架服务是如何执行权限的
For example:
frameworks/base/services/java/com/android/server/VibratorService.java:
package com.android.server;
...
public class VibratorService extends IVibratorService.Stub {
...
public void vibrate(long milliseconds, IBinder token) {
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.VIBRATE) != PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("Requires VIBRATE permission");
}
...
}
...
}
frameworks/base/services/java/com/android/server/LocationManagerService.java:
package com.android.server;
...
public class LocationManagerService extends ILocationManager.Stub implements Runnable {
...
private static final String ACCESS_FINE_LOCATION =
android.Manifest.permission.ACCESS_FINE_LOCATION;
private static final String ACCESS_COARSE_LOCATION =
android.Manifest.permission.ACCESS_COARSE_LOCATION;
...
private void checkPermissionsSafe(String provider) {
if ((LocationManager.GPS_PROVIDER.equals(provider)
|| LocationManager.PASSIVE_PROVIDER.equals(provider))
&& (mContext.checkCallingOrSelfPermission(ACCESS_FINE_LOCATION)
!= PackageManager.PERMISSION_GRANTED)) {
throw new SecurityException("Provider " + provider
+ " requires ACCESS_FINE_LOCATION permission");
}
if (LocationManager.NETWORK_PROVIDER.equals(provider)
&& (mContext.checkCallingOrSelfPermission(ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED)
&& (mContext.checkCallingOrSelfPermission(ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED)) {
throw new SecurityException("Provider " + provider
+ " requires ACCESS_FINE_LOCATION or ACCESS_COARSE_LOCATION permission");
}
}
...
private Location _getLastKnownLocationLocked(String provider) {
checkPermissionsSafe(provider);
...
}
...
public Location getLastKnownLocation(String provider) {
...
_getLastKnownLocationLocked(provider);
...
}
}
For example:
frameworks/base/services/java/com/android/server/LocationManagerService.java:
public class LocationManagerService extends ILocationManager.Stub implements Runnable
{
...
private Receiver getReceiver(ILocationListener listener) {
IBinder binder = listener.asBinder();
Receiver receiver = mReceivers.get(binder);
if (receiver == null) {
receiver = new Receiver(listener);
...
receiver.getListener().asBinder().linkToDeath(receiver, 0);
...
}
return receiver;
}
private final class Receiver implements IBinder.DeathRecipient,
PendingIntent.OnFinished {
final ILocationListener mListener;
...
Receiver(ILocationListener listener) {
mListener = listener;
...
}
...
public void binderDied() {
...
removeUpdatesLocked(this);
}
...
}
...
}
在 active/failed 事务中,Binder驱动通过 /proc/binder/ 报告多种状态
。/proc/binder/failed_transaction_log
。/proc/binder/state
。/proc/binder/stats
。/proc/binder/transaction_log
。/proc/binder/transactions
。/proc/binder/proc/
注意:在设备中将 /sys/kernel/debug/binder 替换成 /proc/binder 的时候,设置 debugfs 为 enable
Android Binder by Thorsten Schreiber from Ruhr-Universität Bochum
Android Binder IPC Mechanism - 0xLab by Jim Huang (黄敬群) from 0xlab
Android’s Binder by Ken from Ken’s Space
Dianne Hackborn on Binder in Android on Linux Kernel Mailing List archive (LKML.ORG)
Android Binder on elinux.org
Share memory using ashmem and binder in the android framework
Introduction to OpenBinder and Interview with Dianne Hackborn
Open Binder Documentation
Binder IPC - A walk-though native IAudioFlinger::setMode call
We learned about:
Why Android needs IPC
What is Binder and how it differs from other forms of IPC
Binder vs Intent/ContentProvider/Messenger-based IPC
Binder Terminology
Binder Communication and Discovery Model
AIDL
Binder Object Reference Mapping
Synchronous vs Async Binder Invocations
Memory Sharing
Binder Limitations
Security Implications
Death Notification
Reporting
Didn’t we run out of time by now? :-)
Thank you for your patience!
Slides and screencast from this class will be posted to: http://mrkn.co/bgnhg You can follow me here:
@agargenta
+Aleksandar Gargenta http://marakana.com/s/author/1/aleksandar_gargenta
This slide-deck is licensed under Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.
Copyright© 2013-2020
All Rights Reserved 京ICP备2023019179号-8