假设我们有一个简单的 Android 应用程序,它通过 IPC 连接到远程服务,安排一个相对较长的任务,然后在等待回调的同时继续工作并获得一些结果。AIDL接口:
IRemoteService.aidl
package com.var.testservice;
import com.var.testservice.IServCallback;
interface IRemoteService {
void scheduleHeavyTask(IServCallback callback);
}
IRemoteService.aidl
package com.var.testservice;
interface IServCallback {
void onResult(int result);
}
活动代码:
package com.var.testclient;
import com.var.testservice.IServCallback;
import com.var.testservice.IRemoteService;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import android.view.View;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
public class MainActivity extends Activity {
private static final String TAG = "TestClientActivity";
private IServCallback.Stub servCallbackListener = new IServCallback.Stub(){
@Override
public void onResult(int result) throws RemoteException {
Log.d(TAG, "Got value: " + result);
}
};
private ServiceConnection servConnection = new ServiceConnection(){
@Override
public void onServiceConnected(ComponentName name, IBinder binder) {
service = IRemoteService.Stub.asInterface(binder);
}
@Override
public void onServiceDisconnected(ComponentName name) {
service = null;
}
};
private IRemoteService service;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if(!bindService(new Intent(IRemoteService.class.getName()), servConnection, Context.BIND_AUTO_CREATE)){
Log.d(TAG, "Service binding failed");
} else {
Log.d(TAG, "Service binding successful");
}
}
@Override
protected void onDestroy() {
if(service != null) {
unbindService(servConnection);
}
super.onDestroy();
}
public void onButtonClick(View view){
Log.d(TAG, "Button click");
if(service != null){
try {
service.scheduleHeavyTask(servCallbackListener);
} catch (RemoteException e) {
Log.d(TAG, "Oops! Can't schedule task!");
e.printStackTrace();
}
}
}
}
服务代码:
package com.var.testservice;
import android.app.Service;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
import android.util.Log;
public class TestService extends Service {
private static final String TAG = "TestService";
class TestServiceStub extends IRemoteService.Stub {
private IServCallback servCallback;
//These 2 fields will be used a bit later
private Handler handler;
private int result;
//The simpliest implementation. In next snippets I will replace it with
//other version
@Override
public void scheduleHeavyTask(IServCallback callback)
throws RemoteException {
servCallback = callback;
result = doSomethingLong();
callback.onResult(result);
}
private int doSomethingLong(){
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
return 42;
}
}
}
@Override
public IBinder onBind(Intent intent) {
return new TestServiceStub();
}
}
这个版本虽然很笨(它使应用程序的 UI 线程挂起 5 秒,导致 ANR),但它成功地通过 IPC 执行所有调用,将结果传递给活动。如果我尝试将计算放入单独的线程中,就会出现问题:
@Override
public void scheduleHeavyTask(IServCallback callback)
throws RemoteException {
servCallback = callback;
Runnable task = new Runnable(){
@Override
public void run() {
result = doSomethingLong();
try {
Log.d(TAG, "Sending result!");
servCallback.onResult(result);
} catch (RemoteException e) {
e.printStackTrace();
}
}
};
new Thread(task).start();
}
在这种情况下,回调只是没有传递给活动:服务成功调用servCallback.onResult(result);
,但在活动中没有调用任何内容。没有例外,没有线索,没有幸存者:完美的祈求谋杀。我找不到有关这种行为的可能原因的任何信息,所以如果有人能澄清这里发生的事情,我将不胜感激。我的建议是有某种安全机制,跟踪绑定了哪些确切的线程,并忽略来自其他线程的“不安全”调用(当我们尝试处理来自非 UI 线程的 UI 元素时会发生类似的情况),但我不能确定。
最明显的解决方案是将回调调用发布到绑定线程,所以我做了这个:
@Override
public void scheduleHeavyTask(IServCallback callback)
throws RemoteException {
Log.d(TAG, "Schedule request received.");
servCallback = callback;
if(Looper.myLooper() == null) {
Looper.prepare();
}
handler = new Handler();
Runnable task = new Runnable(){
@Override
public void run() {
result = doSomethingLong();
Log.d(TAG, "Posting result sender");
handler.post(new Runnable(){
@Override
public void run() {
try {
Log.d(TAG, "Sending result!");
servCallback.onResult(result);
} catch (RemoteException e) {
e.printStackTrace();
}
Looper.myLooper().quit();
Log.d(TAG, "Looper stopped");
}
});
}
};
new Thread(task).start();
Looper.loop();
}
在这里,我遇到了另外两个问题:
- 我必须调用
Looper.loop()
以启用回调可运行对象的处理,但它会阻止 IPC,所以我得到与开始时相同的结果 - 没有实际的多线程; 第二次注册回调(在第一个循环完成并返回值之后)导致异常:
java.lang.RuntimeException: Handler (android.os.Handler) sending message to a Handler on a dead thread at android.os.MessageQueue.enqueueMessage at android.os.Handler.sendMessageAtTime at android.os.Handler.sendMessageDelayed at android.os.Handler.post at com.var.testservice.TestService$TestServiceStub$1.run at java.lang.Thread.run
这让我完全不解:我从实际中创建了一个新实例Looper
,它怎么会指向死线程?
服务能够排队任务并在完成后进行回调的整个想法对我来说听起来并不疯狂,所以我希望更有经验的人可以解释一下:
- 为什么我实际上不能从不同的线程进行 IPC 调用?
- 我的怎么了
Handler
? - 我应该使用什么工具/架构来建立一个干净、适当的队列机制,这样它就可以在正确的线程上调用 IPC 方法而无需不断调用
Looper.loop()
/Looper.quit()
?
谢谢你。