web-dev-qa-db-ja.com

Context.startForegroundService()はService.startForeground()を呼び出しませんでした

私はAndroid O OSでServiceクラスを使用しています。

私はバックグラウンドでServiceを使うつもりです。

Androidの推奨事項では、startService()startForegroundService()を使用するべきです。

startForegroundService()を使うと、Serviceはスローします。

Context.startForegroundService()はService.startForeground()を呼び出しませんでした

エラー。

何が問題なのですか?

143
NiceGuy

のGoogleのドキュメントから、Android 8.0の動作が変更されました

システムは、アプリがバックグラウンドにある間でも、アプリがContext.startForegroundService()を呼び出すことを許可します。ただし、アプリケーションは、サービスが作成されてから5秒以内にそのサービスのstartForeground()メソッドを呼び出す必要があります。

解決策:startForeground()で使用するServiceに対して、onCreate()Context.startForegroundService()を呼び出します。

以下も参照してください。 バックグラウンド実行制限 Android 8.0(Oreo)

72
zhuzhumouse

そのときサービスを開始するためにContextCompat.startForegroundService(this, intent)を呼び出しました

サービス中onCreate

 @Override
 public void onCreate() {
        super.onCreate();

        if (Build.VERSION.SDK_INT >= 26) {
            String CHANNEL_ID = "my_channel_01";
            NotificationChannel channel = new NotificationChannel(CHANNEL_ID,
                    "Channel human readable title",
                    NotificationManager.IMPORTANCE_DEFAULT);

            ((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).createNotificationChannel(channel);

            Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
                    .setContentTitle("")
                    .setContentText("").build();

            startForeground(1, notification);
        }
}
55
humazed

この問題が発生するのは、Androidフレームワークが5秒以内にサービスを開始することを保証できない一方で、フレームワークがサービスを開始しようとしたかどうかをチェックせずに5秒以内に開始する必要があるフォアグラウンド通知に対する厳密な制限があるためです。

これは間違いなくフレームワークの問題ですが、この問題に直面している開発者全員が最善を尽くしているわけではありません。

  1. startForeground通知はonCreateonStartCommandの両方に含まれている必要があります。サービスがすでに作成されていて、何らかの理由でアクティビティが再開しようとしている場合、onCreateは呼び出されません。

  2. 通知IDは0であってはいけません。そうでなければ、同じ理由でなくても同じクラッシュが発生します。

  3. stopSelfstartForegroundの前に呼び出してはいけません。

上記の3つすべてで、この問題は少しでも軽減できますが、それでも修正できません。実際の修正または回避策としては、ターゲットのSDKのバージョンを25にダウングレードすることです。

そして、Googleは何が起こっているのかさえ理解せず、これが彼らのせいではないと信じていないので、Android Pはまだこの問題を抱えていることに注意してください。 #36 そして #56

27
ZhouX

Context.startForegroundService(...)を呼び出してからContext.stopService(...)が呼び出される前にService.startForeground(...)を呼び出すと、アプリはクラッシュします。

私はここに明確な再現を持っています ForegroundServiceAPI26

私はこれでバグをオープンしました: Google issue tracker

これに関するいくつかのバグが修正されています。

うまくいけば、明確な再現手順で採掘することでうまくいくでしょう。

Googleチームから提供された情報

Googleイシュートラッカーコメント36

これはフレームワークのバグではありません。それは意図的です。アプリがstartForegroundService()でサービスインスタンスを開始する場合、それはmustそのサービスインスタンスをフォアグラウンド状態に遷移させて通知を表示します。startForeground()が呼び出される前にサービスインスタンスが停止されると、約束は満たされません。アプリのバグ。

再#31 、他のアプリが直接起動できるサービスを公開することは基本的に危険です。そのサービスのallstartアクションをstartForeground()を必要とするものとして扱うことによってそれを少し軽減することができますが、明らかにそれはあなたが考えていたものではないかもしれません。

Googleイシュートラッカーコメント56

ここで同じ結果につながるいくつかの異なるシナリオがあります。

startForegroundService()で何かを始めるのは単なるエラーですが、startForeground()を使って実際にフォアグラウンドに移行することを怠っているという、あからさまな意味論的な問題はまさにそれです:意味論的な問題。意図的に、これはアプリのバグとして扱われます。フォアグラウンドに移行する前にサービスを停止すると、アプリエラーになります。それがOPの核心であり、この問題が「意図したとおりに機能する」とマークされている理由です。

しかし、偽のこの問題の検出)についての質問もあります。これはisこの問題の追跡者の問題とは別に追跡されていますが、本物の問題として扱われています。 。

18
swooby

私は数日間これについて研究し、解決策を得ました。 Android Oでは、背景の制限を以下のように設定できます。

サービスクラスを呼び出しているサービス

