web-dev-qa-db-ja.com

Android 9(Pie)、Context.startForegroundService()がService.startForeground()を呼び出さなかった:ServiceRecord

まず、私はこれらを見ました。

enter image description here

私はほぼ100万人が使用するストリーミングアプリケーションを持っています。プレーヤーにフォアグラウンドサービスを使用しています。 MediaSessionはまだ実装していません。私は99.95%のクラッシュのないセッションを行っています。したがって、このアプリはすべてのバージョンで機能しますが、Android 9)でクラッシュレポート(ANR)を取得し始めました。このクラッシュはSamsungスマートフォン、特に_s9, s9+, s10, s10+, note9_モデルでのみ発生します。

私はこれらを試しました、

  • startForeground()onCreate()メソッドを呼び出す
  • Service.startForeground()の前にContext.stopService()を呼び出す
  • 同様の質問に対する他のスタックオーバーフローの回答

私はGoogleの開発者からいくつかのコメントを読みました、彼らはそれがちょうど_Intended Behavior_であると言いました。サムスンのシステムまたはAndroid OS。

16
Beyazid

クラッシュレポートが解決策を共有するのを待っていました。クラッシュやANRがほぼ20日間発生しませんでした。私のソリューションを共有したいと思います。この問題に遭遇した人を助けることができます。

onCreate()メソッド内

  • まず、私のアプリはメディアアプリケーションです。メディアセッションはまだ実装していません。 onCreate()の上部に通知チャネルを作成しています。 公式ドキュメント
  • Service.startForeground()メソッドの後にContext.startForegroundService()メソッドを呼び出しています。私のprepareAndStartForeground()メソッド。

    注:理由はわかりませんが、ContextCompat.startForegroundService()が正しく機能しません。

このため、ContextCompat.startForegroundService()を呼び出す代わりに、手動で同じ関数をサービスクラスに追加しました

_private fun startForegroundService(intent: Intent) {
    if (Build.VERSION.SDK_INT >= 26) {
        context.startForegroundService(intent)
    } else {
        // Pre-O behavior.
        context.startService(intent)
    }
}
_

prepareAndStartForeground()メソッド

_private fun prepareAndStartForeground() {
    try {
        val intent = Intent(ctx, MusicService::class.Java)
        startForegroundService(intent)

        val n = mNotificationBuilder.build()
        // do sth
        startForeground(Define.NOTIFICATION_ID, n)
    } catch (e: Exception) {
        Log.e(TAG, "startForegroundNotification: " + e.message)
    }
}
_

それは私のonCreate()です

_override fun onCreate() {
    super.onCreate()
    createNotificationChannel()
    prepareAndStartForeground()
}
_

私のonStartCommand()

_override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
    if (intent == null) {
        return START_STICKY_COMPATIBILITY
    } else {
        //....
        //...
    }
    return START_STICKY
}
_

onRebindonBindonUnbindこれらのようなメソッド

_internal var binder: IBinder? = null

override fun onRebind(intent: Intent) {
    stopForeground(true) // <- remove notification
}

override fun onBind(intent: Intent): IBinder? {
    stopForeground(true) // <- remove notification
    return binder
}

override fun onUnbind(intent: Intent): Boolean {
    prepareAndStartForeground() // <- show notification again
    return true
}
_

OnDestroy()を呼び出すときに何かをクリアする必要があります

_   override fun onDestroy() {
    super.onDestroy()
    releaseService()
   }
_

_private fun releaseService() {
    stopMedia()
    stopTimer()
    // sth like these
    player = null
    mContext = null
    afChangeListener = null
    mAudioBecomingNoisy = null
    handler = null
    mNotificationBuilder = null
    mNotificationManager = null
    mInstance = null
}
_

このソリューションが適切に機能することを願っています。

4
Beyazid

ついにこのクラッシュに苦しみすぎた後、この例外を完全に修正し、解決策を見つけました。

あなたのサービスでこれを行ったことを確認してください:以下にリストします(このものの一部は、別の回答で述べたように繰り返しが多いので、もう一度書きます)。

1-電話

startForeground()

onCreateonStartCommandの両方で(startForeground()を呼び出してもかまいません)何度も)

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

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    if (intent == null) {
        return START_NOT_STICKY;
    }
    final int command = intent.getIntExtra(MAIN_SERVICE_COMMAND_KEY, -1);

    if (command == MAIN_SERVICE_START_COMMAND) {
        startCommand();
        return START_STICKY;
    }
    return START_NOT_STICKY;
}

 private void startCommand() {
    createNotificationAndStartForeground();
    runningStatus.set(STARTED);
}

2-Stopサービスを使用して

context.stopService()

