私のアプリの1つには、onStartCommand
からのSTART_STICKY
リターンコードを使用してシステムが強制終了したときに自動的に再起動するbackgrouodサービスがあります。 Android KitKat。これに対する解決策はありますか?サービスを実行し続けるためにKitKatで何か別のことを行う必要がありますか?
注:Android-Devlopersグループでも、最近のアプリリストの動作からアプリをスワイプすることについて、同様の議論があります。この2つの問題は関連しているでしょうか? https://groups.google.com/forum/#!topic/Android-developers/H-DSQ4-tiac
編集:Android issue tracker:に未解決のバグがあることを見ました:
https://code.google.com/p/Android/issues/detail?id=6379https://code.google.com/p/Android/issues/detail? id = 63618
Edit2:別のプロセスで、startForeground
を使用し、AndroidManifest.xmlファイルでAndroid:stopWithTask="false"
フラグを使用してサービスを実行している場合でも同じことが起こります...
Edit3:Android issue trackerの関連バグ:
https://code.google.com/p/Android/issues/detail?id=62091https://code.google.com/p/Android/issues/detail? id = 5331https://code.google.com/p/Android/issues/detail?id=104308
以前の動作を得るための回避策はありますか?
これは100%の実用的なソリューションではありませんが、問題をほぼ完全に排除する限りでは最適です。これまでのところ、onTaskRemoved
( this answer を参照)とキープアライブ通知( this answer を参照)をオーバーライドして、このソリューションを統合しました。追加の回答は大歓迎です!
さらなる調査の結果、バグはすでにJelly Beanに存在しており、その解決策があるように見えます(少なくとも私の場合はうまくいくようです。必要に応じてテストを続け、回答を更新します)。
私が観察したことから、これはAlarmManager
で設定されたブロードキャストを受信するサービスでのみ発生します。
バグを再現するには、次の手順を実行します。
startForeground
を使用します)adb Shell dumpsys >C:\dumpsys.txt
を使用すると、異なるステップ間でサービスの状態を監視できます。 (dumpsysの出力でProcess LRU list
を探してください)ステップ2と3では、次のようなものが表示されます:
Proc # 2: prcp F/S/IF trm: 0 11073:<your process name>/u0a102 (fg-service)
具体的には、サービスがフォアグラウンドサービスとして実行されていることを示すF/S/IF
および(fg-service)
に注意してください(このリンクでdumpsysを分析する方法の詳細: https://stackoverflow.com/a/14293528/624109 )。
ステップ4の後、Process LRU list
にサービスが表示されなくなります。代わりに、デバイスのlogcatを見ると、次が表示されます。
I/ActivityManager(449): Killing 11073:<your process name>/u0a102 (adj 0): remove task
その動作を引き起こしていると思われるのは、受信したブロードキャストがサービスをフォアグラウンド状態から取り出してから強制終了するという事実です。
これを回避するには、PendingIntent
のAlarmManager
を作成するときに、このsimpleソリューションを使用できます(ソース:- https://code.google.com/p/Android/issues/detail?id=53313#c7 )
AlarmManager am = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent("YOUR_ACTION_NAME");
intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 1, intent, 0);
次の手順に注意してください。
FLAG_RECEIVER_FOREGROUND
を使用しますこれらの手順のいずれかを省略した場合、機能しません。
FLAG_RECEIVER_FOREGROUND
がAPI 16(Jelly bean)に追加されたため、これがバグが最初に現れたときに意味があることに注意してください...
KitKatはプロセスを殺すことに関してはより積極的である可能性が最も高く、これがKitKatで強調された理由ですが、これはすでにJelly beanに関連しているようです。
注2:サービス構成に関する質問の詳細に注意してください。マニフェストでendWithTaskをfalseに設定して、フォアグラウンドサービスとして別のプロセスで実行しています。
注3:アプリがAndroid.appwidget.action.APPWIDGET_CONFIGURE
メッセージを受信し、新しいウィジェットの構成アクティビティを表示するときにも同じことが起こります(上記の手順4を新しいウィジェットの作成に置き換えます)。ウィジェットプロバイダー(Android.appwidget.action.APPWIDGET_UPDATE
を処理するレシーバー)がアクティビティプロセスとは異なるプロセスで実行するように設定されている場合にのみ発生することがわかりました。設定アクティビティとウィジェットプロバイダーの両方が同じプロセス上にあるように変更した後、これは発生しなくなりました。
これはAndroid 4.4に存在するバグであり、次のように回避できたようです。
@Override
public void onTaskRemoved(Intent rootIntent) {
Intent restartService = new Intent(getApplicationContext(),
this.getClass());
restartService.setPackage(getPackageName());
PendingIntent restartServicePI = PendingIntent.getService(
getApplicationContext(), 1, restartService,
PendingIntent.FLAG_ONE_SHOT);
AlarmManager alarmService = (AlarmManager)getApplicationContext().getSystemService(Context.ALARM_SERVICE);
alarmService.set(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() +1000, restartServicePI);
}
この投稿 からこの回答を見つけました
ここでの問題は、AOSPベースのROMでは発生しないようです。つまり、CyanogenMod 11ベースのROMでこれを簡単に再作成できますが、AOSP ROM(およびエミュレーター))では、START_STICKYは期待どおりに動作します。 Nexus 5でこの動作を見ているように見える人々のレポートを見ると、おそらくAOSPの問題である可能性があります。
エミュレータとAOSP ROMで、プロセスに対して「kill 5838」を実行すると、logcatから次のように表示されます(予想どおり)。
12-22 18:40:14.237 D/Zygote ( 52): Process 5838 terminated by signal (15)
12-22 18:40:14.247 I/ActivityManager( 362): Process com.xxxx (pid 5838) has died.
12-22 18:40:14.247 W/ActivityManager( 362): Scheduling restart of crashed service com.xxxx/com.xxxx.NotifyingService in 5000ms
12-22 18:40:19.327 I/ActivityManager( 362): Start proc com.xxxx for service xxxx.pro/com.xxxx.NotifyingService: pid=5877 uid=10054 gids={50054, 3003, 3002, 1028}
最近のタスクリストから「スワイプ」してタスクを終了すると、同じ再起動動作が見られます。これはすべて良いことです-これは、コアAOSPコードが以前のレベルと同じように動作していることを意味します。
Cyanogenmodサービスコードを調べて、再起動のスケジュールが設定されていない理由を見つけようとしています。まだ運がありません。再スケジュールする必要があるようです。 CyanogenmodはAOSPが使用しないサービスマップを使用しますが、それが問題であるかどうかは不明です(疑わしい) https://github.com/CyanogenMod/Android_frameworks_base/blob/cm-11.0/services/Java/com /Android/server/am/ActiveServices.Java#L2092
ややハック的な回避策は、onTaskRemoved AlarmServiceと同様のメカニズムを使用して、X分後にアラームを有効にすることです。その後、アプリの起動中に数分ごとにアラームをリセットできます。そのため、本当に停止されて再起動されなかった場合にのみアラームがオフになります。これは絶対確実ではありません-ハンドラーを使用すると、リアルタイムを使用するアラームサービスに対してアップタイムが提供されるため、「リセット」ハンドラーよりも長い時間に設定されていても、アラームがトリガーされる可能性があります。ただし、インテントエキストラを設定した場合、サービスが既に稼働している場合はonStartCommandを無視して、これを操作に変えることができます。
私は次のハックのファンではありませんが、実際に害を及ぼすことはありません。ユーザーが明示的な強制終了を行うと、アラームマネージャーは設定されたアラームを破棄し、サービスが再起動しないようにします(ユーザーが望んでいることです)。
最初に、20分間アラームを設定するヘルパーメソッドを作成します。これにより、onStartCommandがサービスに対してトリガーされます。 2分ごとに、20分のアラームをリセットするハンドラーがあります。ハンドラーがリアルタイムの20分以内に実行されると、アラームは鳴りません。ハンドラーは、デバイスがスリープ状態にある場合でも実行されるとは限りません(これは良いことです)。
private void ensureServiceStaysRunning() {
// KitKat appears to have (in some cases) forgotten how to honor START_STICKY
// and if the service is killed, it doesn't restart. On an emulator & AOSP device, it restarts...
// on my CM device, it does not - WTF? So, we'll make sure it gets back
// up and running in a minimum of 20 minutes. We reset our timer on a handler every
// 2 minutes...but since the handler runs on uptime vs. the alarm which is on realtime,
// it is entirely possible that the alarm doesn't get reset. So - we make it a noop,
// but this will still count against the app as a wakelock when it triggers. Oh well,
// it should never cause a device wakeup. We're also at SDK 19 preferred, so the alarm
// mgr set algorithm is better on memory consumption which is good.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KitKat)
{
// A restart intent - this never changes...
final int restartAlarmInterval = 20*60*1000;
final int resetAlarmTimer = 2*60*1000;
final Intent restartIntent = new Intent(this, NotifyingService.class);
restartIntent.putExtra("ALARM_RESTART_SERVICE_DIED", true);
final AlarmManager alarmMgr = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
Handler restartServiceHandler = new Handler()
{
@Override
public void handleMessage(Message msg) {
// Create a pending intent
PendingIntent pintent = PendingIntent.getService(getApplicationContext(), 0, restartIntent, 0);
alarmMgr.set(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() + restartAlarmInterval, pintent);
sendEmptyMessageDelayed(0, resetAlarmTimer);
}
};
restartServiceHandler.sendEmptyMessageDelayed(0, 0);
}
}
OnCreateでこのメソッドを呼び出すことができます。また、onStartCommandで、サービスが既に稼働している場合はこれを無視してください。例えば:
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
...
if ((intent != null) && (intent.getBooleanExtra("ALARM_RESTART_SERVICE_DIED", false)))
{
Log.d(TAG, "onStartCommand after ALARM_RESTART_SERVICE_DIED");
if (IS_RUNNING)
{
Log.d(TAG, "Service already running - return immediately...");
ensureServiceStaysRunning();
return START_STICKY;
}
}
// Do your other onStartCommand stuff..
return START_STICKY;
}
alarmManagerを使用せずにこの問題を解決する簡単なトリックを見つけました。
サービスのonDestroy()
メソッドが呼び出されるたびにブロードキャストをリッスンするブロードキャストレシーバーを作成します。
_public class RestartService extends BroadcastReceiver {
private static final String TAG = "RestartService";
public RestartService() {
}
@Override
public void onReceive(Context context, Intent intent) {
Log.e(TAG, "onReceive");
context.startService(new Intent(context, YourService.class));
}
}
_
マニフェストにカスタマイズされたブロードキャストインテントを追加する
_<receiver
Android:name=".RestartService"
Android:enabled="true" >
<intent-filter>
<action Android:name="restartApps" />
</intent-filter>
</receiver>
_
次に、おそらく次のように、onDestroy()
からブロードキャストを送信します。
_@Override
public void onDestroy() {
Intent intent = new Intent("restartApps");
sendBroadcast(intent);
super.onDestroy();
stopThread();
}
_
onDestroy()
からonTaskRemoved(Intent intent)
を呼び出す
このトリックは、ユーザーがタスクマネージャからサービスを閉じるたびにサービスを再起動し、設定から強制的に終了します。これもあなたの助けになることを願っています