Intent serviceIntent = new Intent(SettingActivity.this,DetectedService.class);
                    if (Android.os.Build.VERSION.SDK_INT >= Android.os.Build.VERSION_CODES.O){

                        SettingActivity.this.startForegroundService(serviceIntent);
                    }else{
                        startService(serviceIntent);
                    }

サービスクラスは次のようになります。

public class DetectedService extends Service { 
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

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

    @Override
    public void onCreate() {
        super.onCreate();
        int NOTIFICATION_ID = (int) (System.currentTimeMillis()%10000);
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            startForeground(NOTIFICATION_ID, new Notification.Builder(this).build());
        }


        // Do whatever you want to do here
    }
}
11
Ahmad Arslan

私は widget を持っていますが、これはデバイスが起きているときに比較的頻繁にアップデートされ、わずか数日のうちに何千ものクラッシュが発生していました。

発行トリガー

私はPixel 3 XLでさえも、私がそのデバイスにはそれほど多くの負荷がかかるとは思わなかったときにも、この問題に気付いた。そしてすべてのコードパスはstartForeground()でカバーされていました。しかし、それから私は多くの場合私のサービスが仕事を本当にすばやく終わらせることに気付きました。 私のアプリのきっかけは、システムが実際に通知を表示するようになる前にサービスが終了していたことだと思います。

回避策/解決策

私はすべてのクラッシュを取り除くことができました。私がしたことはstopSelf()への呼び出しを削除することでした。 (通知が確実に表示されるまで停止を遅らせることを考えていましたが、必要でなければユーザーに通知を見せたくありません。)サービスがしばらくアイドル状態だった場合あるいは、システムは例外をスローせずに通常の方法でそれを破棄します。

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    stopForeground(true);
} else {
    stopSelf();
}
8
Roy Solberg

私がこれであまりにも多くの時間を無駄にしたので、ちょうど頭を上げます。 startForeground(..)の最初のものとしてonCreate(..)を呼び出していたにもかかわらず、私はこの例外を受け続けました。結局、問題はNOTIFICATION_ID = 0を使っていることが原因であることがわかりました。他の値を使用するとこれが修正されるようです。

7
tobalr

この問題を回避する方法があります。私はこの修正を自分のアプリ(300K + DAU)で検証しました。これはこの種のクラッシュの少なくとも95%を減らすことができますが、それでも100%この問題を回避することはできません。

この問題は、Googleが文書化しているように、サービスが開始された直後にstartForeground()を呼び出した場合でも発生します。多くのシナリオでサービスの作成と初期化の処理にすでに5秒以上かかるため、startForeground()メソッドをいつ、どこで呼び出しても、このクラッシュは避けられません。

私の解決策は、startForegroundService()メソッドの開始後5秒以内にstartForeground()が実行されるようにすることです。これが詳細な解決策です。

  1. そもそもstartForegroundServiceを使わず、auto_createフラグを付けてbindService()を使ってください。サービスの初期化を待ちます。これがコードです、私のサンプルサービスはMusicServiceです:

    final Context applicationContext = context.getApplicationContext();
    Intent intent = new Intent(context, MusicService.class);
    applicationContext.bindService(intent, new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder binder) {
            if (binder instanceof MusicBinder) {
                MusicBinder musicBinder = (MusicBinder) binder;
                MusicService service = musicBinder.getService();
                if (service != null) {
                    // start a command such as music play or pause.
                    service.startCommand(command);
                    // force the service to run in foreground here.
                    // the service is already initialized when bind and auto_create.
                    service.forceForeground();
                }
            }
            applicationContext.unbindService(this);
        }
    
        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
    }, Context.BIND_AUTO_CREATE);
    
  2. 次にMusicBinderの実装です。

    /**
     * Use weak reference to avoid binder service leak.
     */
     public class MusicBinder extends Binder {
    
         private WeakReference<MusicService> weakService;
    
         /**
          * Inject service instance to weak reference.
          */
         public void onBind(MusicService service) {
             this.weakService = new WeakReference<>(service);
         }
    
         public MusicService getService() {
             return weakService == null ? null : weakService.get();
         }
     }
    
  3. 最も重要な部分、MusicServiceの実装であるforceForeground()メソッドは、startForeground()メソッドがstartForegroundService()の直後に呼び出されるようにします。

    public class MusicService extends MediaBrowserServiceCompat {
    ...
        private final MusicBinder musicBind = new MusicBinder();
    ...
        @Override
        public IBinder onBind(Intent intent) {
            musicBind.onBind(this);
            return musicBind;
        }
    ...
        public void forceForeground() {
            // API lower than 26 do not need this work around.
            if (Build.VERSION.SDK_INT >= 26) {
                Intent intent = new Intent(this, MusicService.class);
                // service has already been initialized.
                // startForeground method should be called within 5 seconds.
                ContextCompat.startForegroundService(this, intent);
                Notification notification = mNotificationHandler.createNotification(this);
                // call startForeground just after startForegroundService.
                startForeground(Constants.NOTIFICATION_ID, notification);
            }
        }
    }
    
  4. アプリを開かずにウィジェットでフォアグラウンドサービスを開始する(ウィジェットボタンをクリック)など、ステップ1のコードスニペットを保留中の意図で実行する場合は、ブロードキャストレシーバーでコードスニペットをラップすることができます。そして、service serviceコマンドの代わりにブロードキャストイベントを発生させます。

