Android AIDL Binder 简单实现与最全细节讲解

一、Android AIDL 简单实现

如下,使用 aidl 实现一个可获取书籍列表与添加书籍的 Service

服务端

Book.java -> 创建一个实现了 Parcelable 接口的书籍实体类

package top.ftas.aidl_demo;
import android.os.Parcel;
import android.os.Parcelable;
/**
 * 书籍类
 */
public class Book implements Parcelable {
    private String name;
    public Book(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return "book name:" + name;
    }
    @Override
    public int describeContents() {
        return 0;
    }
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(this.name);
    }
    public Book() {
    }
    protected Book(Parcel in) {
        this.name = in.readString();
    }
    /**
     * 对于被 inout 定向 tag 标记的类型,需要添加 readFromParcel 方法。返回类型为 void。(注意,readFromParcel 并不是 Parcelable 接口要求必须实现的方法)
     */
    void readFromParcel(Parcel source) {
        this.name = source.readString();
    }
    public static final Creator<Book> CREATOR = new Creator<Book>() {
        @Override
        public Book createFromParcel(Parcel source) {
            return new Book(source);
        }
        @Override
        public Book[] newArray(int size) {
            return new Book[size];
        }
    };
}

Book.aidl -> 右键创建 aidl 文件,在 aidl 文件中声明 Book 实体类 

// Book.aidl
package top.ftas.aidl_demo;
parcelable Book;

BookController.aidl -> 定义服务端接口 

// BookController.aidl
package top.ftas.aidl_demo;
//注意导入 Book 类的全路径
import top.ftas.aidl_demo.Book;
// Declare any non-default types here with import statements
interface BookController {
    //获取书籍列表
    List<Book> getBookList();
    //添加书籍(inout 服务端可以获取并修改客户端传递过来的 Book 实例的数据)
    void addBookInOut(inout Book book);
}

AIDLService.java -> Rebuild 项目,生成 aidl 辅助类。继承 Stub 抽象类,实现 BookController 定义的接口方法。

package top.ftas.aidl_demo;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.annotation.Nullable;
import android.util.Log;
import java.util.ArrayList;
import java.util.List;
/**
 * aidl 服务端
 */
public class AIDLService extends Service {
    private static final String TAG = "BookServer";
    private List<Book> mBookList;
    @Override
    public void onCreate() {
        super.onCreate();
        mBookList = new ArrayList<>();
        initData();
    }
    private void initData() {
        mBookList.add(new Book("活着"));
        mBookList.add(new Book("设计模式"));
        mBookList.add(new Book("大话西游"));
        mBookList.add(new Book("Android 权威编程指南"));
        mBookList.add(new Book("Jvm 虚拟机"));
        mBookList.add(new Book("简约至上"));
        mBookList.add(new Book("微交互"));
    }
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return mStub;
    }
    private final BookController.Stub mStub = new BookController.Stub() {
        @Override
        public List<Book> getBookList() throws RemoteException {
            return mBookList;
        }
        @Override
        public void addBookInOut(Book book) throws RemoteException {
            if (book != null) {
                book.setName("服务器改名 InOut->" + book.getName());
                mBookList.add(book);
            } else {
                Log.e(TAG, "接收到一个空对象 InOut");
            }
        }
    };
}

AndroidManifest.xml -> 定义服务的 action

<service android:name=".AIDLService">
    <intent-filter>
        <action android:name="top.ftas.aidl_demo.action"/>
    </intent-filter>
</service>

客户端

Book.java -> 创建一个实现了 Parcelable 接口的书籍实体类。

可拷贝服务端的实体类代码
注意保持客户端的实体类包路径和服务端一样

Book.aidl -> 右键创建 aidl 文件,在 aidl 文件中声明 Book 实体类 

可拷贝服务端的实体类 aidl 定义文件
注意保持客户端的 Book.aidl 文件路径和服务端一样

BookController.aidl -> 定义服务端接口 

可拷贝服务端的实体类 aidl 定义文件
注意保持客户端的 BookController.aidl 文件路径和服务端一样

MainActivity.java -> Rebuild 项目,生成 aidl 辅助类。使用 BookController.Stub.asInterface 静态方法将 onServiceConnected 返回的 IBinder 对象转换成 BookController 接口实体。(即服务端代理类)

