IPCを介してリモートサービスに接続し、比較的長いタスクをスケジュールし、コールバックを待っている間作業を続けて結果が得られる単純なAndroidアプリケーションがあると想像してみてください。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();
}
ここで私はさらに2つの問題に直面しました:
- コールバックランナブルの処理を有効にするために呼び出す
Looper.loop()
必要がありましたが、IPCがブロックされるため、最初と同じ結果になります。実際のマルチスレッドはありません。 2回目のコールバック(最初のサイクルが終了して戻り値の後)に登録すると、例外が発生します。
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
ますか? - クリーンで適切なキューメカニズムを作成するには、どのインスツルメント/アーキテクチャを使用すればよいですか。これにより、
Looper.loop()
/を常に呼び出さなくても、適切なスレッドでIPCメソッドを呼び出すことができます。Looper.quit()
ありがとうございました。