それがすべてです。それが役に立てば幸い。がんばろう。

5
Hexise

id が0に設定されているときに Service.startForeground(int id、Notification notification) が呼び出されると、Android 8以降でもこのエラーが発生します。

id int:NotificationManager.notify(int、Notification)による、この通知の識別子です。 0 であってはいけません。

4
almisoft

Android O API 26の問題

サービスをすぐに停止した場合(つまり、実際にサービスは実際には実行されず(言葉遣い/内包表記)、ANR間隔内にいる場合でも、stopSelfの前にstartForegroundを呼び出す必要があります)。

https://plus.google.com/116630648530850689477/posts/L2rn4T6SAJ5

このアプローチを試しましたが、それでもやはりエラーを引き起こします: -

if (Util.SDK_INT > 26) {
    mContext.startForegroundService(playIntent);
} else {
    mContext.startService(playIntent);
}

エラーが解決するまでこれを使用しています

mContext.startService(playIntent);
3
Andy Cass

非常に多くの答えが私の場合にはうまくいきませんでした。

こんなサービスを始めました。

              if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                    startForegroundService(intent);
                } else {
                    startService(intent);
                }

そしてonStartCommandの私のサービスで

   if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        Notification.Builder builder = new Notification.Builder(this, Android_CHANNEL_ID)
                .setContentTitle(getString(R.string.app_name))
                .setContentText("SmartTracker Running")
                .setAutoCancel(true);
        Notification notification = builder.build();
        startForeground(NOTIFICATION_ID, notification);
    } else {
        NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
                .setContentTitle(getString(R.string.app_name))
                .setContentText("SmartTracker is Running...")
                .setPriority(NotificationCompat.PRIORITY_DEFAULT)
                .setAutoCancel(true);
        Notification notification = builder.build();
        startForeground(NOTIFICATION_ID, notification);
    }

そして、NOTIFICATION_IDをゼロ以外に設定するのを忘れないでください。

private static final String Android_CHANNEL_ID = "com.xxxx.Location.Channel";プライベートstatic final int NOTIFICATION_ID = 555。

そうすべては完璧だったが8.1でもまだクラッシュしているので原因は以下の通りでした。

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            stopForeground(true);
        } else {
            stopForeground(true);
        }

私はremove notificatonでstop foregroundを呼び出しましたが、通知が削除されたサービスはバックグラウンドになり、バックグラウンドサービスはバックグラウンドからAndroid Oでは実行できません。プッシュ受信後に開始されました。

だから魔法の言葉は

  stopSelf();

これまでのところ、あなたのサービスがクラッシュしている何らかの理由で、上記のすべてのステップに従って、楽しんでください。

3
sandy

私はこの問題を調査してきました、そしてこれは私がこれまでに発見したものです。このクラッシュは、次のようなコードがあると発生する可能性があります。

MyForegroundService.Java

public class MyForegroundService extends Service {
    @Override
    public void onCreate() {
        super.onCreate();
        startForeground(...);
    }
}

MainActivity.Java

Intent serviceIntent = new Intent(this, MyForegroundService.class);
startForegroundService(serviceIntent);
...
stopService(serviceIntent);

例外は、コードの次のブロックでスローされます。

ActiveServices.Java

private final void bringDownServiceLocked(ServiceRecord r) {
    ...
    if (r.fgRequired) {
        Slog.w(TAG_SERVICE, "Bringing down service while still waiting for start foreground: "
                  + r);
        r.fgRequired = false;
        r.fgWaiting = false;
        mAm.mAppOpsService.finishOperation(AppOpsManager.getToken(mAm.mAppOpsService),
                    AppOpsManager.OP_START_FOREGROUND, r.appInfo.uid, r.packageName);
        mAm.mHandler.removeMessages(
                    ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG, r);
        if (r.app != null) {
            Message msg = mAm.mHandler.obtainMessage(
                ActivityManagerService.SERVICE_FOREGROUND_CRASH_MSG);
            msg.obj = r.app;
            msg.getData().putCharSequence(
                ActivityManagerService.SERVICE_RECORD_KEY, r.toString());
            mAm.mHandler.sendMessage(msg);
         }
    }
    ...
}

