在ContentProvider中调用基于Messenger的Service时抛出RuntimeException异常求助
在ContentProvider中调用基于Messenger的Service时抛出RuntimeException异常求助
兄弟,我来帮你搞定这个问题!先给你说清楚为啥会报这个错:
你调用的query()方法是运行在Binder线程池的线程里的,这类线程默认没有初始化Looper。而Messenger内部是依赖Handler工作的,Handler必须绑定到一个有Looper的线程才能创建,所以直接在query里搞Messenger相关操作,自然就抛出Can't create handler inside thread that has not called Looper.prepare()这个异常了。
下面给你两个解决方案,推荐用第二个:
方案一:临时给Binder线程初始化Looper(不推荐,容易出ANR)
这种方式是在query里临时给当前线程初始化Looper,但会阻塞线程,长时间运行可能导致应用无响应,只适合非常简单的场景:
@Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { Looper.prepare(); // 创建处理Service返回消息的Handler Handler handler = new Handler() { @Override public void handleMessage(Message msg) { // 这里处理Service返回的测试结果 // 处理完必须退出Looper,否则线程会一直阻塞 Looper.myLooper().quit(); } }; Messenger messenger = new Messenger(handler); // 发送请求消息给Service try { Message requestMsg = Message.obtain(null, 1); // 1是你的请求码 requestMsg.replyTo = messenger; // 假设你已经拿到了Service的Messenger实例 serviceMessenger.send(requestMsg); } catch (RemoteException e) { e.printStackTrace(); } // 启动Looper,会阻塞直到quit被调用 Looper.loop(); // 继续你的Cursor返回逻辑 return yourDatabase.query(...); }
方案二:用HandlerThread创建独立Looper线程(强烈推荐)
这个方法是创建一个专门的线程来处理和Service的IPC通信,不会阻塞Binder线程,也符合Android的线程规范:
首先在ContentProvider的onCreate()里初始化HandlerThread(只做一次):
private HandlerThread ipcThread; private Messenger replyMessenger; // 假设你已经持有Service的Messenger实例 private Messenger serviceMessenger; @Override public boolean onCreate() { // 创建并启动HandlerThread,自带Looper ipcThread = new HandlerThread("ServiceIPCThread"); ipcThread.start(); // 在HandlerThread的Looper上创建Handler,用来处理Service的回复 Handler handler = new Handler(ipcThread.getLooper()) { @Override public void handleMessage(Message msg) { // 这里处理Service返回的测试结果 // 比如把结果存入你的数据源(SQLite、内存缓存等) saveTestResultToDatabase(msg); // 通知ContentResolver数据变化,让查询者获取最新结果 getContext().getContentResolver().notifyChange(TEST_RESULT_URI, null); } }; replyMessenger = new Messenger(handler); // 这里可以绑定Service,获取serviceMessenger实例 bindServiceToGetMessenger(); return super.onCreate(); }
然后在query()方法里异步发送请求:
@Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { // 发送请求给Service,不用等结果,直接返回当前的Cursor try { Message requestMsg = Message.obtain(null, 1); requestMsg.replyTo = replyMessenger; serviceMessenger.send(requestMsg); } catch (RemoteException e) { e.printStackTrace(); } // 返回当前已有的数据,等Service返回结果后会自动通知更新 return yourDatabase.query(..., projection, selection, selectionArgs, null, null, sortOrder); }
最后别忘了在ContentProvider销毁时释放资源:
@Override public void shutdown() { // 安全退出HandlerThread ipcThread.quitSafely(); try { ipcThread.join(); } catch (InterruptedException e) { e.printStackTrace(); } // 解绑Service getContext().unbindService(serviceConnection); super.shutdown(); }
还有几个注意点要提醒你:
- 绝对不要在
query()里做耗时操作,因为这个方法是同步的,阻塞久了会触发ANR,所以必须用异步方式处理Service通信。 - Service的绑定和解绑要在ContentProvider的生命周期里处理,不要在query里重复绑定,避免资源泄漏。
备注:内容来源于stack exchange,提问作者Jihoon
相关产品推荐
相关产品推荐

