最近做了一个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是否活着,如果没有,则可以按照第一个问题的解决方案进行处理。