Androidはメインスレッドハンドラでサービスの作成をスケジュールしますが、MyForegroundServicename__はbringDownServiceLockedname__で呼び出されるため、このメソッドはBinderThreadname__のonCreate()の前に実行されます。それはMyForegroundServicename__がstartForegroundname__を呼び出す機会がなく、それがクラッシュの原因となることを意味します。

これを修正するには、bringDownServiceLockedname__がMyForegroundServicename__のonCreate()の前に呼び出されないようにする必要があります。

public class MyForegroundService extends Service {

    private static final String ACTION_STOP = "com.example.MyForegroundService.ACTION_STOP";

    private final BroadcastReceiver stopReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            context.removeStickyBroadcast(intent);
            stopForeground(true);
            stopSelf();
        }
    };

    @Override
    public void onCreate() {
        super.onCreate();
        startForeground(...);
        registerReceiver(
            stopReceiver, new IntentFilter(ACTION_STOP));
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        unregisterReceiver(stopReceiver);
    }

    public static void stop(Context context) {
        context.sendStickyBroadcast(new Intent(ACTION_STOP));
    }
}

スティッキーブロードキャストを使用することで、ブロードキャストが失われないようにし、stopReceivername__のonCreate()に登録されるとすぐにMyForegroundServicename__が停止の意図を受け取るようにします。この頃には、すでにstartForeground(...)と呼ばれていました。また、次回のstopReceiverへの通知を防ぐために、このスティッキーブロードキャストを削除する必要があります。

注意してくださいメソッドsendStickyBroadcastname__は推奨されていないので、この問題を解決するための一時的な回避策としてのみ使用します。

3
makovkastar

https://developer.Android.com/reference/Android/content/Context.html#startForegroundService(Android.content.Intent)

StartService(Intent)と似ていますが、実行が開始されると、サービスはstartForeground(int、Android.app.Notification)を呼び出すという暗黙の約束を持っています。これを行うには、サービスにANR間隔に相当する時間が与えられます。それ以外の場合、システムは自動的にサービスを停止し、アプリANRを宣言します。

通常のstartService(Intent)とは異なり、このメソッドは、サービスをホストしているアプリがフォアグラウンド状態にあるかどうかにかかわらず、いつでも使用できます。

onCreate()で必ずService.startForeground(int, Android.app.Notification)を呼び出すようにして、確実に呼び出されるようにします。それを妨げる可能性がある条件がある場合は、通常のContext.startService(Intent)を使用してService.startForeground(int, Android.app.Notification)を自分で呼び出すほうがよいでしょう。

Context.startForegroundService()は、破壊される前にService.startForeground(int, Android.app.Notification)を呼び出したことを確認するためのウォッチドッグを追加しているようです。

3

私は同じ問題に直面していて、時間をかけた後にあなたがコードの下で試すことができる解決策を見つけました。あなたがServiceを使っているなら、このコードをonCreateに、そうでなければあなたはIntent Serviceを使って、このコードをonHandleIntentに入れてください。

if (Build.VERSION.SDK_INT >= 26) {
        String CHANNEL_ID = "my_app";
        NotificationChannel channel = new NotificationChannel(CHANNEL_ID,
                "MyApp", NotificationManager.IMPORTANCE_DEFAULT);
        ((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).createNotificationChannel(channel);
        Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
                .setContentTitle("")
                .setContentText("").build();
        startForeground(1, notification);
    }
3
Sambhaji Karad

@humazed answerにコードを追加しています。したがって、最初の通知はありません。回避策かもしれませんが、私にとってはうまくいきます。

@Override
 public void onCreate() {
        super.onCreate();

        if (Build.VERSION.SDK_INT >= 26) {
            String CHANNEL_ID = "my_channel_01";
            NotificationChannel channel = new NotificationChannel(CHANNEL_ID,
                    "Channel human readable title",
                    NotificationManager.IMPORTANCE_DEFAULT);

            ((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).createNotificationChannel(channel);

            Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
                    .setContentTitle("")
                    .setContentText("")
                    .setColor(ContextCompat.getColor(this, R.color.transparentColor))
                    .setSmallIcon(ContextCompat.getColor(this, R.color.transparentColor)).build();

            startForeground(1, notification);
        }
}

通知時に小さなアイコンと色でtransparentColorを追加しています。それが動作します。

2
KHEM RAJ MEENA

onCreate() メソッド内ではStartForgroundServicesを呼び出さないでください。ワーカースレッドを作成した後は onStartCommand() でStartForgroundサービスを呼び出す必要があります。 onStartCommand() ;のメインスレッドに複雑なログインを記述しないでください。

public class Services extends Service {

    private static final String Android_CHANNEL_ID = "com.xxxx.Location.Channel";
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }


    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            Notification.Builder builder = new Notification.Builder(this, Android_CHANNEL_ID)
                    .setContentTitle(getString(R.string.app_name))
                    .setContentText("SmartTracker Running")
                    .setAutoCancel(true);
            Notification notification = builder.build();
            startForeground(0, notification);
            Log.e("home_button","home button");
        } else {
            NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
                    .setContentTitle(getString(R.string.app_name))
                    .setContentText("SmartTracker is Running...")
                    .setPriority(NotificationCompat.PRIORITY_DEFAULT)
                    .setAutoCancel(true);
            Notification notification = builder.build();
            startForeground(0, notification);
            Log.e("home_button_value","home_button_value");

        }
        return super.onStartCommand(intent, flags, startId);

    }
}