stopForeground()またはstopSelf()を呼び出す必要はありません。

  try {
        context.stopService(
                new Intent(
                        context,
                        NavigationService.class
                )
        );
    } catch (Exception ex) {
        Crashlytics.logException(ex);
        LogManager.e("Service manager can't stop service ", ex);
    }

3-Startサービスを使用して

ContextCompat.startForegroundService()

異なるAPIバージョンを処理します。

   ContextCompat.startForegroundService(
            context,
            NavigationService.getStartIntent(context)
    );

4-サービスにアクションがある場合(保留中のインテントが必要)は、保留中のインテントブロードキャストレシーバー現在のサービスではなく(Create()でサービスを呼び出し、危険な場合があります。またはPendingIntent.FLAG_NO_CREATEを使用します)、サービス通知アクションを処理するための特定のブロードキャストレシーバーを用意することをお勧めします。 、つまり、PendingIntent.getBroadcast()を使用して、保留中のすべてのインテントを作成します。

    private PendingIntent getStopActionPendingIntent() {
    final Intent stopNotificationIntent = getBroadCastIntent();

    stopNotificationIntent.setAction(BROADCAST_STOP_SERVICE);

    return getPendingIntent(stopNotificationIntent);
}

private PendingIntent getPendingIntent(final Intent intent) {
    return PendingIntent.getBroadcast(
            this,
            0,
            intent,
            0
    );
}

new NotificationCompat.Builder(this, CHANNEL_ID)
            .addAction(
                    new NotificationCompat.Action(
                            R.drawable.notification,
                            getString(R.string.switch_off),
                            getStopActionPendingIntent()
                    )
            )

5-常にサービスを停止する前にサービスが作成および開始されていることを確認します(サービスの状態を持つグローバルクラスを作成します)

  if (navigationServiceStatus == STARTED) {
            serviceManager.stopNavigationService();
        }

6-notificationIdを121412などの長い数値に設定します。

7-NotificationCompat.Builderを使用すると、ビルドバージョン> = Build.VERSION_CODES.O。(これは、コードを読みやすくするだけの解決策ではありません)

8-追加

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

マニフェストへのpermission。 (これはAndroid docs)で言及されています) Android Foreground Service

それが役に立てば幸い :))

5
Sepehr

Android Serviceコンポーネントは、特に以降のAndroidバージョンでOSが追加の制限を追加するバージョン)で正しく機能するために少しトリッキーです。前述のように他の回答では、Serviceを開始するときにContextCompat.startForegroundService()を使用します。次に、Service.onStartCommand()startForeground()をすぐに呼び出します。Notificationメンバーフィールドとして表示し、nullでない限りそれを使用したい場合例:

private var notification:Notification? = null
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
    if (notification == null) {
        notification = createDefaultNotification()
    }
    startForeground(NOTIFICATION_ID, notification)

    // Do any additional setup and work herre

    return START_STICKY
}

常にServiceSTART_STICKYを返します。それ以外のものはおそらく間違いです。特に、オーディオプレーヤーを使用している場合はなおさらです。実際、オーディオプレーヤーを使用している場合は、独自のサービスを実装するのではなく、代わりに MediaBrowserServiceCompat (AndroidXから)を使用してください。

私がこれに書いたブログ投稿もお勧めします: https://hellsoft.se/how-to-service-on-Android-part-3-1e24113152cd

1
Erik Hellman

OnPlay()、onPause()などのMediaSessionCompat.CallbackメソッドのstartForeground()の問題をほぼ解消しました。

0

同じ電話で同じ問題が発生した後、私はいくつかの変更を行い、クラッシュはなくなりました。何がうまくいったのかはわかりませんが、onCreateとonStartCommandの両方でstartForegroundを呼び出していると思います。サービスが既に開始されていて、onCreateですべてが正しく呼び出された場合に、なぜそれが必要なのかわかりません。

その他の変更:-serviceIdをいくつかの低い数値(1-10)に変更-シングルトン同期クラスを介してstartFororegroundServiceを呼び出す頻度を減らす(これは、onStartCommandからのコールバックで開始する前にサービスが停止するのを防ぐためにクラッシュで以前実装されていましたが、今ではまたサービスがすでに開始されている場合は、呼び出しをフィルタリングします)。 -START_REDELIVER_INTENTを使用(何も影響しないはず)

この問題は一部のユーザーのみが対象のスマートフォンで発生しているため、Samsungからの新しいアップデートに関連していると考えられ、最終的には修正される予定です

0
Michal Polivka

このエラーメッセージによると、Context.startForegroundService()を呼び出すときに、Service.startForeground()メソッドを使用して通知を発行する必要があるということです。これは私が理解していることです。

0
user1090751