package top.ftas.client_demo;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
import java.util.List;
import top.ftas.aidl_demo.Book;
import top.ftas.aidl_demo.BookController;
public class MainActivity extends AppCompatActivity {
    BookController mBookController;
    private ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mBookController = BookController.Stub.asInterface(service);
        }
        @Override
        public void onServiceDisconnected(ComponentName name) {
            mBookController = null;
        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        bindService();
    }
    public void getBookList(View view) {
        if (mBookController == null){
            return;
        }
        TextView textView = findViewById(R.id.tv_current_book_list);
        try {
            List<Book> bookList = mBookController.getBookList();
            textView.setText(textView.getText().toString() + "bookList = " + bookList + "\n");
        } catch (RemoteException e) {
            e.printStackTrace();
            textView.setText(textView.getText().toString() + e + "\n");
        }
    }
    public void addBook_inout(View view) {
        if (mBookController == null) return;
        EditText editText = findViewById(R.id.et_value);
        TextView textView = findViewById(R.id.tv_current_book_list);
        Book book = new Book(editText.getText().toString());
        try {
            mBookController.addBookInOut(book);
            textView.setText(textView.getText().toString() + "新添加的书名为:" + book.getName() + "\n");
        } catch (RemoteException e) {
            e.printStackTrace();
            textView.setText(textView.getText().toString() + e + "\n");
        }
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        unbindService(mServiceConnection);
    }
    private void bindService(){
        Intent intent = new Intent();
        intent.setPackage("top.ftas.head_first_java23");
        intent.setAction("top.ftas.aidl_demo.action");
        bindService(intent,mServiceConnection, Context.BIND_AUTO_CREATE);
    }
}

activity_main.xml -> 客户端布局文件 

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    android:orientation="vertical"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
    <Button
        android:onClick="getBookList"
        android:text="获取书籍列表"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
    <EditText
        android:text="默认书籍"
        android:id="@+id/et_value"
        android:layout_width="match_parent"
        android:layout_height="49dp"/>
    <Button
        android:onClick="addBook_inout"
        android:text="inout 添加书籍"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
    <TextView
        android:id="@+id/tv_current_book_list"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>
</LinearLayout>

服务端一些配置

build.gradle

注意 applicationId 是 『top.ftas.head_first_java23"』

apply plugin: 'com.android.application'
android {
    compileSdkVersion 28
    defaultConfig {
        applicationId "top.ftas.head_first_java23"
        minSdkVersion 18
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}
dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:28.0.0'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
}

最终,绑定 Service 时,setPackage 填写的应该是『top.ftas.head_first_java23』

AndroidManifest.xml

注意 package 是 『top.ftas.aidl_demo』

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="top.ftas.aidl_demo">
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <service android:name=".AIDLService">
            <intent-filter>
                <action android:name="top.ftas.aidl_demo.action"/>
            </intent-filter>
        </service>
        <activity android:name=".MainActivity">
            <intent-filter>
                <category android:name="android.intent.category.LAUNCHER"/>
                <action android:name="android.intent.action.MAIN"/>
            </intent-filter>
        </activity>
        <activity android:name=".ServiceTestActivity">
            <intent-filter>
                <category android:name="android.intent.category.DEFAULT"/>
                <action android:name="top.ftas.aidl_demo.activity"/>
            </intent-filter>
        </activity>
    </application>
</manifest>

二、Android AIDL 细节与异常

1. Service 记得到 AndroidManifest.xml 文件中进行配置

2. setPackage 填写的是 applicationId 而非 AndroidManifest.xml 中 package 中的值(如果两者不一样的话)。同样的,当我们用到的一些三方sdk(例如友盟,百度等)需要我们的包名,这的包名指的也是 applicationId 而非 package name,当然在 eclipse 中也就没有区分了,因为它只有 package name。

applicationId 标识 App 的进程 ID
package name 决定 R 文件包名以及 AndroidManifest.xml 中 Activity 等四大组件的相对包名
如果 build.gradle 中没有指定 applicationId,那么 applicationId 默认和 AndroidManifest.xml 中的 package 属性值一样。
applicationId 标识某个应用。如果 applicationId 改了,但是 package 属性没有改。这个应用也还是会变为一个全新的应用安装到手机上,而不会覆盖安装 applicationId 修改之前的那个旧应用。

