56

私のアプリでは、Context#startService()でサービスを開始し、 Context#bindService()でサービスにバインドするパターンを使用しています。これは、クライアントが現在サービスにバインドされているかどうかに関係なく、サービスの有効期間を制御できるようにするためです。ただし、最近、アプリがシステムによって強制終了されると、実行中のサービスがすぐに再起動されることに気付きました。この時点で、サービスが停止するように指示されることはありません。これにより、停止が発生するたびにバッテリーが消耗します。最小限の例を次に示します。

ここで同様の問題を抱えている人を見つけましたが、診断も解決もされていません。

サービス:

@Override
public void onCreate() {
    Toast.makeText(this, "onCreate", Toast.LENGTH_LONG).show();
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    return START_NOT_STICKY;
}

@Override
public IBinder onBind(Intent intent) {
    return new Binder();
}

アクティビティ:

@Override
protected void onStart() {
    super.onStart();
    Intent service = new Intent(this, BoundService.class);
    startService(service);
    bindService(service, mServiceConnection, 0);
}

@Override
protected void onStop() {
    unbindService(mServiceConnection);
    Toast.makeText(this, "unbindService", Toast.LENGTH_SHORT).show();
    super.onStop();
}

それをテストするために、サービスを開始してそれにバインドするアプリを起動しました。次に、アプリをバックアウトすると、バインドが解除されます (ただし、サービスは実行されたままになります)。それから私はやった

$ adb shell am kill com.tavianator.servicerestart

案の定、5 秒後に "onCreate" トーストが表示され、サービスが再開されたことを示します。Logcat はこれを示します:

$ adb logcat | grep BoundService
W/ActivityManager(  306): Scheduling restart of crashed service com.tavianator.servicerestart/.BoundService in 5000ms
I/ActivityManager(  306): Start proc com.tavianator.servicerestart for service com.tavianator.servicerestart/.BoundService: pid=20900 uid=10096 gids={1028}

startService() パターンを BIND_AUTO_CREATE に置き換えると、(アプリがまだサービスにバインドされている間にアプリをクラッシュさせても) 問題は発生しません。サービスにバインドしない場合にも機能します。しかし、開始、バインド、およびバインド解除の組み合わせによって、サービスが停止することはないようです。

アプリを強制終了する前に dumpsys を使用すると、次のようになります。

$ adb shell dumpsys activity services com.tavianator.servicerestart
ACTIVITY MANAGER SERVICES (dumpsys activity services)
  Active services:
  * ServiceRecord{43099410 com.tavianator.servicerestart/.BoundService}
    intent={cmp=com.tavianator.servicerestart/.BoundService}
    packageName=com.tavianator.servicerestart
    processName=com.tavianator.servicerestart
    baseDir=/data/app/com.tavianator.servicerestart-2.apk
    dataDir=/data/data/com.tavianator.servicerestart
    app=ProcessRecord{424fb5c8 20473:com.tavianator.servicerestart/u0a96}
    createTime=-20s825ms lastActivity=-20s825ms
    executingStart=-5s0ms restartTime=-20s825ms
    startRequested=true stopIfKilled=true callStart=true lastStartId=1
    Bindings:
    * IntentBindRecord{42e5e7c0}:
      intent={cmp=com.tavianator.servicerestart/.BoundService}
      binder=android.os.BinderProxy@42aee778
      requested=true received=true hasBound=false doRebind=false
4

3 に答える 3

31

このドキュメントセクションを参照してください: サービス ライフサイクルの変更 (1.6 以降)

更新しました

いくつかの調査(プロジェクトを作成し、説明されたコマンドを段階的に実行するなど)の後、コードが「サービスライフサイクルの変更」で説明されているとおりに機能していることがわかりました

私が見つけたもの:何も
adb shell am kill com.tavianator.servicerestart殺しません
(試してみるとadb shell、シェルam kill com.tavianator.servicerestart
エラーメッセージが表示されますError: Unknown command: kill

したがって、
アプリケーションを
実行adb shell
し、シェル実行psコマンド で実行しますシェル実行コマンドで
アプリのPID番号を見つけます どこであなたのPID番号 はサービスの強制終了手順を繰り返します(それが独自のプロセスで実行されている場合) サービスが実行されているかどうかを確認します(すべきではありません) 、5〜7秒後に再起動し ます 1つの解決策を更新します(十分ではありませんが、場合によっては使用できます)は次のとおりです。
kill <app_xx_PID>




stopSelf()

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    Toast.makeText(this, "onStartCommand", Toast.LENGTH_LONG).show();
    stopSelf();
    return START_NOT_STICKY;
}


更新
されたソリューションを更新する

void writeState(int state) {
    Editor editor = getSharedPreferences("serviceStart", MODE_MULTI_PROCESS)
            .edit();
    editor.clear();
    editor.putInt("normalStart", state);
    editor.commit();
}

