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.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.Stub,ApiInterfaceV1.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定位此服务。

客户端:

最终项目结构:

Android Programming: Pushing the Limits -- Chapter 7:Android IPC -- AIDL  Android Programming: Pushing the Limits -- Chapter 7:Android IPC -- AIDL运行效果,三个按钮对应服务的三个方法。

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>
上一篇:06MySQL数据库入门


下一篇:ubuntu下Open vSwitch安装