編集: 注意! startForeground関数は最初の引数として0を取ることはできません、それは例外が発生します! この例には誤った関数呼び出しが含まれています。0を0にすることもMax(Int32)より大きくすることもできない独自のconstに変更してください。

2
Shalu Gupta

startForegroundServiceを呼び出した後でも、stopServiceが呼び出される直前にonCreateを呼び出すと、一部のデバイスでクラッシュします。だから、私は追加のフラグでサービスを開始することでこの問題を修正しました:

Intent intent = new Intent(context,
                YourService.class);
intent.putExtra("request_stop", true);
context.startService(intent);

onStartCommandにチェックを追加して、実際に停止し始めたかどうかを確認します。

@Override
public int onStartCommand(Intent intent,
    int flags, int startId) {

    //call startForeground first
    boolean stopService = false;
    if (intent != null) {
        stopService = intent.getBooleanExtra("request_stop", false);
    }
    if (stopService) {
        stopSelf();
        return START_STICKY;
    }
    //Continue with the background task
    return START_STICKY;
}

P.Sサービスが実際に実行されていない場合は、最初にサービスが開始されますが、これはオーバーヘッドです。

1
Gaurav Singla

context.startForegroundService(service_intent)関数を呼び出す前に、PendingIntentがnullかどうかをチェックするだけです。

これは私のために働く

PendingIntent pendingIntent=PendingIntent.getBroadcast(context,0,intent,PendingIntent.FLAG_NO_CREATE);

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && pendingIntent==null){
            context.startForegroundService(service_intent);
        }
        else
        {
            context.startService(service_intent);
        }
}
1
SaimumIslam27

serviceまたはIntentServiceが作成された直後にstartForegroundメソッドを呼び出すだけです。このような:

import Android.app.Notification;
public class AuthenticationService extends Service {

    @Override
    public void onCreate() {
        super.onCreate();
        startForeground(1,new Notification());
    }
}
1
Hossein Karami

私のために私はマニフェストでフォアグラウンドサービスを追加しました

 <uses-permission Android:name="Android.permission.FOREGROUND_SERVICE"/>

それがうまくいったら下のコメント私に知らせて

0
Arun Abimanyu

わかりました、私がこれに気づいた何かはまた他の少数の他を助けるかもしれない。これは厳密に、私が見ている発生を修正する方法を見つけ出すことができるかどうかをテストすることからのものです。簡単にするために、プレゼンターからこれを呼び出すメソッドがあるとしましょう。

context.startForegroundService(new Intent(context, TaskQueueExecutorService.class));

try {
    Thread.sleep(10000);
} catch (InterruptedException e) {
    e.printStackTrace();
}       

これは同じエラーでクラッシュします。メソッドが完了するまでサービスは開始されないため、サービスにonCreate()はありません。

したがって、メインスレッドからUIを更新しても、その後にそのメソッドを保持する可能性のあるものがある場合、それは時間通りに開始されず、恐ろしいフォアグラウンドエラーが表示されます。私の場合、キューにいくつかのものをロードし、それぞれがstartForegroundServiceと呼ばれていましたが、バックグラウンドでそれぞれにいくつかのロジックが関係していました。したがって、ロジックが連続して呼び出されてからそのメソッドを完了するのに時間がかかりすぎると、クラッシュします。古いstartServiceはそれを無視してそのまま進み、毎回呼び出したので、次のラウンドは終了しました。

これにより、バックグラウンドでスレッドからサービスを呼び出すと、開始時に完全にバインドされずにすぐに実行できないので、実験を始めました。これはすぐには起動しませんが、クラッシュすることはありません。

new Handler(Looper.getMainLooper()).post(new Runnable() {
        public void run() {
               context.startForegroundService(new Intent(context, 
           TaskQueueExecutorService.class));
               try {
                   Thread.sleep(10000);
               } catch (InterruptedException e) {
                  e.printStackTrace();
              }       
        }
});

