概要
Android Q では、従来よく使われていた AlarmManager によるアプリ強制再起動が使えなくなりました。
この記事ではその代替手段である Activity によるアプリ強制再起動について説明します。
問題
従来、アプリを強制的に再起動するために次のような方法がありました。
- AlarmManager を使ってメインの Activity を数秒後に起動するように設定
- アプリのプロセスを Kill する。
具体的には次のようなコードです。
private void restartApp() {
Intent intent = new Intent(this, MainActivity.class);
PendingIntent pi = PendingIntent.getActivity(this, 0, intent,
PendingIntent.FLAG_ONE_SHOT);
AlarmManager am =
(AlarmManager) getApplicationContext().getSystemService(Context.ALARM_SERVICE);
// 1秒後に MainActivity を起動
am.setExact(AlarmManager.RTC, System.currentTimeMillis() + 1000, pi);
// 現在のプロセスを Kill する
android.os.Process.killProcess(android.os.Process.myPid());
}
この方法は Android Q では使えなくなりました。
この制限は Android Q で動くすべてのアプリに適用されます。targetSdkVersion は関係ありません。
これは『Android Q におけるバックグラウンドでの Activity 起動の制限』が原因です。
Android Issues にこの問題に関するチケットが起票されています。これに対し、Android チームは「Android Q では AlarmManager による再起動は利用できなくなった」とコメントしています。
解決方法
Android Q からは Activity によるアプリ強制再起動が利用できます。
概要は次の通りです。
- 再起動を実行する RestartActivity を定義する
- RestartActivity が別プロセスで起動するように宣言する
- メインプロセスから RestartActivity を起動させる
- RestartActivity で再起動処理をする
- RestartActivity が起動したらメインプロセスを Kill する
- メインプロセスを Kill したら、MainActivity を起動する
- RestartActivity を終了する
RestartActivity を定義する
RestartActivity を定義します。
RestartActivity を起動する Intent にメインプロセスの PID を保持しておきます。
package com.x.androidapprestart;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
public class RestartActivity extends Activity {
public static final String EXTRA_MAIN_PID = "RestartActivity.main_pid";
public static Intent createIntent(Context context) {
Intent intent = new Intent();
intent.setClassName(context.getPackageName(), RestartActivity.class.getName());
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
// メインプロセスの PID を Intent に保存しておく
intent.putExtra(RestartActivity.EXTRA_MAIN_PID, android.os.Process.myPid());
return intent;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// ... (省略) ...
}
}
別プロセスで起動するように宣言する
AndroidManifest.xml に RestartActivity の宣言を追加します。
この時、別プロセスで起動させるために android:process に任意のプロセス名を指定します。
<activity
android:name=".RestartActivity"
android:excludeFromRecents="true"
android:exported="false"
android:launchMode="singleInstance"
android:process=":restart_process"
android:theme="@android:style/Theme.Translucent.NoTitleBar">
</activity>
RestartActivity を起動する
終了したいタイミングで RestartActivity を起動します。
private void restartApp() {
Context context = getApplicationContext();
Intent intent = RestartActivity.createIntent(context);
// RestartActivity を起動(AndroidManifest.xml での宣言により別プロセスで起動する
context.startActivity(intent);
}
再起動処理をする
1. メインプロセスを Kill する
RestartActivity の onCreate メソッドでメインプロセスを Kill します。
この時、PID は Intent で渡された値を利用します。
2. MainActivity を起動する
メインプロセスを Kill したら、再起動したい Activity(今回のケースでは MainActivity)を起動します。
3. RestartActivity を終了する
RestartActivity とそのプロセスを終了します。
これでアプリが再起動されるはずです。
以下に RestartActivity の onCreate のコードを記載します。
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 1. メインプロセスを Kill する
Intent intent = getIntent();
int mainPid = intent.getIntExtra(EXTRA_MAIN_PID, -1);
android.os.Process.killProcess(mainPid);
// 2. MainActivity を再起動する
Context context = getApplicationContext();
Intent restartIntent = new Intent(Intent.ACTION_MAIN);
restartIntent.setClassName(context.getPackageName(), MainActivity.class.getName());
restartIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(restartIntent);
// 3. RestartActivity を終了する
finish();
android.os.Process.killProcess(android.os.Process.myPid());
}
すべてのコード
すべてのコードは github にありますので参考にしてください。
余談
この方法は Android 版 Chrome でも使われているようです。
Chrome にも BrowserRestartActivity というクラスがあり、ChromeLifetimeController から呼ばれています。