1. ホーム
  2. android

[解決済み] Androidでフォアグラウンドサービスが強制終了する

2023-04-20 18:32:45

質問

更新 : 私はこの問題に対する真の解決策を見いだすことができませんでした。 私が思いついたのは、接続が失われたときにいつでも以前の Bluetooth デバイスに自動的に再接続する方法です。 これは理想的ではありませんが、かなりうまく機能しているようです。 これに関する他の提案もお聞きしたいと思います。

私はこの質問とほぼ同じ問題を抱えています。 ウェイクロックの保持中および startForeground の呼び出し後にサービスが停止する デバイス (Asus Transformer)、サービスが停止するまでの時間 (30 ~ 45 分) 、ウェイクロックの使用、startForeground() の使用、および画面がオフになったときにアプリを開いている場合は問題が発生しないことを含めて、質問とほぼ同じ問題を抱えています。

私のアプリは別のデバイスとの bluetooth 接続を維持し、2 つのデバイス間でデータを送信するため、データを聞くために常にアクティブである必要があります。 ユーザーは自由にサービスを開始および停止することができ、実際、これが私が実装したサービスを開始または停止する唯一の方法です。 サービスが再起動すると、もう一方のデバイスとの Bluetooth 接続は失われます。

リンク先の質問の回答によると、startForeground() "はサービスが強制終了される可能性を低減しますが、それを防ぐことはできません"。 私はそれが事実であると理解していますが、この問題がない他のアプリの多くの例を見てきました (たとえば、Tasker など)。

ユーザーによって停止されるまでサービスを実行する機能がないと、私のアプリの有用性は大幅に減少します。 これを回避する方法はありますか?

サービスが停止されるたびに、私のlogcatにこれが表示されます。

