Aidl內耗時操作並供客戶端調用

項目中有個需求,在項目中實現基站定位,並將基站定位寫成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通信

源碼

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章