3. android 5.0 以后 Service 必须使用显示 Intent 启动

使用如下的方式(仅 setAction ,没有 setPackage)启动 Service 将报错。

Intent intent = new Intent();
intent.setAction("top.ftas.aidl_demo.action");
bindService(intent,mServiceConnection, Context.BIND_AUTO_CREATE);

报错如下

java.lang.RuntimeException: Unable to start activity ComponentInfo{top.ftas.client_demo/top.ftas.client_demo.MainActivity}: java.lang.IllegalArgumentException: Service Intent must be explicit: Intent { act=top.ftas.aidl_demo.action }
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2416)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2476)

正确的 Service 启动方式示例

Intent intent = new Intent();
intent.setPackage("top.ftas.head_first_java23");
intent.setAction("top.ftas.aidl_demo.action");
bindService(intent,mServiceConnection, Context.BIND_AUTO_CREATE);

隐式启动 Activity 可无需 setPackage,示例如下:

Intent intent = new Intent();
intent.setAction("top.ftas.aidl_demo.activity");
startActivity(intent);

4. 需要导出的 Activity 需要配置 android.intent.category.DEFAULT 选择器。而 Service 则没有必要配置 android.intent.category.DEFAULT 选择器。

Service 可按如下配置

<service android:name=".AIDLService">
    <intent-filter>
        <action android:name="top.ftas.aidl_demo.action"/>
    </intent-filter>
</service>

Activity 可按如下配置

<activity android:name=".ServiceTestActivity">
    <intent-filter>
        <category android:name="android.intent.category.DEFAULT"/>
        <action android:name="top.ftas.aidl_demo.activity"/>
    </intent-filter>
</activity>

系统在使用隐式 Intent 启动 Activity 时候,会自动帮我们添加上“android.intent.category.DEFAULT”,所以实际上所有需要被隐式 Intent 启动的 Activity,都要加上 android.intent.category.DEFAULT 声明,否则就会启动不了并提示无法匹配该Intent的错误。

at android.app.ActivityThread.main(ActivityThread.java:5417) 
        at java.lang.reflect.Method.invoke(Native Method) 
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616) 
     Caused by: android.content.ActivityNotFoundException: No Activity found to handle Intent { act=top.ftas.aidl_demo.activity }

exported - false 可以阻止其它应用访问到这个 Activity 或者 Service。对于被 exported - false 标记的组件。

<service android:name=".AIDLService" android:exported="false">
    <intent-filter>
        <action android:name="top.ftas.aidl_demo.action"/>
    </intent-filter>
</service>

报错如下

Not allowed to bind to service Intent { act=top.ftas.aidl_demo.action pkg=top.ftas.head_first_java23 }
------
Caused by: java.lang.SecurityException: Permission Denial: starting Intent { act=top.ftas.aidl_demo.activity cmp=top.ftas.head_first_java23/top.ftas.aidl_demo.ServiceTestActivity } from ProcessRecord{fef796b 6220:top.ftas.client_demo/u0a64} (pid=6220, uid=10064) not exported from uid 10065

5. 先检查应用是否安装,再启动服务

/**
 * 判断应用是否安装
 * @param packageName 这里的 packageName 指的 applicationId,而非 packageName
 */
private static boolean isAppInstalled(Context context,String packageName){
    PackageInfo packageInfo;
    try {
        packageInfo = context.getPackageManager().getPackageInfo(packageName,0);
    }catch (PackageManager.NameNotFoundException e){
        packageInfo = null;
    }
    return packageInfo != null;
}
/**
 * aidl 绑定服务
 */
private void bindService(){
    if (isAppInstalled(this,"top.ftas.head_first_java23")){
        Intent intent = new Intent();
        //应当传递 applicationId 而非 packageName
        intent.setPackage("top.ftas.head_first_java23");
        intent.setAction("top.ftas.aidl_demo.action");
        bindService(intent,mServiceConnection, Context.BIND_AUTO_CREATE);
    }else {
        Toast.makeText(this,"应用未安装",Toast.LENGTH_LONG).show();
    }
}

