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.aidl
package com.ldb.android.example.aidl;

parcelable CustomData;

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

AidlCallback.aidl:

// AidlCallback.aidl
package com.ldb.android.example.aidl;

// Declare any non-default types here with import statements
import com.ldb.android.example.aidl.CustomData;

oneway interface AidlCallback {
    void onDataUpdated(in CustomData[] data);
}

ApiInterfaceV1.aidl:

// ApiInterfaceV1.aidl
package com.ldb.android.example.aidl;

// Declare any non-default types here with import statements
import 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 = 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>

 

posted @ 2016-09-04 10:24  liDB  阅读(434)  评论(0编辑  收藏  举报