2

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つの問題に直面しました:

  1. コールバックランナブルの処理を有効にするために呼び出すLooper.loop()必要がありましたが、IPCがブロックされるため、最初と同じ結果になります。実際のマルチスレッドはありません。
  2. 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それはどのようにデッドスレッドを指すことができますか?

サービスがタスクをキューに入れ、タスクが終了したときにコールバックを作成できるという考え全体は、私には気が狂ったようには聞こえないので、経験豊富な誰かが私に説明してくれることを願っています。

  1. 異なるスレッドから実際にIPC呼び出しを行うことができないのはなぜですか?
  2. 私の何が問題になっていHandlerますか?
  3. クリーンで適切なキューメカニズムを作成するには、どのインスツルメント/アーキテクチャを使用すればよいですか。これにより、 Looper.loop()/を常に呼び出さなくても、適切なスレッドでIPCメソッドを呼び出すことができます。Looper.quit()

ありがとうございました。

4

1 に答える 1

2

プログラムが機能しない理由を説明できません。しかし、スレッドと非同期コールバックを含むバージョン:

@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();
}

正常に動作するはずです。

AndroidがAIDLやその他のタイプのバインダートランザクション用にスレッドを配置する方法は次のとおりです。

  • 呼び出し元と呼び出し先が同じプロセスにある場合、それは単純なメソッド呼び出しになります。したがって、とscheduleHeavyTaskまったく同じスレッドから呼び出された場合は問題ありませんonButtonClick。同様に、toの呼び出しはonResult、タスクを実行しているスレッドからの単純なメソッド呼び出しである必要があります。
  • onButtonClick呼び出し元と呼び出し先が異なるプロセスにある場合、Androidは呼び出し先プロセス内のスレッドのプールからバインダートランザクションを実行します(バインダー#1などと呼ばscheduleHeavyTaskれます)。ここでも非常に巧妙ですonResult。同じスレッドの場合、呼び出し元プロセス内でonButtonClick直接呼び出すように見えます。onResult

「安全でない」または「バインドされていない」スレッドからの呼び出しを回避するメカニズムはまったくありません。したがって、投稿したこの単純なアプローチは機能するはずです。あなたが言うように、それは一般的なパターンであり、私はそれを何度も使用しました。したがって、余分なルーパーやハンドラーをいじるよりも、これを続行することをお勧めします。

ここにいくつかのアイデアがあります:

  • デバッガーで実行します。サービスとアクティビティが同じプロセスにあると仮定すると、ブレークポイントを設定して、適切なメソッド呼び出しが行われていることを確認できるはずです。
  • できない場合でも、コマンドラインから起動するddmsか、Eclipse内に[デバイス]ビューと[スレッド]ビューを表示します。これにより、各スレッドが実行していることを正確に把握できます。コールスタックを取得できます。フルオンデバッガが不便な場合でも、通常はこれを使用できます。
  • synchronizedどこかにその言葉はありますか?何が起こっているのかというonResultと、先に進んで実行したいのですが、一部のモニターでブロックされています。ブロックが解除されるとすぐに実行される可能性があります。
于 2013-01-29T22:36:40.140 に答える