通常心例,AIDL 支持以下類型。
- Java 編程語(yǔ)言中的所有原語(yǔ)類型(如 int鞋囊、long契邀、char、boolean 等等)
- String
- CharSequence
- List
List 中的所有元素都必須是以上列表中支持的數(shù)據(jù)類型失暴、其他 AIDL 生成的接口或您聲明的可打包類型坯门。 可選擇將 List 用作“通用”類(例如微饥,List)。另一端實(shí)際接收的具體類始終是 ArrayList古戴,但生成的方法使用的是 List 接口欠橘。 - Map
Map 中的所有元素都必須是以上列表中支持的數(shù)據(jù)類型、其他 AIDL 生成的接口或您聲明的可打包類型现恼。 不支持通用 Map肃续,如 Map
注意事項(xiàng)
在 aidl 文件中,除了 Java 編程語(yǔ)言中的所有原語(yǔ)類型叉袍、String始锚、CharSequence、List喳逛、Map,其他在 AIDL 文件中用到的類瞧捌,你必須使用 import 語(yǔ)句導(dǎo)入,否則會(huì)報(bào)錯(cuò)润文。
當(dāng)你使用實(shí)現(xiàn)Parceable 的自定義類型的時(shí)候姐呐,當(dāng)其作為參數(shù)的時(shí)候,你必須為其制定是輸入或者是輸出參數(shù)典蝌。
in 表示輸入?yún)?shù)曙砂,即服務(wù)端可以修改該類型
out 表示輸出參數(shù),即客戶端可以修改該類型骏掀,客戶端不行
inout 表示客戶端和服務(wù)端都可以修改該類型
如: void onSuccess(int code,in MusicInfo musicInfo);
Server (服務(wù)端的實(shí)現(xiàn))
服務(wù)端主要有三個(gè)步驟
- 將請(qǐng)求抽象成接口鸠澈,并編寫 aidl 文件;
- 編寫一個(gè) Service截驮,實(shí)現(xiàn)接口款侵,處理客戶端的請(qǐng)求,并將 binder 返回回去侧纯;
- 在 AndroidManifet 配置 Service新锈,將我們的 Service 暴露出去。
一眶熬、將請(qǐng)求抽象成接口妹笆,并編寫 aidl 文件
首先我們先來(lái)看一下 IPlayService aidl 文件,下面的代碼中娜氏,我們定義了一個(gè) play 方法拳缠,有兩個(gè)參數(shù),name 是代表歌曲的名字贸弥,IPlayListener 是一個(gè)接口窟坐。需要注意的是它不是一個(gè) java 類,是 aidl 文件 。這樣才能在服務(wù)端和客戶端之間傳遞
package xj.musicserver;
// Declare any non-default types here with import statements
import xj.musicserver.IPlayListener;
interface IPlayService {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
void play(String name,IPlayListener iPlayListener);
}
要手動(dòng)添加 import xj.musicserver.IPlayListener;
package xj.musicserver;
// Declare any non-default types here with import statements
import xj.musicserver.MusicInfo;
interface IPlayListener {
/**
* Demonstrates some basic types that you can use as parameters
* and return values in AIDL.
*/
void onError(int code);
void onSuccess(int code,in MusicInfo musicInfo);
}
要手動(dòng)添加 import xj.musicserver.MusicInfo;
接下來(lái)我們?cè)賮?lái)看一下我們的實(shí)體類 MusicInfo哲鸳,實(shí)現(xiàn)了 Parceable 接口
//下面是自定義的一個(gè)MusicInfo子類臣疑,實(shí)現(xiàn)了Parcelable
public class MusicInfo implements Parcelable {
private long id;
private String title;
private String album;
private int duration;
private long size;
private String artist;
private String url;
private String displayName;
public MusicInfo(long id, String title, String album, int duration, long size, String artist,
String url, String displayName) {
this.id = id;
this.title = title;
this.album = album;
this.duration = duration;
this.size = size;
this.artist = artist;
this.url = url;
this.displayName = displayName;
}
public MusicInfo(){
}
protected MusicInfo(Parcel in) {
id = in.readLong();
title = in.readString();
album = in.readString();
duration = in.readInt();
size = in.readLong();
artist = in.readString();
url = in.readString();
displayName = in.readString();
}
//必須提供一個(gè)名為CREATOR的static final屬性 ,
//該屬性需要實(shí)現(xiàn)android.os.Parcelable.Creator<T>接口
public static final Creator<MusicInfo> CREATOR = new Creator<MusicInfo>() {
@Override
public MusicInfo createFromParcel(Parcel in) {
return new MusicInfo(in);
}
@Override
public MusicInfo[] newArray(int size) {
return new MusicInfo[size];
}
};
public MusicInfo(long id, String title) {
this.id=id;
this.title=title;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeLong(id);
dest.writeString(title);
dest.writeString(album);
dest.writeInt(duration);
dest.writeLong(size);
dest.writeString(artist);
dest.writeString(url);
dest.writeString(displayName);
}
public void readFromParcel(Parcel reply) {
id=reply.readLong();
title=reply.readString();
album=reply.readString();
duration=reply.readInt();
size=reply.readLong();
artist=reply.readString();
url=reply.readString();
displayName=reply.readString();
}
}
看 writeToParcel 和 readFromParcel 方法,需要注意的是 writeToParcel 和 readFromParcel 方法讀寫的順序是一一對(duì)應(yīng)的徙菠。
這里有一點(diǎn)要提醒大家的是 AndroidStudio 中讯沈,我們通過(guò)插件會(huì)自動(dòng)幫我們生成 writeToParcel 方法及 CREATOR,通常 readFromParcel 方法是不會(huì)自動(dòng)生成的婿奔,需要我們自己手動(dòng)編寫缺狠,不然會(huì)編譯不過(guò)。
注意了萍摊,接下來(lái)我們需要寫一個(gè) MusicInfo.aidl 文件
package xj.musicserver;
// Declare any non-default types here with import statements
parcelable MusicInfo;
指定包名挤茄,并聲明 MusicInfo 是 parcelable,注意 parcelable 是小寫的 p冰木,不是大寫的 P穷劈。這是一個(gè)規(guī)范,google 官方指定需要的片酝。同時(shí) MusicInfo.aidl 和 MusicInfo.java 需要放置在同個(gè)包中。
二挖腰、第二步,編寫一個(gè) Service雕沿,實(shí)現(xiàn)接口,處理客戶端的請(qǐng)求猴仑,并將 binder 返回回去审轮;
(以下代碼涉及到兩個(gè)接口的傳遞,過(guò)程大致如此辽俗,即自定義動(dòng)作IPlayListener接口從客戶端傳遞到服務(wù)端疾渣,服務(wù)端再將動(dòng)作傳遞到任務(wù)類MusicTask.IResultListener接口中。)
IPlayService.Stub mIPlayService=new IPlayService.Stub() {
@Override
public void play(String name, final IPlayListener iPlayListener) throws RemoteException {
MusicTask musicTask = new MusicTask(getApplicationContext(), name, "");
musicTask.setIResultListener(new MusicTask.IResultListener() {
@Override
public void onSuccess(MusicInfo musicInfo) {
try {
iPlayListener.onSuccess(0,musicInfo);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onFail(int code, MusicInfo musicInfo) {
try {
iPlayListener.onError(0);
} catch (RemoteException e) {
e.printStackTrace();
}
}
});
musicTask.execute();
}
};
@Nullable
@Override
public IBinder onBind(Intent intent) {
LogUtil.i(TAG, "onBind: intent = " +intent.toString());
return mIPlayService;
}
這里我們所做的工作就是到數(shù)據(jù)庫(kù)里面查詢看是否有相應(yīng)的歌曲崖飘,如果有榴捡,通過(guò) aidl 回調(diào),告訴客戶端我們查找成功朱浴,調(diào)用 onSuccess 方法吊圾,沒(méi)有找到,調(diào)用客戶端的 onError 方法翰蠢。
package xj.musicserver;
import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.AsyncTask;
import android.provider.MediaStore;
import android.support.annotation.NonNull;
import android.text.TextUtils;
import android.util.Log;
import java.util.ArrayList;
/**
* @author meitu.xujun on 2017/10/17
* @version 0.1
*/
public class MusicTask extends AsyncTask<Void,Void,Integer> {
// 這里只貼出主要代碼项乒,詳細(xì)代碼可到文章的末尾下載。
public MusicTask(Context context, String name, String artist){
mContext = context.getApplicationContext();
mName = name;
mArtist = artist;
}
@Override
protected Integer doInBackground(Void... params) {
LogUtil.i(TAG,"doInBackground: mName="+mName +" mArtist"+mArtist);
mResult = "";
ContentResolver contentResolver = mContext.getContentResolver();
Cursor cursor;
if (TextUtils.isEmpty(mArtist)) {
cursor = contentResolver.query(contentUri, projection, where_title, new String[]{getFixName(mName)},null);
}else{
cursor=contentResolver.query(contentUri, projection,
where_title_and_artist, new String[]{getFixName(mName),getFixName(mArtist)},null);
if(cursor==null || cursor.getCount()<=0){
cursor = contentResolver.query(contentUri, projection,
where_title, new String[]{getFixName(mName)},null);
}
}
if(cursor==null || cursor.getCount()<=0){
return RESULT_FAIL_MUSIC_NULL;
}
int displayNameCol = cursor.getColumnIndex(MediaStore.Audio.Media.DISPLAY_NAME);
int albumCol = cursor.getColumnIndex(MediaStore.Audio.Media.ALBUM);
int idCol = cursor.getColumnIndex(MediaStore.Audio.Media._ID);
int durationCol = cursor.getColumnIndex(MediaStore.Audio.Media.DURATION);
int sizeCol = cursor.getColumnIndex(MediaStore.Audio.Media.SIZE);
int artistCol = cursor.getColumnIndex(MediaStore.Audio.Media.ARTIST);
int urlCol = cursor.getColumnIndex(MediaStore.Audio.Media.DATA);
int titleCol = cursor.getColumnIndex(MediaStore.Audio.Media.TITLE);
mMusicInfos = new ArrayList<>();
String songName="";
while (cursor.moveToNext()){
songName = cursor.getString(titleCol);
MusicInfo musicInfo = getMusicInfo(cursor, displayNameCol, albumCol, idCol,
durationCol, sizeCol, artistCol, urlCol,titleCol);
mMusicInfos.add(musicInfo);
if(songName.equals(mName)){
mResult =mName;
mMusicInfo=musicInfo;
break;
}
}
if(mMusicInfo==null){
mMusicInfo =mMusicInfos.get(0);
}
return RESULT_SUCUESS;
}
@Override
protected void onPostExecute(Integer result) {
super.onPostExecute(result);
Log.i(TAG, "onPostExecute: result =" +result);
if(mIResultListener==null){
return;
}
if(result==RESULT_SUCUESS){
mIResultListener.onSuccess(mMusicInfo);
}else{
mIResultListener.onFail(result,mMusicInfo);
}
}
-----
public void setIResultListener(IResultListener IResultListener) {
mIResultListener = IResultListener;
}
public interface IResultListener{
void onSuccess(MusicInfo musicInfo);
void onFail(int code, MusicInfo musicInfo);
}
}
三梁沧、在 AndroidManifet 配置 Service檀何,將我們的 Service 暴露出去。
<service
android:name=".PlayService"
android:exported="true"
android:process=":remote">
<intent-filter>
<action android:name="xj.musicserver.IPlayService"/>
</intent-filter>
</service>
到這里我們服務(wù)端的配置就完成了
Client(客戶端) 的實(shí)現(xiàn)
實(shí)現(xiàn)客戶端大概需要幾個(gè)步驟:
- 將服務(wù)端的 aidl 文件 copy 過(guò)來(lái),注意要放在同一個(gè)包下频鉴。
- 通過(guò)服務(wù)端 Service 的 Action 啟動(dòng)栓辜, 當(dāng)啟動(dòng) Service 成功的時(shí)候,將服務(wù)端返回的 Binder 保存下來(lái)并轉(zhuǎn)化成相應(yīng)的實(shí)例砚殿。
- 之后如果想與服務(wù)端通訊啃憎,通過(guò)保存下來(lái)的 Binder,即可調(diào)用服務(wù)端的方法似炎。
一辛萍、第一步:將服務(wù)端的 aidl 文件 copy 過(guò)來(lái),注意要放在同一個(gè)包下羡藐。
如下圖所示贩毕,我們將 IPlayListener.aidl,IPalyService.aidl,MusicInfo.aidl 和 MuicInfo.java copy 到客戶端
二、第二步:通過(guò)服務(wù)端 Service 的 Action 啟動(dòng)仆嗦, 當(dāng)啟動(dòng) Service 成功的時(shí)候辉阶,將服務(wù)端返回的 Binder 保存下來(lái)并轉(zhuǎn)化成相應(yīng)的實(shí)例。
(注意在 Android 5.0以后瘩扼,不能通過(guò)隱式 Intent 啟動(dòng) service谆甜,必須制定包名)
這里的 Action 是與服務(wù)端一一對(duì)應(yīng)的。
case R.id.btn_start_service:
LogUtil.i(TAG,"onButtonClick: btn_start_service=");
Intent intent = new Intent(ACTION);
intent.setPackage(XJ_MUSICSERVER);
bindService(intent,mServiceConnection, Context.BIND_AUTO_CREATE);
public static final String ACTION = "xj.musicserver.IPlayService";
public static final String XJ_MUSICSERVER = "xj.musicserver";
三集绰、第三步:通過(guò)第二步保存下來(lái) 的 mIBinder规辱,與服務(wù)端進(jìn)行通訊。
當(dāng)我們調(diào)用 mIPlayService.play 方法的時(shí)候栽燕,服務(wù)端會(huì)去查找本地是否存在 丑八怪 這首歌罕袋,查找到的時(shí)候會(huì)回調(diào) onSuccess 方法,查找不到的時(shí)候會(huì)回調(diào) onError 方法碍岔。
case R.id.btn_contact:
LogUtil.i(TAG,"onButtonClick: btn_contact=");
if(mIPlayService!=null){
mIPlayService.play("丑八怪", mPlayListener);
}
IPlayListener.Stub mPlayListener=new IPlayListener.Stub(){
@Override
public void onError(int code) throws RemoteException {
LogUtil.i(TAG,"onError: code = "+code);
}
@Override
public void onSuccess(int code, MusicInfo musicInfo) throws RemoteException {
LogUtil.i(TAG,"onSuccess: code = "+code+ " musicInfo" + musicInfo.toString());
}
};
全文結(jié)束浴讯。
感謝:
Android AIDL 傳遞對(duì)象(Parceable)