メインスレッドがタイムリーに処理できるようになるまで強制的に待機するのではないかと思われますが、なぜクラッシュしないのかを知るつもりはありません。メインスレッドに結び付けることは理想的ではないことはわかっていますが、私の使用法ではバックグラウンドで呼び出すため、クラッシュするのではなく完了するまで待機するかどうかはあまり気にしません。

0
a54studio

startService(intent)の代わりにContext.startForeground()でサービスを開始し、startForegound()の直後にsuper.OnCreate()を呼び出すことで問題を解決しました。また、起動時にサービスを開始すると、起動ブロードキャストでサービスを開始するアクティビティを開始できます。これは恒久的な解決策ではありませんが、うまくいきます。

0
mystogan

私はそれが遅い答えであることを知っています、しかし、私はそれが将来役立つかもしれないと思います、私はちょうどそのJobSchedulerを含み、私のためにすべてを扱うJobIntentServiceの代わりにIntentServiceを使いました。これを見てください example

0
raed

私は知っています、あまりにも多くの答えがすでに公開されています、しかし真実は - startForegroundServiceはアプリレベルで固定することができず、あなたはそれを使うのを止めるべきです。 Context#startForegroundService()が呼び出されてから5秒以内にService#startForeground()APIを使用することをGoogleが推奨することは、アプリが常にできることではありません。

Androidは多くのプロセスを同時に実行しており、5秒以内にstartForeground()を呼び出すことになっているターゲットサービスをLooperが呼び出すという保証はありません。あなたのターゲットサービスが5秒以内に電話を受けなかった場合、あなたは運が悪く、あなたのユーザーはANRの状況に遭遇するでしょう。スタックトレースでは、次のように表示されます。

Context.startForegroundService() did not then call Service.startForeground(): ServiceRecord{1946947 u0 ...MessageService}

main" prio=5 tid=1 Native
  | group="main" sCount=1 dsCount=0 flags=1 obj=0x763e01d8 self=0x7d77814c00
  | sysTid=11171 Nice=-10 cgrp=default sched=0/0 handle=0x7dfe411560
  | state=S schedstat=( 1337466614 103021380 2047 ) utm=106 stm=27 core=0 HZ=100
  | stack=0x7fd522f000-0x7fd5231000 stackSize=8MB
  | held mutexes=
  #00  pc 00000000000712e0  /system/lib64/libc.so (__epoll_pwait+8)
  #01  pc 00000000000141c0  /system/lib64/libutils.so (Android::Looper::pollInner(int)+144)
  #02  pc 000000000001408c  /system/lib64/libutils.so (Android::Looper::pollOnce(int, int*, int*, void**)+60)
  #03  pc 000000000012c0d4  /system/lib64/libandroid_runtime.so (Android::Android_os_MessageQueue_nativePollOnce(_JNIEnv*, _jobject*, long, int)+44)
  at Android.os.MessageQueue.nativePollOnce (MessageQueue.Java)
  at Android.os.MessageQueue.next (MessageQueue.Java:326)
  at Android.os.Looper.loop (Looper.Java:181)
  at Android.app.ActivityThread.main (ActivityThread.Java:6981)
  at Java.lang.reflect.Method.invoke (Method.Java)
  at com.Android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.Java:493)
  at com.Android.internal.os.ZygoteInit.main (ZygoteInit.Java:1445)

私が理解しているように、ルーパーはここで待ち行列を分析し、「虐待者」を見つけ、そして単にそれを殺しました。開発者やユーザーはそうではありませんが、システムは現在幸せで健康的ですが、Googleはシステムに対する責任を制限しているため、なぜ後者の2つを気にする必要があるのでしょうか。どうやら彼らは違います。彼らはそれを良くすることができますか?もちろん、 「アプリケーションがビジーです」というダイアログを表示して、ユーザーにアプリの待機または強制終了を決定するよう依頼することもできましたが、なぜ煩わしいのは、彼らの責任ではありません。主なことは、システムが現在健全であるということです。

私の観察によると、これは比較的まれにしか発生しません。私の場合、1Kユーザーにとって月に1回クラッシュします。それを再現することは不可能です、そしてそれが再現されたとしても、それを恒久的に修正するためにあなたができることは何もありません。

このスレッドでは、「start」の代わりに「bind」を使用し、次にサービスの準備ができたらonServiceConnectedを処理することをお勧めしますが、これもstartForegroundService呼び出しをまったく使用しないことを意味します。

私は、startForegourndServcieには欠陥があり、使用すべきではないと全員に言うことが、グーグル側からの正当で誠実な行動であると思います。

Qsはまだ残っています:代わりに何を使うべきですか?幸いなことに、現在JobSchedulerとJobServiceがあります。これらは、フォアグラウンドサービスに代わる優れた選択肢です。そのため、これはより良い選択肢です。

