Why are you here?

1668次阅读  |  发布于4年以前

Why are you here?

Objectives

Slides and screencast from this class will be posted to: http://mrkn.co/bgnhg

Who am I?

亚历山大 加尔根塔

Developer and instructor of Android Internals and Security training at Marakana

What is Binder?

Why Binder?

IPC with Intents and ContentProviders?

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 
    }
}


Messenger IPC

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 术语

Binder Communication and Discovery

Binder通信挖掘

警告:因为服务端可能从多个客户端得到并发的请求,所以,它需要保护(同步访问的)其变量的状态


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;

};

注意:基于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]

Location Service: An Example

AIDL

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文件,它就会自动调用

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) 方法中修改它。

src/com/example/app/Bar.aidl


package com.example.app; 
parcelable Bar;

注意:AIDL接口必须导入parcelable自定义的类,即使它们在同一个包中。对于前面的示例,src/com/example/app/IFooService.aidl 必须导入com.example.app.Bar。

跨进程映射Binder对象引用

这种映射实现:

。Binder事务的目标

。IBinder对象的引用作为参数或返回值跨进程共享(嵌套在事务数据中)

构建基于Binder的服务端和客户端

FibonacciCommon - Define AIDL Interface and Custom Types

FibonacciCommon - 定义AIDL接口和自定义类型

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/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;

}

FibonacciService - 实现AIDL接口,并暴露给我们的客户端


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);
    } 
}

暴露我们定义的AIDL服务实现给客户端

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、我们添加一些日志,这样跟踪服务的生命周期就会简单


<?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接口的全名

FibonacciClient - 使用基于Binder的AIDL服务


<?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。这里我们走了一个捷径。


<?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>

异步 Binder IPC

FibonacciCommon - 定义一个单向的 AIDL 服务

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 - 实现异步的 AIDL 服务

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 - 实现异步的 AIDL 客户端

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共享内存


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提供的

Binder的局限性

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 - 安全机制

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;
    }
    ... 
}
...

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); 
        ...
    } 
}

Binder消亡通知

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); 
        }

        ... 
    }

... 

}

Binder报告

注意:在设备中将 /sys/kernel/debug/binder 替换成 /proc/binder 的时候,设置 debugfs 为 enable

Additional Binder Resources

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

Summary

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

Questions?

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