6. 使用 aidl 文件声明实现了 Parcelable 接口的数据类型时不用导包

// Book.aidl
package top.ftas.aidl_demo;
parcelable Book;

7. 所有自定义类使用的时候应该有明确的导包。简单点说 List Map 和其它默认类型不用导包,其余的,即使在同一个包路径下面,也需要明确导包。

// BookController.aidl
package top.ftas.aidl_demo;
import top.ftas.aidl_demo.Book;
// Declare any non-default types here with import statements
interface BookController {
    List<Book> getBookList();
    void addBookInOut(inout Book book);
}

三、AIDL 定向 Tag

inout

客户端实体对象(的值)可以传递到服务端,服务端对实体对象的值的修改也会同步到客户端。看上去他们引用的就像是同一个对象实体一样。效果类似我们常说的『引用传递』。

in

客户端实体对象(的值)可以传递到服务端。服务端对实体对象的值的修改不会同步到客户端。效果类似我们常说的『值传递』。

out

客户端实体对象的值无法传递到服务端(即使实体对象里面有值,传递过来也会变为空对象)。服务端对实体对象的值的修改会同步到客户端。效果类似客户端传递了一个『引用』,指向了一块空白的内存空间。然后服务端向这个内存空间写数据的时候,写入的值会被映射到客户端所持有的实体对象中。

被 out 标记的参数,在传递实体对象到服务端的时候,编译器会 new 一个新的实体对象传递过去。类似代码如下:

case TRANSACTION_addBookOut: {
                    data.enforceInterface(DESCRIPTOR);
                    top.ftas.aidl_demo.Book _arg0;
                    _arg0 = new top.ftas.aidl_demo.Book();
                    this.addBookOut(_arg0);
                    reply.writeNoException();

因此,当参数被 out 标记时,你定义的实体类应该有无参的构造方法。以方便 aidl 去创建一个空的实体对象。类似如下:

public Book() {
    this.name = "我是无参构造器创建的对象";
}

定向 Tag 『inout in out』 测试代码

Book.java 

package top.ftas.aidl_demo;
import android.os.Parcel;
import android.os.Parcelable;
/**
 * 书籍类
 */
public class Book implements Parcelable {
    private String name;
    public Book(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return "book name:" + name;
    }
    @Override
    public int describeContents() {
        return 0;
    }
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(this.name);
    }
    /**
     * Book 被 out 标记时,必须有此无参构造方法。
     * out 标记的参数,服务端将无法接收到客户端传递过来的实体对象的数据。只能接收到此无参构造方法创建的默认对象。
     */
    public Book() {
        this.name = "我是无参构造器创建的对象";
    }
    protected Book(Parcel in) {
        this.name = in.readString();
    }
    /**
     * 对于被 inout 定向 tag 标记的类型,需要添加 readFromParcel 方法。返回类型为 void。(注意,readFromParcel 并不是 Parcelable 接口要求必须实现的方法)
     */
    void readFromParcel(Parcel source) {
        this.name = source.readString();
    }
    public static final Creator<Book> CREATOR = new Creator<Book>() {
        @Override
        public Book createFromParcel(Parcel source) {
            return new Book(source);
        }
        @Override
        public Book[] newArray(int size) {
            return new Book[size];
        }
    };
}

BookController.aidl

// BookController.aidl
package top.ftas.aidl_demo;
import top.ftas.aidl_demo.Book;
// Declare any non-default types here with import statements
interface BookController {
    List<Book> getBookList();
    void addBookInOut(inout Book book);
    void addBookIn(in Book book);
    void addBookOut(out Book book);
}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    android:orientation="vertical"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
    <Button
        android:onClick="getBookList"
        android:text="获取书籍列表"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
    <EditText
        android:text="默认书籍"
        android:id="@+id/et_value"
        android:layout_width="match_parent"
        android:layout_height="49dp"/>
    <Button
        android:onClick="addBook_inout"
        android:text="inout 添加书籍"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
    <Button
        android:onClick="addBook_in"
        android:text="in 添加书籍"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
    <Button
        android:onClick="addBook_out"
        android:text="out 添加书籍"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
    <TextView
        android:id="@+id/tv_current_book_list"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>