ActivityManager: No longer want com.howettl.textab (pid 32321): hidden #16
WindowManager: WIN DEATH: Window{40e2d968 com.howettl.textab/com.howettl.textab.TexTab paused=false
ActivityManager: Scheduling restart of crashed service com.howettl.textab/.TexTabService in 5000ms

EDIT: また、私は、これは私が接続している他のデバイス上で発生しないようであることに注意してください。Cyanogen を実行している HTC レジェンド

編集: 次の出力は adb shell dumpsys activity services :

* ServiceRecord{40f632e8 com.howettl.textab/.TexTabService}

intent={cmp=com.howettl.textab/.TexTabService}

packageName=com.howettl.textab

processName=com.howettl.textab

baseDir=/data/app/com.howettl.textab-1.apk

resDir=/data/app/com.howettl.textab-1.apk

dataDir=/data/data/com.howettl.textab

app=ProcessRecord{40bb0098 2995:com.howettl.textab/10104}

isForeground=true foregroundId=2 foregroundNoti=Notification(contentView=com.howettl.textab/0x1090087 vibrate=null,sound=null,defaults=0x0,flags=0x6a)

createTime=-25m42s123ms lastActivity=-25m42s27ms

 executingStart=-25m42s27ms restartTime=-25m42s124ms

startRequested=true stopIfKilled=false callStart=true lastStartId=1

Bindings:

* IntentBindRecord{40a02618}:

  intent={cmp=com.howettl.textab/.TexTabService}

  binder=android.os.BinderProxy@40a9ff70

  requested=true received=true hasBound=true doRebind=false

  * Client AppBindRecord{40a3b780 ProcessRecord{40bb0098 2995:com.howettl.textab/10104}}

    Per-process Connections:

      ConnectionRecord{40a76920 com.howettl.textab/.TexTabService:@40b998b8}

All Connections:

  ConnectionRecord{40a76920 com.howettl.textab/.TexTabService:@40b998b8}

そして,出力される adb shell dumpsys activity :

* TaskRecord{40f5c050 #23 A com.howettl.textab}

numActivities=1 rootWasReset=false

affinity=com.howettl.textab

intent={act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.howettl.textab/.TexTab}

realActivity=com.howettl.textab/.TexTab

lastActiveTime=4877757 (inactive for 702s)

* Hist #1: ActivityRecord{40a776c8 com.howettl.textab/.TexTab}

    packageName=com.howettl.textab processName=com.howettl.textab

    launchedFromUid=2000 app=ProcessRecord{40bb0098 2995:com.howettl.textab/10104}

    Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.howettl.textab/.TexTab }

    frontOfTask=true task=TaskRecord{40f5c050 #23 A com.howettl.textab}

    taskAffinity=com.howettl.textab

    realActivity=com.howettl.textab/.TexTab

    base=/data/app/com.howettl.textab-1.apk/data/app/com.howettl.textab-1.apk data=/data/data/com.howettl.textab

    labelRes=0x7f060000 icon=0x7f020000 theme=0x0

    stateNotNeeded=false componentSpecified=true isHomeActivity=false

    configuration={ scale=1.0 imsi=0/0 loc=en_CA touch=3 keys=2/1/1 nav=1/2 orien=L layout=0x10000014 uiMode=0x11 seq=6}

    launchFailed=false haveState=true icicle=Bundle[mParcelledData.dataSize=1644]

    state=STOPPED stopped=true delayedResume=false finishing=false

    keysPaused=false inHistory=true visible=false sleeping=true idle=true

    fullscreen=true noDisplay=false immersive=false launchMode=2

    frozenBeforeDestroy=false thumbnailNeeded=false

    connections=[ConnectionRecord{40a76920 com.howettl.textab/.TexTabService:@40b998b8}]

...

Proc #15: adj=prcp /F 40e75070 959:android.process.acore/10006 (provider)

          com.android.providers.contacts/.ContactsProvider2<=Proc{40bb0098 2995:com.howettl.textab/10104}

Proc #16: adj=bak+2/F 40bb0098 2995:com.howettl.textab/10104 (foreground-service)

これらは、サービスがフォアグラウンドで実行されていることを示すために表示されます。

解決方法は?

オーケー、ドーキー。私はこの問題で地獄を見ました。以下は、その方法です。バグがあります。この投稿では、実装のバグを分析し、問題を回避する方法を説明します。

要約すると、物事がどのように機能するかを説明します。実行中のサービスは定期的に清掃され、約 30 分ごとに終了します。これより長く生き続けたいサービスは、Service.startForeground を呼び出す必要があり、通知バーに通知が表示されるため、ユーザーはサービスが永久に実行され、バッテリーの寿命を奪う可能性があることを知ることができます。常に3つのサービスプロセスだけがフォアグラウンドサービスとして指名することができます。3 つ以上のフォアグラウンド サービスがある場合、Android は最も古いサービスを廃棄して終了する候補として指名します。

残念ながら、Android には、サービス バインディング フラグのさまざまな組み合わせによって引き起こされる、フォアグラウンド サービスの優先順位付けに関するバグがあります。サービスをフォアグラウンド サービスとして正しく指名しても、プロセス内のサービスへの接続が特定の結合フラグの組み合わせで行われたことがある場合、Android はサービスを終了する可能性があります。詳細は以下のとおりです。

フォアグラウンドサービスである必要があるサービスは非常に少ないことに注意してください。一般に、フォアグラウンド サービスである必要があるのは、ユーザーがオン/オフしたり、キャンセルしたりできる何らかのインターネット接続が常にアクティブまたは長時間実行されている場合だけです。フォアグラウンドの状態を必要とするサービスの例。UPNP サーバー、非常に大きなファイルの長時間実行ダウンロード、Wi-Fi によるファイルシステムの同期、音楽の再生などです。

時々ポーリングしたり、システムのブロードキャスト レシーバーやシステム イベントを待っているだけなら、タイマーやブロードキャスト レシーバーへの応答でサービスを起動し、完了したらサービスを終了させる方がよいでしょう。これがサービスの設計通りの動作です。どうしても生き続けたい場合は、この先を読んでください。

よく知られた要件(Service.startForeground の呼び出しなど)にチェックを入れたら、次に見るべきは Context.bindService 呼び出しで使用するフラグです。バインドに使用するフラグは、対象となるサービスプロセスの優先度に予想外の影響を及ぼします。特に、特定のバインドフラグを使用すると、Android がフォアグラウンドサービスを誤って通常のサービスに格下げしてしまうことがあります。プロセスの優先度を割り当てるために使用されるコードは、かなり大きく変更されています。特に、API 14+ には、古いバインド フラグを使用したときにバグを引き起こす可能性のある改訂があります。

このユーティリティは、アクティビティ マネージャーがサービス プロセスに割り当てた優先度を把握し、誤った優先度を割り当てているケースを発見するために使用できます。サービスを立ち上げて実行し、ホスト コンピューター上のコマンド プロンプトから次のコマンドを発行します。

adb シェル dumpsys activity processes > tmp.txt

メモ帳(wordpad/writeではない)を使って内容を確認します。

まず、フォアグラウンド状態でサービスを実行することに成功したことを確認します。dumpsys ファイルの最初のセクションは、各プロセスの ActivityManager プロパティの説明を含んでいます。dumpsys ファイルの最初のセクションで、自分のアプリケーションに対応する次のような行を探します。

APP UID 10068 ProcessRecord{41937d40 2205:tunein.service/u0a10068} です。

次のセクションで foregroundServices=true であることを確認します。hidden と empty の設定は気にしないでください。これらは、プロセス内のアクティビティの状態を記述するもので、サービスを含むプロセスには特に関係がないように思われます。foregroundServiceがtrueでない場合、Service.startForegroundを呼び出してtrueにする必要があります。

次に見る必要があるのは、"Process LRU list (sorted by oom_adj):" と題されたファイルの末尾付近のセクションです。このリストのエントリから、Android が実際にアプリケーションをフォアグラウンド サービスとして分類したかどうかを判断できます。プロセスがこのリストの一番下にある場合、それは略式退出の有力候補です。プロセスがリストの最上位にある場合は、事実上破壊不可能です。

この表のある行を見てみましょう。

  Proc #31: adj=prcp /FS trm= 0 2205:tunein.service/u0a10068 (fg-service)

これは、すべてが正しく行われたフォアグラウンドサービスの例です。ここでの重要なフィールドは、"adj=" フィールドです。このフィールドは、すべてが完了した後にActivityManagerServiceによって割り当てられたプロセスの優先度を示しています。このフィールドは、"adj=prcp" (可視フォアグラウンド・サービス)、または"adj=vis" (可視プロセス、アクティビティ付き)、または"fore" (フォアグラウンドのアクティビティ付きプロセス)にしたいのです。もし、"adj=svc" (サービスプロセス)、 "adj=svcb" (レガシーサービス?)、 "adj=bak" (空のバックグラウンドプロセス) であれば、プロセスは終了候補であり、メモリの再要求がない場合でも30分に一度は終了させられることになります。この行の残りのフラグは、ほとんどが Google のエンジニアのための診断用デバッグ情報です。終了の判断はadjフィールドに基づいて行われます。簡単に言うと、/FSはフォアグラウンドサービス、/FAはアクティビティを持つフォアグラウンドプロセス、/Bはバックグラウンドサービスを表します。/Bはバックグラウンドサービスを示す。末尾のラベルは、そのプロセスに優先度が割り当てられた一般的な規則を示します。通常はadj=フィールドと一致するはずですが、他のサービスやアクティビティとのアクティブなバインディングのバインディングフラグにより、adj=の値が上方または下方に調整されることがあります。

バインドフラグのバグにつまづいた場合、dumpsys 行はこのようになります。

  Proc #31: adj=bak /FS trm= 0 2205:tunein.service/u0a10068 (fg-service)

adj フィールドの値がどのように間違って "adj=bak" (空のバックグラウンド プロセス) に設定されているかに注目してください。これは、プロセス スキャベンジングの目的で、おおよそ "please terminate me now so that I can end this pointless existence" と訳されます。また、行末の (fg-service) フラグは、"forground サービス ルールが "adj" の設定を決定するために使用されたことを示しています。fg-serviceルールが使用されたにもかかわらず、このプロセスにはadj設定 "bak"が割り当てられ、それは長くは続きません。平たく言えば、これはバグです。

つまり、目標は、プロセスが常に "adj=prcp" (またはそれ以上) を取得することを確実にすることです。そして、その目標を達成するための方法は、優先順位割り当てのバグを回避できるようになるまで、バインドフラグを微調整することです。

以下は、私が知っているバグです。(1) あらゆるサービスまたはアクティビティが Context.BIND_ABOVE_CLIENT を使用してサービスにバインドしたことがある場合、そのバインドがもはやアクティブではないとしても、adj= 設定が "bak" にダウングレードされる危険性があります。これは、サービス間のバインディングがある場合に特に当てはまります。4.2.1 のソースには明らかなバグがあります。(2) BIND_ABOVE_CLIENTはサービス間のバインディングには絶対に使わないでください。アクティビティからサービスへの接続にも使わないでください。BIND_ABOVE_CLIENT の動作を実装するために使われるフラグは、接続ごとではなく、プロセスごとに設定されるようです。したがって、フラグが設定されたアクティブなアクティビティからサービスへのバインディングがない場合でも、サービスからサービスへのバインディングでバグを誘発することになります。また、プロセス内に複数のサービスがある場合に、サービス間のバインディングで優先順位を確立する際にも問題があるようです。service-to-service バインディングで Context.BIND_WAIVE_PRIORITY (API 14) を使用すると、解決するようです。Activityからサービスへのバインディングでは、Context.BIND_IMPORTANTが多かれ少なかれ良いようです。そうすることで、アクティビティがフォアグラウンドにあるときにプロセスの優先順位を 1 ノッチ高くし、アクティビティが一時停止または終了したときに明白な害を及ぼしません。

しかし全体としては、sysdump がプロセスが正しい優先度を受け取ったと示すまで、bindService フラグを調整することが戦略です。

私の目的では、アクティビティからサービスへのバインディングには Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT を使用し、サービスからサービスへのバインディングには Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY を使うと、正しく動作するように思われます。あなたの走行距離とは異なるかもしれません。

私のアプリはかなり複雑です。2 つのバックグラウンド サービスがあり、それぞれが独立してフォアグラウンド サービスの状態を保持し、さらにフォアグラウンド サービスの状態を取ることができる 3 番目のサービスもあります。また、Activiteは別プロセスで実行されます(アニメーションをよりスムーズにします)。アクティビティとサービスを同じプロセスで実行しても、違いはないように見えました。

プロセスのスキャベンジングのルールの実装 (および sysdump ファイルのコンテンツを生成するために使用されるソース コード) は、コアとなるアンドロイド ファイルで見つけることができます。

frameworks\base\services\java\com\android\server\am\ActivityManagerService.java.

ボン・チャンス

PS: Android 5.0 の sysdump 文字列の解釈は以下の通りです。私はそれを使って作業したことがないので、好きなように解釈してください。4 は 'A' または 'S'、5 は "IF" または "IB" で、1 はできるだけ低く (おそらく 3 以下、デフォルト設定では 3 つのフォアグラウンド サービス プロセスのみがアクティブに維持されるため) したいものと思われます。

Example:
   Proc # : prcp  F/S/IF trm: 0 31719: neirotech.cerebrum.attention:blePrcs/u0a77 (fg-service)

Format:
   Proc # {1}: {2}  {3}/{4}/{5} trm: {6} {7}: {8}/{9} ({10}

1: Order in list: lower is less likely to get trimmed.

2: Not sure.

3:
    B: Process.THREAD_GROUP_BG_NONINTERACTIVE
    F: Process.THREAD_GROUP_DEFAULT

4:
    A: Foreground Activity
    S: Foreground Service
    ' ': Other.

5:
    -1: procState = "N ";
        ActivityManager.PROCESS_STATE_PERSISTENT: procState = "P ";
    ActivityManager.PROCESS_STATE_PERSISTENT_UI:procState = "PU";
    ActivityManager.PROCESS_STATE_TOP: procState = "T ";
    ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND: procState = "IF";
    ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND: procState = "IB";
    ActivityManager.PROCESS_STATE_BACKUP:procState = "BU";
    ActivityManager.PROCESS_STATE_HEAVY_WEIGHT: procState = "HW";
    ActivityManager.PROCESS_STATE_SERVICE: procState = "S ";
    ActivityManager.PROCESS_STATE_RECEIVER: procState = "R ";
    ActivityManager.PROCESS_STATE_HOME: procState = "HO";
    ActivityManager.PROCESS_STATE_LAST_ACTIVITY: procState = "LA";
    ActivityManager.PROCESS_STATE_CACHED_ACTIVITY: procState = "CA";
    ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT: procState = "Ca";
    ActivityManager.PROCESS_STATE_CACHED_EMPTY: procState = "CE";

{6}: trimMemoryLevel

{8} Process ID.
{9} process name
{10} appUid