項目中有個需求,在項目中實現基站定位,並將基站定位寫成aidl,供給其他應用遠程調用,實現方式就是 在項目中監聽附近的基站,通過獲取到基站的信息,去服務器上獲取基站的經緯度,所以 問題來了,一般形式的aidl的做法是寫一個aidl接口,需要的地方直接綁定服務,然後通過服務調用aidl接口,直接把結果返回給調用端,但是如果aidl的服務端裏面是耗時操作的話,直接返回,那麼會返回空值,因爲數據加載的速度遠遠要小於代碼執行速度,所以需要一個特殊的變種的aidl。
先看一下一般形式的aidl:`
1.第一步 寫一個接口,參數類型爲int,返回類型爲int.
interface ILocationService {
int registerCallback(int sum);
}
2.第二步 在服務端新建一個Service
public class RemoteService extends Service {
public IBinder onBind(Intent intent) {
return mBinder;
}
private final ILocationService.Stub mBinder = new ILocationService.Stub() {
@Override
public int registerCallback(int data) throws RemoteException {
//直接返回計算結果給調用端 (問題就是 如果計算的過程是一個耗時操作,那麼沒等耗時操作做完就調用了return 那麼客戶端收到的是一個空值)
return data+666;
}
};
}
第三步:在客戶端需要調用這個方法的時候加入
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
mService = ILocationService.Stub.asInterface(service);
}
public void onServiceDisconnected(ComponentName className) {
mService = null;
}
};
//獲得計算結果
int sum= mService .registerCallback(22); //打印結果爲688
第四步 也是最重要的一步:切記 在AndroidManifest.xml裏面註冊Service,另外如果是分成兩個應用調用的時候
要把aidl文件一模一樣的從服務端複製到客戶端,如果參數是實體類的話,要把實體類的路徑在服務端和客戶端統一
<service
android:name=".RemoteService"
android:enabled="true"
android:process=":remote"
android:exported="true">
<intent-filter>
<action android:name="com.daking.aidl.RemoteService" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</service>
------------------------------------------------------華麗的分割線--------------------------------------------------------
以上方案只能解決正常的aidl通訊問題,參數是自定義實體類,和耗時訪問操作還沒有解決,所以以下是針對這個問題的解決方案:
第一步 依舊是新建aidl文件,區別是新增一個回調的aidl接口
package com.easyliu.demo.aidl;
//不要忽略這個import aidl文件不會自動生成,需要自己去引用這個包,地址要修改成你自己的地址
import com.easyliu.demo.aidl.IRemoteServiceCallback;
interface IRemoteService {
void registerCallback(IRemoteServiceCallback cb);
void unregisterCallback(IRemoteServiceCallback cb);
}
第二步 按你的需要建一個回調實體類的接口
package com.easyliu.demo.aidl;
//Station是我的實體類 裏面存放着經緯度,基站等信息
import com.easyliu.demo.aidl.Station;
interface IRemoteServiceCallback {
//不需要返回類型,因爲做耗時操作 return是沒用的 傳入的參數類型是Station
void valueChanged(in Station value); //in 輸入,out 輸出,
}
第三步 新建實體類 並建立實體類的aidl
首先先建立 實體類的aidl 否則可能編輯器會報文件必須唯一的問題
package com.easyliu.demo.aidl;
parcelable Station;
然後再建真正的實體類Station 切記 一定要實現Parcelable接口 給實體類序列化
這個實體類要存放在和aidl文件一直的java目錄下,否則會報找不到這個實體類的錯誤
public class Station implements Parcelable{
private String cell_id;// cellid連接基站編碼
private String lac;// lac連接基站位置區域碼
private String mcc;// mcc MCC國家碼
private String mnc;// mnc MNC網號
private String signalstrength;// signalstrength連接基站信號強度
private String NetType;
private String lat;
private String log;
public Station(){}
……
}
}
第四步 Service改造
public class RemoteService extends Service {
Station station2;
RemoteCallbackList<IRemoteServiceCallback> mCallbacks = new RemoteCallbackList<IRemoteServiceCallback>();
private int mValue = 0;
private static final int REPORT_MSG = 1;
private int position=0;
@Override
public void onCreate() {
//在oncreate裏面可以放你的耗時操作
doInBackGround(stationBeans);
}
@Override
public void onDestroy() {
mCallbacks.kill();
mHandler.removeMessages(REPORT_MSG);
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
private final IRemoteService.Stub mBinder = new IRemoteService.Stub() {
public void registerCallback(IRemoteServiceCallback cb) {
if (cb != null) mCallbacks.register(cb);
}
public void unregisterCallback(IRemoteServiceCallback cb) {
if (cb != null) mCallbacks.unregister(cb);
}
};
private final Handler mHandler = new Handler() {
@Override public void handleMessage(Message msg) {
switch (msg.what) {
case REPORT_MSG: {
.//開始回調 一定要加上 否則回調失敗
final int N = mCallbacks.beginBroadcast();
for (int i=0; i<N; i++) {
try {
//將收到的值回調給aidl定義的方法
mCallbacks.getBroadcastItem(i).valueChanged(station2);
} catch (RemoteException e) {
}
}
//結束回調
mCallbacks.finishBroadcast();
} break;
default:
super.handleMessage(msg);
}
}
};
public void doInBackGround(final List<Station> data) {
//網絡請求等耗時操作
OkGo.<String>post(Static.Url)
.tag(this)
.params("cid", data.get(0).getCell_id())
.params("lac", data.get(0).getLac())
.params("mnc", data.get(0).getMnc())
.execute(new StringCallback() {
@Override
public void onSuccess(Response<String> response) {
ResultData newInstructionEntity = new Gson().fromJson(response.body(), ResultData.class);
station2 = new Station();
station2.setLac(String.valueOf(newInstructionEntity.getData().getLatitude()));
station2.setLog(String.valueOf(newInstructionEntity.getData().getLongitude()));
//獲得請求的返回值,並通過handler發消息通知
mHandler.sendEmptyMessage(REPORT_MSG);
}
})
}
}}
}
第五步:
客戶端調用
// 綁定啓動Service
Intent intent=new Intent(IRemoteService.class.getName());
intent.setPackage("com.easyliu.demo.aidldemo");
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
mService = ILocationService.Stub.asInterface(service);
/////////////////////////////加上綁定監聽/////////////////////////////
try {
mService.registerCallback(mCallback);
} catch (RemoteException e) {
}
}
public void onServiceDisconnected(ComponentName className) {
mService = null;
}
};
private IRemoteServiceCallback mCallback = new IRemoteServiceCallback.Stub() {
@Override
public void valueChanged(Station value) throws RemoteException {
//這個回調就是獲得結果的回調 value就是從服務器的請求結果 成功在客戶端獲取到服務端請求
……
}
};
筆記:
1.RemoteCallbackList。 使用這個類只要在Service使用單例模式就可以了,使用register和unregister方法來添加客戶端的回調,使用時,先beginBroadcast,在getBroadcastItem,最後finishBroadcast。他的主要作用是可以把多個callback保存到列表裏,在合適的時機同時回調,也可以防止重複的調用相同的任務
2. aidl通訊失敗的大多數原因就是 aidl文件放的目錄不對,或者aidl裏面引用的實體類或者其他文件沒有導包,和存放真正實體類的目錄客戶端和服務端沒有統一。
3. 實體類一定要實現系列化纔可以通過aidl通信