</LinearLayout>

MainActivity.java

package top.ftas.client_demo;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import java.util.List;
import top.ftas.aidl_demo.Book;
import top.ftas.aidl_demo.BookController;
public class MainActivity extends AppCompatActivity {
    BookController mBookController;
    private ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mBookController = BookController.Stub.asInterface(service);
        }
        @Override
        public void onServiceDisconnected(ComponentName name) {
            mBookController = null;
        }
    };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        bindService();
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        unbindService(mServiceConnection);
    }
    /**
     * 判断应用是否安装
     *
     * @param packageName 这里的 packageName 指的 applicationId,而非 packageName
     */
    private static boolean isAppInstalled(Context context, String packageName) {
        PackageInfo packageInfo;
        try {
            packageInfo = context.getPackageManager().getPackageInfo(packageName, 0);
        } catch (PackageManager.NameNotFoundException e) {
            packageInfo = null;
        }
        return packageInfo != null;
    }
    /**
     * aidl 绑定服务
     */
    private void bindService() {
        if (isAppInstalled(this, "top.ftas.head_first_java23")) {
            Intent intent = new Intent();
            //应当传递 applicationId 而非 packageName
            intent.setPackage("top.ftas.head_first_java23");
            intent.setAction("top.ftas.aidl_demo.action");
            bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
        } else {
            Toast.makeText(this, "应用未安装", Toast.LENGTH_LONG).show();
        }
    }
    /**
     * 从服务端获取书籍列表
     */
    public void getBookList(View view) {
        if (mBookController == null) return;
        TextView textView = findViewById(R.id.tv_current_book_list);
        try {
            List<Book> bookList = mBookController.getBookList();
            textView.setText(textView.getText().toString() + "\n-----\nbookList = " + bookList + "\n-----\n");
        } catch (RemoteException e) {
        }
    }
    /**
     * 添加书籍 inout 标记
     */
    public void addBook_inout(View view) {
        if (mBookController == null) return;
        EditText editText = findViewById(R.id.et_value);
        TextView textView = findViewById(R.id.tv_current_book_list);
        Book book = new Book(editText.getText().toString());
        try {
            mBookController.addBookInOut(book);
            textView.setText(textView.getText().toString() + "inout 新添加的书名为:" + book.getName() + "\n");
        } catch (RemoteException e) {
        }
    }
    /**
     * 添加书籍 in 标记
     */
    public void addBook_in(View view) {
        if (mBookController == null) return;
        EditText editText = findViewById(R.id.et_value);
        TextView textView = findViewById(R.id.tv_current_book_list);
        Book book = new Book(editText.getText().toString());
        try {
            mBookController.addBookIn(book);
            textView.setText(textView.getText().toString() + "in 新添加的书名为:" + book.getName() + "\n");
        } catch (RemoteException e) {
        }
    }
    /**
     * 添加书籍 out 标记
     */
    public void addBook_out(View view) {
        if (mBookController == null) return;
        EditText editText = findViewById(R.id.et_value);
        TextView textView = findViewById(R.id.tv_current_book_list);
        Book book = new Book(editText.getText().toString());
        try {
            mBookController.addBookOut(book);
            textView.setText(textView.getText().toString() + "out 新添加的书名为:" + book.getName() + "\n");
        } catch (RemoteException e) {
        }
    }
}

依次点击下面的按钮

『获取书籍列表』->『INOUT 添加书籍』,输入框内容『InOut_Test』->『IN 添加书籍』,输入框内容『In_Test』->『OUT 添加书籍』,输入框内容『Out_Test』->『获取书籍列表』

 

/binder-inout-test.jpg

结果如

result

四、 定向 Tag 对 null 的支持

inout 支持客户端传递 null,服务端获取到的也是 null

AIDLService.java

@Override
public void addBookInOut(Book book) throws RemoteException {
    if (book != null) {
        book.setName("服务器改名 InOut->" + book.getName());
        mBookList.add(book);
    } else {
        book = new Book();
        book.setName("Service 接收到一个空对象 InOut");
        mBookList.add(book);
    }
}

MainActivity.java