ジョブが実行されている間、システムはアプリケーションに代わってwakelockを保持します。このため、デバイスがジョブの実行中に起きていることを保証するための措置を講じる必要はありません。

それはあなたがもうwakelockを扱うことを気にする必要がないことを意味し、それがフォアグラウンドサービスと変わらない理由です。実装からPoV JobSchedulerはあなたのサービスではない、それはシステムのものであり、そしておそらくそれはキューの権利を処理し、そしてグーグルは決して自分自身の子を終了させないでしょう:)

Samsungは、Samsungアクセサリプロトコル(SAP)でstartForegroundServiceからJobSchedulerおよびJobServiceに切り替えました。スマートウォッチのようなデバイスが電話のようなホストと通信する必要があるときに非常に役立ちます。そこでは仕事はアプリケーションのメインスレッドを通してユーザーと対話する必要があります。ジョブはスケジューラによってメインスレッドにポストされるので可能になります。ただし、ジョブはメインスレッドで実行されており、重いものはすべてスレッドおよび非同期タスクにオフロードされます。

このサービスは、アプリケーションのメインスレッドで実行されているHandlerで各受信ジョブを実行します。これは、あなたが実行ロジックをあなたが選んだ別のスレッド/ハンドラ/ AsyncTaskにオフロードしなければならないことを意味します。

JobScheduler/JobServiceに切り替えることの唯一の落とし穴は、古いコードをリファクタリングする必要があるということです、そしてそれは面白くありません。私はここ2日間、サムソンの新しいSAPの実装を使うためだけにそれをやってきました。私は私のクラッシュレポートを見て、またクラッシュが見られるかどうかを知らせます。理論的にはそれは起こらないはずですが、私たちが気付いていないかもしれない詳細が常にあります。

0
Oleg Gryb

ここを訪れる人は皆同じことをしているので、他の誰も試したことのない解決策を共有したいと思います(とにかくこの質問で)。 停止したブレークポイントでもが機能していることを確認できます。

問題は、サービス自体からService.startForeground(id, notification)を呼び出すことですよね? Androidフレームワークは、残念ながら5秒以内にService.startForeground(id, notification)内でService.onCreate()を呼び出すことを保証しませんが、とにかく例外をスローするので、この方法を考え出しました。

  1. Context.startForegroundService()を呼び出す前に、サービスからのバインダーを使用してコンテキストにサービスをバインドします
  2. バインドが成功した場合、Context.startForegroundService()サービス接続からを呼び出し、すぐにService.startForeground()サービス接続内を呼び出します
  3. 重要な注意:内部でContext.bindService()メソッドを呼び出しますtry-catch呼び出しが例外をスローする場合があるため、その場合は呼び出しに依存する必要がありますContext.startForegroundService()直接、失敗しないことを願っています。例としてブロードキャストレシーバコンテキストを使用できますが、その場合、アプリケーションコンテキストを取得しても例外はスローされませんが、コンテキストを直接使用すると例外がスローされます。

これは、サービスをバインドした後、「startForeground」呼び出しをトリガーする前にブレークポイントで待機している場合でも機能します。 3〜4秒待機しても例外はトリガーされませんが、5秒後に例外がスローされます。 (デバイスが5秒以内に2行のコードを実行できない場合、それをゴミ箱に捨てます。)

そのため、サービス接続の作成から始めます。

// Create the service connection.
ServiceConnection connection = new ServiceConnection()
{
    @Override
    public void onServiceConnected(ComponentName name, IBinder service)
    {
        // The binder of the service that returns the instance that is created.
        MyService.LocalBinder binder = (MyService.LocalBinder) service;

        // The getter method to acquire the service.
        MyService myService = binder.getService();

        // getServiceIntent(context) returns the relative service intent 
        context.startForegroundService(getServiceIntent(context));

        // This is the key: Without waiting Android Framework to call this method
        // inside Service.onCreate(), immediately call here to post the notification.
        myService.startForeground(myNotificationId, MyService.getNotification());

        // Release the connection to prevent leaks.
        context.unbindService(this);
    }

    @Override
    public void onBindingDied(ComponentName name)
    {
        Log.w(TAG, "Binding has dead.");
    }

    @Override
    public void onNullBinding(ComponentName name)
    {
        Log.w(TAG, "Bind was null.");
    }

    @Override
    public void onServiceDisconnected(ComponentName name)
    {
        Log.w(TAG, "Service is disconnected..");
    }
};

サービス内で、サービスのインスタンスを返すバインダーを作成します。

public class MyService extends Service
{
    public class LocalBinder extends Binder
    {
        public MyService getService()
        {
            return MyService.this;
        }
    }

    // Create the instance on the service.
    private final LocalBinder binder = new LocalBinder();

