最近做了一個android的語音項目,service端集成了語音服務,該service提供相關的語音SDK,比如語音搜索音樂,然後音樂app service集成這個SDK,從而獲得語音能力。
在實際開發的過程中遇到兩個棘手的問題:
1 Voice Service起來了,如果Music並沒有啓動,有人請求語音搜索音樂,那麼怎麼能告訴你呢?
2 如果Music起來並且通過bind接口綁定了Voice,那麼voice如何知道呢?
針對兩個問題我們進行分析:
1 Voice Service起來而Music沒有起來,如果有人請求語音搜索音樂,觸發了相關的回調
onSeachMusic(MusicBean musicBean)
這個時候怎麼發給music呢?
答案其實不難,就是如果music service沒有啓動,則它也不會利用service提供的aidl向voice service註冊語音監聽服務,這個時候只要判斷這個就可以解決,然後還有一個問題,就是把準備觸發的音樂搜索的回調(AIDL中提供給Music的)給保存起來,等到我啓動了music service讓他向我註冊,再發送給他。
這裏有一個技巧,爲了實現所有方法的統一管理,我這裏使用動態代理,去緩存即將要發送的任意一個client的回調的方法和參數。
public boolean onSearchMusic(MusicModel model)
{
DataCenter.getInstance().recordVoiceRequest(DataCenter.IntentDomain.MUSIC);
IMusicControlCb callback = (IMusicControlCb) RegisterAIDLManager
.getInstance()
.getInterface(ServiceManager.MUSIC_KEY);
boolean ret = false;
try {
if (callback != null && callback.asBinder().pingBinder()) {
ret = callback.searchMusic(model);//觸發音樂的搜索回調監聽
} else {
ret = CallbackProxyManager.getInstance().getMusicCallbackProxy().searchMusic(model);//代理
}
} catch (RemoteException e) {
e.printStackTrace();
}
return ret;
}
這裏有個難點就是AIDL文件如何使用Android的動態代理呢?Interface我們知道可以,但是aidl文件並不知道,廢話不多說直接上代碼,這裏用到了反射
private void initAudioBookCallbackProxy() {
Class<?> reflectCallback = null;
//核心部分
try {
reflectCallback = Class.forName("com.byton.vas.vassdk.IOnlineFMControlCb");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
mAudioBookCallbackProxyHandler = new CallbackProxyHandler(ServiceManager.AUDIO_BOOK_KEY);
mAudioBookCallbackProxy = (IOnlineFMControlCb) Proxy.newProxyInstance(
CallbackProxyManager.class.getClassLoader(), new Class[] { reflectCallback },//核心
mAudioBookCallbackProxyHandler);
}
這裏一旦完成,就可以利用動態代理對任意的aidl回調方法和參數進行緩存,然後去啓動相關的client應用服務。
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
boolean result = ServiceManager.getInstance().startRemoteService(key);
if (result) {
InvokeObject invokeObject = new InvokeObject();
invokeObject.setKey(key);
invokeObject.setMethod(method);
invokeObject.setObjects(objects);
CallbackEventsCache.getInstance().product(invokeObject);
return true;
}
return false;
}
一旦music的應用啓動了和我們綁定了,則這個時候必然向我們註冊,我們只要在註冊的地方,將所有即將發送給這個應用的緩存指令發送給他就可以了。
public void registerMusicControlCb(IMusicControlCb callback) {
RegisterAIDLManager.getInstance().addInterface(ServiceManager.MUSIC_KEY, callback);
CallbackEventsCache.getInstance().consume();
}
總結一下就是如下幾點:
- 判斷你是否活着
- 如果沒有活?則利用動態代理以及BlockingQuee構建消費者-生產者隊列緩存所有的語音aidl回調方法和參數
- 啓動相應的client端回調,向voice註冊。
- 觸發緩存中的回調方法。
第二個問題,如果client端music service被殺了,那麼我的語音指令怎麼辦。
有人可能會說,它不是和你bind了嗎?它死了voice service的unbind回調肯定能知道。對不起錯了,如果有N個client bind servcie,除非所有的client都斷開了,你的service纔會收到unbind回調。
這裏有個方法就是
callback.asBinder().pingBinder()
這個AIDL的方法,可以用來判斷目標client是否和你還在綁定的狀態。一旦語音回調觸發,只要判斷這個就可以知道client是否活着,如果沒有,則可以按照第一個問題的解決方案進行處理。