ユーザーがルート(場所/ GPS)を記録できるアプリを作成しようとしています。画面がオフの場合でも場所が記録されるようにするために、場所の記録用に_foreground service
_を作成しました。 _Room Database
_を使用してサービスに挿入される_Dagger2
_に場所を保存します。
ただし、このサービスはAndroidによって強制終了されます。これはもちろん良くありません。メモリ不足の警告を購読することはできますが、30秒後に強制終了されるサービスの根本的な問題は解決しませんAndroid 8.0を実行している最新のハイエンド携帯電話での分数
「Hello world」アクティビティとサービスのみを含む最小限のプロジェクトを作成しました: https://github.com/RandomStuffAndCode/AndroidForegroundService
サービスはApplication
クラスで開始され、ルートロギングはBinder
を介して開始されます。
_// Application
@Override
public void onCreate() {
super.onCreate();
mComponent = DaggerAppComponent.builder()
.appModule(new AppModule(this))
.build();
Intent startBackgroundIntent = new Intent();
startBackgroundIntent.setClass(this, LocationService.class);
startService(startBackgroundIntent);
}
// Binding activity
bindService(new Intent(this, LocationService.class), mConnection, Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT);
// mConnection starts the route logging through `Binder` once connected. The binder calls startForeground()
_
おそらく_BIND_AUTO_CREATE
_フラグは必要ありません。サービスが停止しないようにさまざまなフラグをテストしてきました。これまでのところ運はありません。
プロファイラーを使用すると、メモリリークがあるようには見えませんが、メモリ使用量は約35MBで安定しています:
_adb Shell dumpsys activity processes > tmp.txt
_を使用すると、_foregroundServices=true
_と私のサービスがLRUリストの8番目にリストされていることを確認できます。
Proc # 3: prcp F/S/FGS trm: 0 31592:com.example.foregroundserviceexample/u0a93 (fg-service)
殺されないように信頼できるフォアグラウンドサービスを作成することはできないようです。それで、私たちは何ができますか?まあ...
START_STICKY
_でサービスを開始します。これは一種の無駄で、非常に美しいコードにはなりませんが、おそらくうまくいくでしょう...いくらか。 Androidを強制終了してからサービスを再作成するのにかかる時間に応じて、場所の大部分が失われる可能性があります。これは本当にAndroidのバックグラウンドで何かをする現在の状態ですか?もっと良い方法はありませんか?
編集:バッテリー最適化のためにアプリをホワイトリストに登録(無効化)しても、私のサービスが強制終了されることはありません
編集:Context.startForegroundService()
を使用してサービスを開始しても状況は改善されません
編集:したがって、これは確かにsomeデバイスでのみ発生しますが、一貫して発生します。膨大な数のユーザーをサポートしないか、本当にいコードを書くかを選択する必要があると思います。驚くばかり。
私はそれが遅れていることを知っていますが、これは誰かを助けるかもしれません。私も、さまざまなメーカーのOSに殺されることなく、フォアグラウンドサービスを維持するという同じ問題に直面しました。中国の製造元のOSのほとんどは、例外リスト(バッテリー、クリーナーなど)に追加されて自動起動が許可されている場合でも、フォアグラウンドサービスを強制終了します。
この link を見つけて、サービスを維持するというこの長い時間の問題を解決しました。
必要なのは、フォアグラウンドサービスを別のプロセスで実行することだけです。それでおしまい。
それには、AndroidManifest.xmlのサービスにAndroid:process
を追加します。
例えば:
<service Android:name=".YourService"
Android:process=":yourProcessName" />
Android:process
の詳細については、 docs を参照してください。
編集:SharedPreferencesは複数のプロセスで機能しません。この場合、IPC(プロセス間通信)メソッドを選択するか、ContentProviders
を使用して、プロセス間で使用されるデータを保存およびアクセスできます。docs から参照。
これらを使用することをお勧めします:AlarmManager
、PowerManager
、WakeLock
、Thread
、WakefulBroadcastReceiver
、Handler
、Looper
あなたはすでにそれらの「別々のプロセス」と他の微調整も使用していると思います。
Application
クラスで:
MyApp.Java
:
import Android.app.AlarmManager;
import Android.app.Application;
import Android.app.PendingIntent;
import Android.content.Context;
import Android.content.Intent;
import Android.os.PowerManager;
import Android.util.Log;
public final class MyApp extends Application{
public static PendingIntent pendingIntent = null;
public static Thread infiniteRunningThread;
public static PowerManager pm;
public static PowerManager.WakeLock wl;
@Override
public void onCreate(){
try{
Thread.setDefaultUncaughtExceptionHandler(
(thread, e)->restartApp(this, "MyApp uncaughtException:", e));
}catch(SecurityException e){
restartApp(this, "MyApp uncaughtException SecurityException", e);
e.printStackTrace();
}
pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
if(pm != null){
wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "TAG");
wl.acquire(10 * 60 * 1000L /*10 minutes*/);
}
infiniteRunningThread = new Thread();
super.onCreate();
}
public static void restartApp(Context ctx, String callerName, Throwable e){
Log.w("TAG", "restartApp called from " + callerName);
wl.release();
if(pendingIntent == null){
pendingIntent =
PendingIntent.getActivity(ctx, 0,
new Intent(ctx, ActivityMain.class), 0);
}
AlarmManager mgr = (AlarmManager) ctx.getSystemService(Context.ALARM_SERVICE);
if(mgr != null){
mgr.set(AlarmManager.RTC_WAKEUP,
System.currentTimeMillis() + 10, pendingIntent);
}
if(e != null){
e.printStackTrace();
}
System.exit(2);
}
}
そして、あなたのサービスで:
ServiceTrackerTest.Java
:
import Android.app.Service;
import Android.content.Context;
import Android.content.Intent;
import Android.graphics.BitmapFactory;
import Android.os.Handler;
import Android.os.IBinder;
import Android.os.Looper;
import Android.os.PowerManager;
import Android.support.v4.app.NotificationCompat;
import Android.support.v4.content.WakefulBroadcastReceiver;
public class ServiceTrackerTest extends Service{
private static final int SERVICE_ID = 2018;
private static PowerManager.WakeLock wl;
@Override
public IBinder onBind(Intent intent){
return null;
}
@Override
public void onCreate(){
super.onCreate();
try{
Thread.setDefaultUncaughtExceptionHandler(
(thread, e)->MyApp.restartApp(this,
"called from ServiceTracker onCreate "
+ "uncaughtException:", e));
}catch(SecurityException e){
MyApp.restartApp(this,
"called from ServiceTracker onCreate uncaughtException "
+ "SecurityException", e);
e.printStackTrace();
}
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
if(pm != null){
wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "TAG");
wl.acquire(10 * 60 * 1000L /*10 minutes*/);
}
Handler h = new Handler();
h.postDelayed(()->{
MyApp.infiniteRunningThread = new Thread(()->{
try{
Thread.setDefaultUncaughtExceptionHandler(
(thread, e)->MyApp.restartApp(this,
"called from ServiceTracker onCreate "
+ "uncaughtException "
+ "infiniteRunningThread:", e));
}catch(SecurityException e){
MyApp.restartApp(this,
"called from ServiceTracker onCreate uncaughtException "
+ "SecurityException "
+ "infiniteRunningThread", e);
e.printStackTrace();
}
Looper.prepare();
infiniteRunning();
Looper.loop();
});
MyApp.infiniteRunningThread.start();
}, 5000);
}
@Override
public void onDestroy(){
wl.release();
MyApp.restartApp(this, "ServiceTracker onDestroy", null);
}
@SuppressWarnings("deprecation")
@Override
public int onStartCommand(Intent intent, int flags, int startId){
if(intent != null){
try{
WakefulBroadcastReceiver.completeWakefulIntent(intent);
}catch(Exception e){
e.printStackTrace();
}
}
startForeground(SERVICE_ID, getNotificationBuilder().build());
return START_STICKY;
}
private void infiniteRunning(){
//do your stuff here
Handler h = new Handler();
h.postDelayed(this::infiniteRunning, 300000);//5 minutes interval
}
@SuppressWarnings("deprecation")
private NotificationCompat.Builder getNotificationBuilder(){
return new NotificationCompat.Builder(this)
.setContentIntent(MyApp.pendingIntent)
.setContentText(getString(R.string.notification_text))
.setContentTitle(getString(R.string.app_name))
.setLargeIcon(BitmapFactory.decodeResource(getResources(),
R.drawable.ic_launcher))
.setSmallIcon(R.drawable.ic_stat_tracking_service);
}
}
「非推奨」などを無視し、選択の余地がないときに使用します。コードは明確で、説明する必要がないと思います。回避策の提案と解決策についてだけです。