    // Return this instance from onBind method.
    // You may also return new LocalBinder() which is
    // basically the same thing.
    @Nullable
    @Override
    public IBinder onBind(Intent intent)
    {
        return binder;
    }
}

次に、サービスのバインドを試みますそのコンテキストから。成功すると、使用しているサービス接続からServiceConnection.onServiceConnected()メソッドを呼び出します。次に、上記のコードのロジックを処理します。サンプルコードは次のようになります。

// Try to bind the service
try
{
     context.bindService(getServiceIntent(context), connection,
                    Context.BIND_AUTO_CREATE);
}
catch (RuntimeException ignored)
{
     // This is probably a broadcast receiver context even though we are calling getApplicationContext().
     // Just call startForegroundService instead since we cannot bind a service to a
     // broadcast receiver context. The service also have to call startForeground in
     // this case.
     context.startForegroundService(getServiceIntent(context));
}

私が開発したアプリケーションで動作しているようですので、試してみても動作するはずです。

0
Furkan Yurdakul

onStartCommand(...)内のデータを更新する

onBind(...)

onBind(...)startForegroundを初期化するのに必要なIntentに重要なデータを含む可能性があるBundleを渡すので、onCreate(...)ServiceonBind(...)を開始するためのより良いライフサイクルイベントです。ただし、Serviceが最初に作成されたとき、またはその後に呼び出されるときにonStartCommand(...)が呼び出されるので、これは必要ありません。

onStartCommand(...)

onStartCommand(...)startForegroundは、Serviceが作成された後にそれを更新するために重要です。

Serviceが作成された後にContextCompat.startForegroundService(...)が呼び出されるときonBind(...)onCreate(...)は呼び出されません。したがって、更新されたデータをIntentBundleを介してonStartCommand(...)に渡し、Service内のデータを更新することができます。

サンプル

Coinverse / cryptocurrencyニュースアプリでPlayerNotificationManagerを実装するためにこのパターンを使用しています。

Activity/Fragment.kt

context?.bindService(
        Intent(context, AudioService::class.Java),
        serviceConnection, Context.BIND_AUTO_CREATE)
ContextCompat.startForegroundService(
        context!!,
        Intent(context, AudioService::class.Java).apply {
            action = CONTENT_SELECTED_ACTION
            putExtra(CONTENT_SELECTED_KEY, contentToPlay.content.apply {
                audioUrl = uri.toString()
            })
        })

AudioService.kt

private var uri: Uri = Uri.parse("")

override fun onBind(intent: Intent?) =
        AudioServiceBinder().apply {
            player = ExoPlayerFactory.newSimpleInstance(
                    applicationContext,
                    AudioOnlyRenderersFactory(applicationContext),
                    DefaultTrackSelector())
        }

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
    intent?.let {
        when (intent.action) {
            CONTENT_SELECTED_ACTION -> it.getParcelableExtra<Content>(CONTENT_SELECTED_KEY).also { content ->
                val intentUri = Uri.parse(content.audioUrl)
                // Checks whether to update Uri passed in Intent Bundle.
                if (!intentUri.equals(uri)) {
                    uri = intentUri
                    player?.prepare(ProgressiveMediaSource.Factory(
                            DefaultDataSourceFactory(
                                    this,
                                    Util.getUserAgent(this, getString(app_name))))
                            .createMediaSource(uri))
                    player?.playWhenReady = true
                    // Calling 'startForeground' in 'buildNotification(...)'.          
                    buildNotification(intent.getParcelableExtra(CONTENT_SELECTED_KEY))
                }
            }
        }
    }
    return super.onStartCommand(intent, flags, startId)
}

// Calling 'startForeground' in 'onNotificationStarted(...)'.
private fun buildNotification(content: Content): Unit? {
    playerNotificationManager = PlayerNotificationManager.createWithNotificationChannel(
            this,
            content.title,
            app_name,
            if (!content.audioUrl.isNullOrEmpty()) 1 else -1,
            object : PlayerNotificationManager.MediaDescriptionAdapter {
                override fun createCurrentContentIntent(player: Player?) = ...
                override fun getCurrentContentText(player: Player?) = ...
                override fun getCurrentContentTitle(player: Player?) = ...
                override fun getCurrentLargeIcon(player: Player?,
                                                 callback: PlayerNotificationManager.BitmapCallback?) = ...
            },
            object : PlayerNotificationManager.NotificationListener {
                override fun onNotificationStarted(notificationId: Int, notification: Notification) {
                    startForeground(notificationId, notification)
                }
                override fun onNotificationCancelled(notificationId: Int) {
                    stopForeground(true)
                    stopSelf()
                }
            })
    return playerNotificationManager.setPlayer(player)
}
0
Adam Hurwitz