int getState() {
    return getApplicationContext().getSharedPreferences("serviceStart",
            MODE_MULTI_PROCESS).getInt("normalStart", 1);
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    if (getState() == 0) {
        writeState(1);
        stopSelf();
    } else {
        writeState(0);
        Toast.makeText(this, "onStartCommand", Toast.LENGTH_LONG).show();
    }
    return START_NOT_STICKY;
}

プロセスが強制終了されたときにサービスが再開されるのはなぜですか?

この文書によると:

サービスが開始されると、それを開始したコンポーネントから独立したライフサイクルがあり、サービスを開始したコンポーネントが破棄された場合でも、サービスはバックグラウンドで無期限に実行できます。そのため、サービスは stopSelf() を呼び出してジョブが完了したときに停止する必要があります。または、別のコンポーネントが stopService() を呼び出してサービスを停止できます
注意: システム リソースの浪費とバッテリー電力の消費を避けるために、アプリケーションの動作が終了したらサービスを停止することが重要です。必要に応じて、他のコンポーネントは stopService() を呼び出してサービスを停止できます。サービスのバインディングを有効にした場合でも、サービスへの呼び出しを受け取った場合は、常に自分でサービスを停止する必要があります。onStartCommand()

一方、ドキュメントには次のように書かれています。

*START_NOT_STICKY* - onStartCommand() が戻った後にシステムがサービスを強制終了した場合、配信する保留中のインテントがない限り、サービスを再作成しないでください。これは、不要なときにサービスを実行しないようにするための最も安全なオプションであり、アプリケーションが未完了のジョブを簡単に再開できる場合です。

したがって、このドキュメントといくつかの実験を読んだ後、システムは手動で強制終了されたサービスを未完成 (クラッシュ: @see W/ActivityManager(306): Scheduling restart of crashed service) として扱い、onStartCommand によって返された値にもかかわらず再起動すると思います。


stopSelf() または stopService() -再起動なし、ジョブが完了した場合はなぜですか?

于 2012-10-02T12:37:56.603 に答える
10

ここでの問題は、 を呼び出すことによって開始された Service のstartService()場合、そのバインディングの内部アカウンティング ( bindService()/への呼び出しの影響を受けるunbindService()) が、プロセスが強制終了されたときに再起動がスケジュールされた後に、サービスが正しく停止されるのを何らかの形で妨げていることだと思います.

あなたの好みに応じて、この問題を回避するための 3 つの選択肢があるようです (最初の 2 つについては既に質問に記載されています)。

  • startService()/stopService()のみを使用し、/ をまったく使用しないでbindService()くださいunbindService()

  • bindService()/unbindService()のみを ( とともにBIND_AUTO_CREATE) 使用し、/ はまったく使用しないでstartService()くださいstopService()

  • startService()/stopService() bindService()/が必要な場合は、サービスunbindService()のメソッドをオーバーライドして、そこからonUnbind()呼び出します。stopSelf()

    @Override
    public boolean onUnbind(Intent intent) {
        stopSelf();
        return super.onUnbind(intent);
    }
    

私のテストから:

  • 最初の選択肢を使用すると、次の行がログに出力されます。

    W/ActivityManager( nnn): クラッシュしたサービス xxxx の再起動を 5000 ミリ秒でスケジューリング

    その後、サービスは実際には再起動されません。

  • 2番目の選択肢では、バインドを解除した後にサービスが破棄されます(onStop()例のアクティビティ内)。

  • 3 番目の選択肢は、基本的にコードを 2 番目の選択肢のように動作させます (大幅に変更する必要はありません)。

お役に立てれば!

于 2012-10-04T20:59:35.627 に答える
2

自分のパターンを見直してみませんか?

START_NOT_STICKY フラグは、Android の進化中のある時点 (1.6 以降?) で登場しました。おそらく、その時点で導入されたサービス ライフ サイクルの微妙な後退に直面しているでしょう。そのため、今のところ同様に微妙な解決策が見つかり、すべての可能なデバイスで動作することが魔法のように検証されたとしても、新しい Android バージョンで必ずしも動作するとは限りません。

とにかく、そのライフサイクルパターンは異常に見えます。それが、この風変わりな問題に遭遇するまさにその理由かもしれません.

2 つのケースを考えてみましょう。

  1. 普通 (?)。何らかのアクティビティがサービスを開始します。その後すぐに、それは縛られ、使用され、解放されます。なんか止まった?この場合、バッテリーの大幅な消耗がないのはなぜですか?

  2. 異常な。ある時点 (ランダム?) でサービスが終了しました。再起動すると、いくつかの間違った仮定が実装され、アクティブなままになり、バッテリーを消耗するようなことをしますか?

それはすべてかなり奇妙に見えます。

あなたの製品の並行プロセスを分析します。すべての重要なケース。いくつかの図など。ほとんどの場合、結果として、そのようなパターンの必要性が排除されます。

それ以外の場合、回避策は脆弱なハックになるようです。

于 2012-10-07T20:22:26.583 に答える