/**
 * 添加书籍 inout 标记
 */
public void addBook_inout(View view) {
    if (mBookController == null) return;
    try {
        mBookController.addBookInOut(null);
    } catch (RemoteException e) {
    }
}

依次点击『获取书籍列表』->『INOUT 添加书籍』->『获取书籍列表』,结果如下

null

in 支持客户端传递 null,服务端获取到的也是 null

AIDLService.java

@Override
public void addBookIn(Book book) throws RemoteException {
    if (book != null){
        book.setName("服务器改名 In->" + book.getName());
        mBookList.add(book);
    }else {
        book = new Book();
        book.setName("Service 接收到一个空对象 In");
        mBookList.add(book);
    }
}

依次点击『获取书籍列表』->『IN 添加书籍』->『获取书籍列表』,结果和 inout 类似。

out 不支持客户端传递 null

AIDLService.java

@Override
public void addBookOut(Book book) throws RemoteException {
    if (book != null){
        book.setName("服务器改名 Out->" + book.getName());
        mBookList.add(book);
    }else {
        book = new Book();
        book.setName("Service 接收到一个空对象 Out");
        mBookList.add(book);
    }
}

MainActivity.java

/**
 * 添加书籍 out 标记
 */
public void addBook_out(View view) {
    if (mBookController == null) return;
    try {
        mBookController.addBookOut(null);
    } catch (RemoteException e) {
    }
}

依次点击『获取书籍列表』->『OUT 添加书籍』->『获取书籍列表』,闪退,并报如下异常:

Caused by: java.lang.reflect.InvocationTargetException
at java.lang.reflect.Method.invoke(Native Method)
at android.support.v7.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:385)
at android.view.View.performClick(View.java:5198) 
at android.view.View$PerformClick.run(View.java:21147) 
at android.os.Handler.handleCallback(Handler.java:739) 
at android.os.Handler.dispatchMessage(Handler.java:95) 
at android.os.Looper.loop(Looper.java:148) 
at android.app.ActivityThread.main(ActivityThread.java:5417) 
at java.lang.reflect.Method.invoke(Native Method) 
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726) 
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616) 
Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'void top.ftas.aidl_demo.Book.readFromParcel(android.os.Parcel)' on a null object reference
at top.ftas.aidl_demo.BookController$Stub$Proxy.addBookOut(BookController.java:195)
at top.ftas.client_demo.MainActivity.addBook_out(MainActivity.java:121)
at java.lang.reflect.Method.invoke(Native Method) 
at android.support.v7.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:385) 
at android.view.View.performClick(View.java:5198) 
at android.view.View$PerformClick.run(View.java:21147) 
at android.os.Handler.handleCallback(Handler.java:739) 
at android.os.Handler.dispatchMessage(Handler.java:95) 
at android.os.Looper.loop(Looper.java:148) 

out 标记的参数传递 null 报错是因为编译器根据 BookController.aidl 自动生成的 BookController.java 里面有如下代码。

@Override
public void addBookOut(top.ftas.aidl_demo.Book book) throws android.os.RemoteException {
    android.os.Parcel _data = android.os.Parcel.obtain();
    android.os.Parcel _reply = android.os.Parcel.obtain();
    try {
        _data.writeInterfaceToken(DESCRIPTOR);
        mRemote.transact(Stub.TRANSACTION_addBookOut, _data, _reply, 0);
        _reply.readException();
        if ((0 != _reply.readInt())) {
            //如果 out 标记的参数传递 null,此处将报空指针错误。 NullPointerException
            book.readFromParcel(_reply);
        }
    } finally {
        _reply.recycle();
        _data.recycle();
    }
}

五、AIDL 支持的所有数据类型

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

String,CharSequence

实现了Parcelable接口的数据类型

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

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

注意 short 在传递的时候,实际是以 int 的形式传递的

private short pageCount;
---
@Override
public void writeToParcel(Parcel dest, int flags) {
    dest.writeString(this.name);
    dest.writeInt(this.pageCount);
}
---
void readFromParcel(Parcel source) {
    this.name = source.readString();
    this.pageCount = (short) source.readInt();
}

六、Demo 下载

Android AIDL Binder 实现与详解 Demo

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值