首页 > 代码库 > Android Programming: Pushing the Limits -- Chapter 7:Android IPC -- AIDL

Android Programming: Pushing the Limits -- Chapter 7:Android IPC -- AIDL

 

服务端:

最终项目结构:

技术分享

这个项目中,我们将用到自定义类CustomData作为服务端与客户端传递的数据。

Step 1:创建CustomData类

package com.ldb.android.example.aidl;import android.os.Parcel;import android.os.Parcelable;import android.util.Log;import java.util.ArrayList;import java.util.Date;import java.util.List;/** * Created by lsp on 2016/9/1. */public class CustomData implements Parcelable {    private static final String TAG = "CustomData";    private String mName;    private List<String> mReference;    private Date mCreated;    public CustomData(){        mName = "";        mReference = new ArrayList<>();        mCreated = new Date();    }    public String getName() {        return mName;    }    public void setName(String name) {        mName = name;    }    public List<String> getReference() {        return mReference;    }    public void setReference(List<String> reference) {        mReference = reference;    }    public Date getCreated() {        return mCreated;    }    public void setCreated(Date created) {        mCreated = created;    }    @Override    public int describeContents() {        return 0;    }    @Override    public void writeToParcel(Parcel dest, int flags) {        dest.writeString(mName);        dest.writeStringList(mReference);        dest.writeLong(mCreated.getTime());    }    @Override    public boolean equals(Object o) {        if(this == o) return true;        if(o == null || getClass() != o.getClass()) return false;        CustomData that = (CustomData) o;        return mCreated.equals(that.mCreated) && mName.equals(that.mName);    }    @Override    public int hashCode() {        int result = mName.hashCode();        result = 31 * result + mCreated.hashCode();        return result;    }    public static final Parcelable.Creator<CustomData> CREATOR = new Parcelable.Creator<CustomData>(){        @Override        public CustomData createFromParcel(Parcel source) {            CustomData customData = new CustomData();            customData.mName = source.readString();//            customData.mReference = new ArrayList<>();            source.readStringList(customData.mReference);            Long created = source.readLong();            Log.d(TAG, "createFromParcel " + created);            customData.mCreated = new Date(created);            return customData;        }        @Override        public CustomData[] newArray(int size) {            return new CustomData[size];        }    };}

为了实现进程间传递,CustomData 需要实现接口Parcelable,writeToParcel()方法和CREATOR是不可少的。

 

Step 2:创建CustomData类对应的aidl文件, 不过aidl文件先任意命名,不能是CustomData,否则Android Studio不让继续执行。创建完之后再对aidl重命名为CustomData.aidl。注意此aidl文件的package与CustomData的package要保持一致。模块名app上右键-->new-->AIDL,生成文件后重命名,然后修改文件内容为:

// CustomData.aidlpackage com.ldb.android.example.aidl;parcelable CustomData;

Step 3:继续生成AidlCallback.aidl文件和ApiInterfaceV1.aidl文件,修改文件内容为:

AidlCallback.aidl:

// AidlCallback.aidlpackage com.ldb.android.example.aidl;// Declare any non-default types here with import statementsimport com.ldb.android.example.aidl.CustomData;oneway interface AidlCallback {    void onDataUpdated(in CustomData[] data);}

ApiInterfaceV1.aidl:

// ApiInterfaceV1.aidlpackage com.ldb.android.example.aidl;// Declare any non-default types here with import statementsimport com.ldb.android.example.aidl.CustomData;import com.ldb.android.example.aidl.AidlCallback;interface ApiInterfaceV1 {    boolean isPrime(long value);    void getAllDataSince(long timestamp, out CustomData[] result);    void storeData(in CustomData data);    void setCallback(in AidlCallback callback);}

Step 4:菜单 Build --> Make Project 或者 Rebuild Project,如果顺利的话,就能够自动生成AidlCallback.aidl文件和ApiInterfaceV1.aidl文件对应的.java文件。

在编译目录下,如我的目录是AidlService\app\build\generated\source\aidl\... 下有AidlCallback.java和ApiInterfaceV1.java两个文件。或者在创建服务的时候再进行验证。

Step 5:创建服务类AidlService:

package com.ldb.android.example.aidlservice;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 com.ldb.android.example.aidl.AidlCallback;import com.ldb.android.example.aidl.ApiInterfaceV1;import com.ldb.android.example.aidl.CustomData;import java.util.ArrayList;import java.util.Date;/** * Created by lsp on 2016/9/1. */public class AidlService extends Service {    private static final String TAG = "AidlService";    private ArrayList<CustomData> mCustomDataCollection;    private AidlCallback mCallback;    @Override    public void onCreate() {        super.onCreate();        mCustomDataCollection = new ArrayList<>();        // TODO Populate the list with stored value...    }    @Nullable    @Override    public IBinder onBind(Intent intent) {        return mBinder;    }    @Override    public boolean onUnbind(Intent intent) {        Log.d(TAG, "onUnbind");        return super.onUnbind(intent);    }    @Override    public void onDestroy() {        super.onDestroy();        Log.d(TAG, "onDestroy");    }    private static boolean isPrimeImpl(long number) {        // Implementation left out for brevity...        return false;    }    private void getDataSinceImpl(CustomData[] result, Date since) {        int size = mCustomDataCollection.size();        Log.d(TAG, "getDataSinceImpl size = " + size);        Log.d(TAG, "since: " + since);        int pos = 0;        for (int i = 0; i < size && pos < result.length; i++) {            CustomData storedValue = mCustomDataCollection.get(i);            Log.d(TAG, "storedValue " + i + ": " + storedValue.getCreated());            if (since.before(storedValue.getCreated())) {                Log.d(TAG, "add " + i);                result[pos++] = storedValue;            }        }    }    private void storeDataImpl(CustomData data) {        int size = mCustomDataCollection.size();        try {            Thread.sleep(30000);        } catch (InterruptedException e) {            e.printStackTrace();        }        for (int i = 0; i < size; i++) {            CustomData customData = mCustomDataCollection.get(i);            if (customData.equals(data)) {                mCustomDataCollection.set(i, data);                return;            }        }        mCustomDataCollection.add(data);    }    private final ApiInterfaceV1.Stub mBinder = new ApiInterfaceV1.Stub() {        @Override        public boolean isPrime(long value) throws RemoteException {            return isPrimeImpl(value);        }        @Override        public void getAllDataSince(long timestamp, CustomData[] result) throws RemoteException {            getDataSinceImpl(result, new Date(timestamp));        }        @Override        public void storeData(CustomData data) throws RemoteException {            Log.d(TAG, data.getName() + " -- " + data.getCreated());            storeDataImpl(data);            if(mCallback != null){                mCallback.onDataUpdated(new CustomData[]{data});            }        }        @Override        public void setCallback(AidlCallback callback) throws RemoteException {            mCallback = callback;            mCallback.asBinder().linkToDeath(new DeathRecipient() {                @Override                public void binderDied() {                    Log.d(TAG, "binderDied");                    mCallback = null;                }            }, 0);        }    };}

服务类中AidlCallback 和 ApiInterfaceV1 分别对应上一步的AidlCallback.java和ApiInterfaceV1.java,与客户端进行通信的就是mBinder,mBinder继承了ApiInterfaceV1.StubApiInterfaceV1.Stub是上一步自动生成的一个类, 查看它的代码,ApiInterfaceV1.Stub实际就是一个Binder,同时它实现了接口ApiInterfaceV1,但没有实现ApiInterfaceV1具体的方法,因此它还是个抽象类,具体实现就得由我们在服务类中完成。而Binder在服务端正是通过onTransact(...)这个方法进行接收客户端的调用的(客户端则是调用transact(...)方法)。

因此服务端要完成的操作是:

1、定义Aidl文件。

2、IDE自动生成Aidl文件对应的java文件。

3、在服务类中定义一个成员变量,这个成员变量是上一步java文件中生成的Stub的一个实例,并且由我们实现Aidl文件中定义的接口方法。

4、在onBind()方法中返回此成员变量。

5、在AndroidManifest.xml文件中声明服务,并且在<inten-filter>中定义<action android.name="..." />,这样客户端可通过此action定位此服务。

 

客户端:

最终项目结构:

技术分享  技术分享运行效果,三个按钮对应服务的三个方法。

Step 1:将服务端的Aidl文件和CustomData.java文件拷贝到客户端,注意保持package与服务端一致。

Step 2:菜单 Build --> Make Project 或者 Rebuild Project,如果顺利的话,就能够自动生成AidlCallback.aidl文件和ApiInterfaceV1.aidl文件对应的.java文件。

Step 3:实现回调接口AidlCallback.Stub,并定义一个此实现的变量作为客户端成员变量,用于给服务端设置回调。

// Implement the callback        mAidlCallback = new AidlCallback.Stub() {            @Override            public void onDataUpdated(final CustomData[] data) throws RemoteException {                Log.d(TAG, data[0].getName() + " was updated");                runOnUiThread(new Runnable() {                    @Override                    public void run() {                        Log.d(TAG, data[0].getName() + " was updated");                        Toast.makeText(MainActivity.this, data[0].getName() + " was updated",                                Toast.LENGTH_SHORT).show();                    }                });            }        };

 

Step 4:实现接口ServiceConnection,这步是使用Binder进行服务通信必须做的一件事,因为服务端onBind()传出的Binder,最终作为onServiceConnected(ComponentName name, IBinder service)的参数传到客户端。在此方法的实现中,通过ApiInterfaceV1.Stub.asInterface(service)可得到服务端的代理对象。

@Override    public void onServiceConnected(ComponentName name, IBinder service) {        mService = ApiInterfaceV1.Stub.asInterface(service);        try {            mService.setCallback(mAidlCallback);        } catch (RemoteException e) {            e.printStackTrace();        }    }

Step 5:bindService,通过Intent并指定action(与服务端设置的保存一致),来实现绑定,不过从Android 5.0(Lollipop)开始需要显示Intent才能完成bindService。

// Since Android 5.0(Lollipop), bindService should use explicit intent.        Intent intent = new Intent("com.ldb.android.example.aidlservice.AidlService");        bindService(                new Intent(createExplicitFromImplicitIntent(this, intent)),                this, BIND_AUTO_CREATE);

Step 6:unbindService。

以上是实现客户端与服务端进行通信的基本步骤。

客户端实例代码:

package com.ldb.android.example.aidlclient;import android.content.ComponentName;import android.content.Context;import android.content.Intent;import android.content.ServiceConnection;import android.content.pm.PackageManager;import android.content.pm.ResolveInfo;import android.os.IBinder;import android.os.RemoteException;import android.support.v7.app.AppCompatActivity;import android.os.Bundle;import android.util.Log;import android.view.View;import android.widget.Button;import android.widget.EditText;import android.widget.Toast;import com.ldb.android.example.aidl.AidlCallback;import com.ldb.android.example.aidl.ApiInterfaceV1;import com.ldb.android.example.aidl.CustomData;import java.util.Date;import java.util.GregorianCalendar;import java.util.List;public class MainActivity extends AppCompatActivity implements ServiceConnection{    private static final String TAG = "MainActivity";    private ApiInterfaceV1 mService;    private EditText mNumber;    private Button mPrime;    private Button mStore;    private Button mGet;    private AidlCallback.Stub mAidlCallback;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        mNumber = (EditText) findViewById(R.id.number_input);        mPrime = (Button) findViewById(R.id.prime);        mStore = (Button) findViewById(R.id.store);        mGet = (Button) findViewById(R.id.get);        mPrime.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                onCheckForPrime();            }        });        mStore.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                final CustomData customData = http://www.mamicode.com/new CustomData();                String name = mNumber.getText().toString();                customData.setName(name);                customData.getReference().add(name + "1");                customData.getReference().add(name + "2");                customData.getReference().add(name + "3");//                customData.setCreated(new GregorianCalendar(2016, 9, 1, 9, 0 ).getTime());//                try {                    new Thread(new Runnable() {                        @Override                        public void run() {                            try {                                mService.storeData(customData);                                Log.d(TAG, "mService.storeData1");                            } catch (RemoteException e) {                                e.printStackTrace();                            }                        }                    }).start();                    Log.d(TAG, "mService.storeData2");//                } catch (RemoteException e) {//                    e.printStackTrace();//                }            }        });        mGet.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                CustomData[] result = new CustomData[10];                Date since = new GregorianCalendar(2016, 8, 1, 8, 0 ).getTime();                try {                    mService.getAllDataSince(since.getTime(), result);                    Log.d(TAG, "Result: " + result.length);                    for(int i = 0; i < result.length; i++){                        CustomData customData = result[i];                        if(customData != null) {                            Log.d(TAG, result[i].getName() + result[i].getCreated().toString());                            for (String s : result[i].getReference()) {                                Log.d(TAG, "  -- " + s);                            }                        }                    }                } catch (RemoteException e) {                    e.printStackTrace();                }            }        });    }    @Override    protected void onResume() {        super.onResume();        // Since Android 5.0(Lollipop), bindService should use explicit intent.        Intent intent = new Intent("com.ldb.android.example.aidlservice.AidlService");        bindService(                new Intent(createExplicitFromImplicitIntent(this, intent)),                this, BIND_AUTO_CREATE);        // Implement the callback        mAidlCallback = new AidlCallback.Stub() {            @Override            public void onDataUpdated(final CustomData[] data) throws RemoteException {                Log.d(TAG, data[0].getName() + " was updated");                runOnUiThread(new Runnable() {                    @Override                    public void run() {                        Log.d(TAG, data[0].getName() + " was updated");                        Toast.makeText(MainActivity.this, data[0].getName() + " was updated",                                Toast.LENGTH_SHORT).show();                    }                });            }        };    }    @Override    protected void onPause() {        super.onPause();        unbindService(this);    }    @Override    public void onServiceConnected(ComponentName name, IBinder service) {        mService = ApiInterfaceV1.Stub.asInterface(service);        try {            mService.setCallback(mAidlCallback);        } catch (RemoteException e) {            e.printStackTrace();        }    }    @Override    public void onServiceDisconnected(ComponentName name) {        mService = null;    }    public void onCheckForPrime() {        long number = Long.valueOf(mNumber.getText().toString());        boolean isPrime = false;        try {            isPrime = mService.isPrime(number);        } catch (RemoteException e) {            e.printStackTrace();        }        String message = isPrime ? "number_is_prime" : "number_not_prime";        Toast.makeText(this, message, Toast.LENGTH_SHORT).show();    }    public static Intent createExplicitFromImplicitIntent(Context context, Intent implicitIntent) {        // Retrieve all services that can match the given intent        PackageManager pm = context.getPackageManager();        List<ResolveInfo> resolveInfo = pm.queryIntentServices(implicitIntent, 0);        // Make sure only one match was found        if (resolveInfo == null || resolveInfo.size() != 1) {            return null;        }        // Get component info and create ComponentName        ResolveInfo serviceInfo = resolveInfo.get(0);        String packageName = serviceInfo.serviceInfo.packageName;        String className = serviceInfo.serviceInfo.name;        ComponentName component = new ComponentName(packageName, className);        // Create a new intent. Use the old one for extras and such reuse        Intent explicitIntent = new Intent(implicitIntent);        // Set the component to be explicit        explicitIntent.setComponent(component);        return explicitIntent;    }}

布局文件:

<?xml version="1.0" encoding="utf-8"?><LinearLayout    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"    android:orientation="vertical">    <EditText        android:id="@+id/number_input"        android:layout_width="match_parent"        android:layout_height="wrap_content"/>    <Button        android:id="@+id/prime"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:text="prime"/>    <Button        android:id="@+id/store"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:text="store"/>    <Button        android:id="@+id/get"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:text="get"/></LinearLayout>

 

Android Programming: Pushing the Limits -- Chapter 7:Android IPC -- AIDL