2

Serviceインターフェイスを使用してリモートプロセスでと通信するアプリケーションがありMessengerます。設定方法の基本的なアーキテクチャは次のとおりです。

  • アプリケーションは、サービスへのアクセスを必要とするいくつかの「操作」オブジェクトを生成します。
  • 各「操作」には、から応答データを受信するために使用されるHandlerラップが含まれています。MessengerService
  • 操作が実行されると、操作はにラップMessengerされてIntent呼び出さstartService()れ、メッセージがリモートサービスに渡されます。
  • リモートサービスは、のパラメータに基づいていくつかの作業を行い、その操作のためににIntentを送信することによって応答を返します。MessageMessenger

操作に存在する基本的なコードは次のとおりです。

public class SessionOperation {

    /* ... */

    public void runOperation() {
        Intent serviceIntent = new Intent(SERVICE_ACTION);
        /* Add some other extras specific to each operation */
        serviceIntent.putExtra(Intent.EXTRA_EMAIL, replyMessenger);

        context.startService(serviceIntent);
    }

    private Handler mAckHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            //Process the service's response
        }
    };
    protected Messenger replyMessenger = new Messenger(mAckHandler);
}

そして、サービスがどのように構成されているかのスニペット(基本的IntentServiceにはキューが空のときにシャットダウンしない):

public class WorkService extends Service {
    private ServiceHandler mServiceHandler;

    private final class ServiceHandler extends Handler {
        public ServiceHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            onHandleIntent((Intent)msg.obj);
        }
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        //If intent has a message, queue it up
        Message msg = mServiceHandler.obtainMessage();
        msg.obj = intent;
        mServiceHandler.sendMessage(msg);

        return START_STICKY;
    }

    private void onHandleIntent(Intent intent) {
        Messenger replyTarget = intent.getParcelableExtra(Intent.EXTRA_EMAIL);

        /* Do some work */

        Message delivery = Message.obtain(...);
        replyTarget.send(delivery);
    }
}

これはすべて素晴らしくうまく機能します。いくつかの異なるアプリケーションから同じサービスに大量の操作を送信でき、それらはすべて処理され、適切な場所に応答を送信します。でも...

アプリケーションが十分に長く実行され、十分なアクティビティがあると、がクラッシュすることに気づきましたOutOfMemoryError。MATでHPROFデータを見ると、これらすべての操作がメモリに残っていることに気付きました。これらの操作は、ガベージコレクターから人質にされていましたMessenger。どうやら、Messengerインスタンスは、GCルートとしてカウントされるBinderへの長期ネイティブ接続を作成しており、各「操作」オブジェクトを無期限にメモリに保持しています。

MATトレースの例

Messenger「操作」が終了したときにこのメモリリークが発生しないようにクリアまたは無効にする方法があるかどうか誰かが知っていますか?Service同じ方法でIPCをに実装して、複数の異なるオブジェクトが要求を行い、非同期で結果を取得できるようにする別の方法はありますか?

前もって感謝します!

4

2 に答える 2

12

AndroidチームのDianneHackbornからの非常に有益な洞察のおかげで、問題は、リモートサービスプロセスがまだガベージコレクションを行っていないためです。メッセンジャーのインスタンスは、事実上、その時点までアプリケーションのプロセスの人質にインスタンスを保持していました。

これは彼女の返信のテキストです:

プロセス間でメッセンジャーを送信するには、他のプロセスがメッセンジャーと通信するためにGREFを保持する必要があるのは事実です。バグ(発生したが、リリースされたプラットフォームバージョンがあるかどうかはわかりません)を除いて、他のプロセス自体がこれに関する参照を保持しなくなったときにGREFがリリースされます。Dalvikで物事について話しているとき、「参照を保持しなくなった」とは、一般に「反対側がJavaプロキシオブジェクトをガベージコレクションした」ことを意味します。

つまり、Messenger(または任意のIBinderオブジェクト)を別のプロセスにスローすると、独自のプロセスのDalvik VMはそのオブジェクト自体のメモリを管理できなくなり、すべてのリモートオブジェクトがそれを解放できるようになるまで依存します。ローカルでリリースされます。また、これには、IBinderが参照するすべてのオブジェクトも含まれます。

これに対処するための一般的なパターンは、アクセスする残りのオブジェクトへの参照を保持するIBinder/MessengerでWeakReferenceを使用することです。これにより、リモートプロセスがまだIBinderに参照を持っている場合でも、ローカルのガベージコレクターが他のすべてのオブジェクト(非常に重い場合があり、ビットマップなどの大きなものを保持している場合があります)をクリーンアップできます。もちろん、これを行う場合は、これらの他のオブジェクトが不要になるまで参照を保持する何かが必要です。そうしないと、ガベージコレクターが不要になる前にそれらをクリーンアップできます。

他にお勧めするのは、IPCごとにMessengerオブジェクトをインスタンス化する設計を行わないことです。各IPC呼び出しに渡すメッセンジャーを1つ作成します。そうしないと、他のプロセスが参照を保持し続けるために保持されている多くのリモートオブジェクトを生成できます。これは、これらの呼び出しによって作成されるすべてのオブジェクトが小さいため、反対側が積極的にガベージコレクションを行わないためです。

詳細: https ://groups.google.com/d/msg/android-developers/aK2o1W2xrMU/Z0-QujnU3wUJ

于 2012-08-30T22:32:09.093 に答える
0

Activityバックグラウンドにある場合でも、messageからを取得するため、これが最善の方法かどうかはわかりませんService

サービスが接続されたらすぐに、にバインドしてサービスにservice登録する必要があると思います。messengerそして、messenger切断するときに登録を解除します。

AOSPでExportVcardActivityを確認してください。それはこれらの線に沿って何かをたどっています。

于 2012-08-30T20:48:36.887 に答える