Как программно «перезапустить» приложение Android? - PullRequest
189 голосов
/ 07 июля 2011

Во-первых, я знаю, что на самом деле нельзя убивать / перезапускать приложение на Android. В моем случае использования я хочу сбросить заводские настройки моего приложения в конкретном случае, когда сервер отправляет конкретную информацию клиенту.

Пользователь может войти на сервер только с ОДНОГО экземпляра приложения (т.е. несколько устройств не допускаются). Если другой экземпляр получает этот «вход в систему», тогда все остальные экземпляры этого пользователя должны удалить свои данные (сброс к заводским настройкам), чтобы сохранить согласованность.

Возможно принудительно получить блокировку, потому что пользователь может удалить приложение и переустановить его, что приведет к другому идентификатору экземпляра, и пользователь больше не сможет снять блокировку. Поэтому можно принудительно получить блокировку.

Из-за этой принудительной возможности нам нужно всегда проверять в конкретном случае, что он имеет блокировку. Это делается для (почти) каждого запроса к серверу. Сервер может отправить «неправильный идентификатор блокировки». Если это обнаружено, клиентское приложение должно удалить все.


Это был вариант использования.

У меня есть Activity A, который запускает Login Activity L или основной Activity B приложения в зависимости от значения sharedPrefs. После запуска L или B он закрывается, так что работает только L или B. Таким образом, в случае, если пользователь уже вошел в систему, B работает сейчас.

B запускает C. C вызывает startService для IntentService D. В результате получается этот стек:

(A)> B> C> D

Из метода onHandleIntent в D событие отправляется ResultReceiver R.

R теперь обрабатывает это событие, предоставляя пользователю диалоговое окно, в котором он может выбрать заводскую настройку приложения (удалить базу данных, sharedPrefs и т. Д.)

После сброса к заводским настройкам я хочу перезапустить приложение (чтобы закрыть все действия) и снова запустить только A, который затем запускает логин Activity L и завершает себя:

(A)> L

OnClick-метод Диалога выглядит следующим образом:

@Override
public void onClick(DialogInterface dialog, int which) {

    // Will call onCancelListener
    MyApplication.factoryReset(); // (Deletes the database, clears sharedPrefs, etc.)
    Intent i = new Intent(MyApp.getContext(), A.class);
    i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
    i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    MyApp.getContext().startActivity(i);
}

И это MyApp класс:

public class MyApp extends Application {
    private static Context context;

    @Override
    public void onCreate() {
        super.onCreate();
        context = getApplicationContext();
    }

    public static Context getContext() {
        return context;
    }

    public static void factoryReset() {
        // ...
    }
}

Проблема в том, что если я использую FLAG_ACTIVITY_NEW_TASK, операции B и C все еще выполняются. Если я нажму кнопку «Назад» при входе в систему Activity, я вижу C, но я хочу вернуться на главный экран.

Если я не установил FLAG_ACTIVITY_NEW_TASK, я получаю ошибку:

07-07 12:27:12.272: ERROR/AndroidRuntime(9512): android.util.AndroidRuntimeException: Calling startActivity() from outside of an Activity  context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?

Я не могу использовать Действия 'Context, потому что ServiceIntent D также может вызываться из фоновой задачи, которая запускается AlarmManager.

Так, как я мог решить это, чтобы стек активности стал (A)> L?

Ответы [ 21 ]

262 голосов
/ 18 июня 2013

Вы можете использовать PendingIntent, чтобы настроить запуск вашей стартовой деятельности в будущем, а затем закрыть приложение

Intent mStartActivity = new Intent(context, StartActivity.class);
int mPendingIntentId = 123456;
PendingIntent mPendingIntent = PendingIntent.getActivity(context, mPendingIntentId,    mStartActivity, PendingIntent.FLAG_CANCEL_CURRENT);
AlarmManager mgr = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 100, mPendingIntent);
System.exit(0);
85 голосов
/ 12 марта 2014

Вы можете просто позвонить:

public static void triggerRebirth(Context context, Intent nextIntent) {
    Intent intent = new Intent(context, YourClass.class);
    intent.addFlags(FLAG_ACTIVITY_NEW_TASK);
    intent.putExtra(KEY_RESTART_INTENT, nextIntent);
    context.startActivity(intent);
    if (context instanceof Activity) {
      ((Activity) context).finish();
    }

    Runtime.getRuntime().exit(0);
}

Используется в библиотеке ProcessPhoenix


В качестве альтернативы:

Вот немного улучшенная версия ответа Олега Кошкина.

Если вы действительно хотите возобновить свою деятельность, включая уничтожение текущего процесса, попробуйте следующий код. Поместите его в HelperClass или там, где вам нужно.

public static void doRestart(Context c) {
        try {
            //check if the context is given
            if (c != null) {
                //fetch the packagemanager so we can get the default launch activity 
                // (you can replace this intent with any other activity if you want
                PackageManager pm = c.getPackageManager();
                //check if we got the PackageManager
                if (pm != null) {
                    //create the intent with the default start activity for your application
                    Intent mStartActivity = pm.getLaunchIntentForPackage(
                            c.getPackageName()
                    );
                    if (mStartActivity != null) {
                        mStartActivity.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
                        //create a pending intent so the application is restarted after System.exit(0) was called. 
                        // We use an AlarmManager to call this intent in 100ms
                        int mPendingIntentId = 223344;
                        PendingIntent mPendingIntent = PendingIntent
                                .getActivity(c, mPendingIntentId, mStartActivity,
                                        PendingIntent.FLAG_CANCEL_CURRENT);
                        AlarmManager mgr = (AlarmManager) c.getSystemService(Context.ALARM_SERVICE);
                        mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 100, mPendingIntent);
                        //kill the application
                        System.exit(0);
                    } else {
                        Log.e(TAG, "Was not able to restart application, mStartActivity null");
                    }
                } else {
                    Log.e(TAG, "Was not able to restart application, PM null");
                }
            } else {
                Log.e(TAG, "Was not able to restart application, Context null");
            }
        } catch (Exception ex) {
            Log.e(TAG, "Was not able to restart application");
        }
    }

Это также приведет к повторной инициализации классов jni и всех статических экземпляров.

61 голосов
/ 04 сентября 2015

Джейк Уортон недавно опубликовал свою библиотеку ProcessPhoenix , которая делает это надежным способом. Вам в основном нужно только позвонить:

ProcessPhoenix.triggerRebirth(context);

Библиотека автоматически завершит вызов, завершит процесс приложения и перезапустит действие приложения по умолчанию.

35 голосов
/ 14 марта 2016

IntentCompat.makeRestartActivityTask

Новый способ сделать это, используя IntentCompat.makeRestartActivityTask

Создать намерение, которое можно использовать для повторногозапустить задачу приложения в ее базовом состоянии.Это похоже на makeMainActivity (ComponentName), но также устанавливает флаги Intent.FLAG_ACTIVITY_NEW_TASK и FLAG_ACTIVITY_CLEAR_TASK.

PackageManager packageManager = context.getPackageManager();
Intent intent = packageManager.getLaunchIntentForPackage(context.getPackageName());
ComponentName componentName = intent.getComponent();
Intent mainIntent = IntentCompat.makeRestartActivityTask(componentName);
context.startActivity(mainIntent);
System.exit(0);
30 голосов
/ 20 октября 2017

Я немного изменил ответ Ilya_Gazman для использования новых API (IntentCompat устарел, начиная с API 26). Runtime.getRuntime (). Exit (0) выглядит лучше, чем System.exit (0).

 public static void triggerRebirth(Context context) {
    PackageManager packageManager = context.getPackageManager();
    Intent intent = packageManager.getLaunchIntentForPackage(context.getPackageName());
    ComponentName componentName = intent.getComponent();
    Intent mainIntent = Intent.makeRestartActivityTask(componentName);
    context.startActivity(mainIntent);
    Runtime.getRuntime().exit(0);
}
27 голосов
/ 01 ноября 2012

Есть действительно хороший трюк.Моя проблема заключалась в том, что в какой-то действительно старой C ++ jni библиотеке произошла утечка ресурсов.В какой-то момент он перестал функционировать.Пользователь попытался выйти из приложения и запустить его снова - безрезультатно, потому что завершение действия - это не то же самое, что завершение (или уничтожение) процесса.(Кстати, пользователь может перейти к списку запущенных приложений и остановить его оттуда - это будет работать, но пользователи просто не знают, как завершать приложения.)

Если вы хотитенаблюдайте за эффектом этой функции, добавьте переменную static к вашей активности и увеличивайте ее, скажем, нажатием кнопки.Если вы выйдете из активности приложения, а затем снова вызовете приложение, эта статическая переменная сохранит свое значение.(Если приложение действительно было закрыто, переменной будет присвоено начальное значение.)

(И я должен прокомментировать, почему я не хотел исправить ошибку вместо этого. БиблиотекаОн был написан десятилетия назад и с тех пор пропускал ресурсы. Руководство считает, что это всегда работает . стоит предоставления исправления вместо обходного пути ... Я думаю, вы поняли идею.)

Теперь, как я могу сбросить совместно используемую библиотеку jni (она же динамическая, .so) в исходное состояние?Я решил перезапустить приложение как новый процесс.

Хитрость в том, что System.exit () закрывает текущее действие, а Android заново создает приложение с одним действием меньше.

Итак, код:

/** This activity shows nothing; instead, it restarts the android process */
public class MagicAppRestart extends Activity {
    // Do not forget to add it to AndroidManifest.xml
    // <activity android:name="your.package.name.MagicAppRestart"/>
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        System.exit(0);
    }
    public static void doRestart(Activity anyActivity) {
        anyActivity.startActivity(new Intent(anyActivity.getApplicationContext(), MagicAppRestart.class));
    }
}

Вызывающее действие просто выполняет код MagicAppRestart.doRestart(this);, выполняется вызывающее действие onPause(), а затем процесс создается заново.И не забудьте упомянуть об этой активности в AndroidManifest.xml

Преимущество этого метода в том, что нет задержек.

UPD: он работал в Android 2.x, но в Android 4что-то изменилось.

16 голосов
/ 13 ноября 2015

Мое решение не перезапускает процесс / приложение.Это только позволяет приложению «перезапустить» домашнюю активность (и отклонить все остальные действия).Это выглядит как перезагрузка для пользователей, но процесс такой же.Я думаю, что в некоторых случаях люди хотят добиться этого эффекта, поэтому я просто оставляю его здесь, к вашему сведению.

public void restart(){
    Intent intent = new Intent(this, YourHomeActivity.class);
    this.startActivity(intent);
    this.finishAffinity();
}
15 голосов
/ 07 июля 2011

Хорошо, я провела рефакторинг своего приложения, и я не закончу A автоматически.Я позволяю этому запускаться всегда и заканчиваю через событие onActivityResult.Таким образом, я могу использовать флаги FLAG_ACTIVITY_CLEAR_TOP + FLAG_ACTIVITY_NEW_TASK, чтобы получить то, что я хочу:

public class A extends Activity {

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        finish();
    }

    protected void onResume() {
        super.onResume();
        // ...
        if (loggedIn) {
            startActivityForResult(new Intent(this, MainActivity.class), 0);
        } else {
            startActivityForResult(new Intent(this, LoginActivity.class), 0);
        }
    }
}

и в ResultReceiver

@Override
public void onClick(DialogInterface dialog, int which) {
    MyApp.factoryReset();
    Intent i = new Intent(MyApp.getContext(), A.class);
    i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
    i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    MyApp.getContext().startActivity(i);
}

В любом случае, спасибо!

13 голосов
/ 30 ноября 2012
Intent i = getBaseContext().getPackageManager().getLaunchIntentForPackage( getBaseContext().getPackageName() );
i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(i);
7 голосов
/ 09 октября 2015

Лучший способ полностью перезапустить приложение - это перезапустить его, а не просто перейти к действию с FLAG_ACTIVITY_CLEAR_TOP и FLAG_ACTIVITY_NEW_TASK. Поэтому мое решение состоит в том, чтобы сделать это из вашего приложения или даже из другого приложения, единственное условие - знать имя пакета приложения (пример: ' com.example.myProject ')

 public static void forceRunApp(Context context, String packageApp){
    Intent launchIntent = context.getPackageManager().getLaunchIntentForPackage(packageApp);
    launchIntent.setFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS | Intent.FLAG_ACTIVITY_NEW_TASK);
    context.startActivity(launchIntent);
}

Пример использования перезапуска или запуска appA из appB :

forceRunApp(mContext, "com.example.myProject.appA");

Вы можете проверить, работает ли приложение:

 public static boolean isAppRunning(Context context, String packageApp){
    ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
    List<ActivityManager.RunningAppProcessInfo> procInfos = activityManager.getRunningAppProcesses();
    for (int i = 0; i < procInfos.size(); i++) {
        if (procInfos.get(i).processName.equals(packageApp)) {
           return true;
        }
    }
    return false;
}

Примечание : я знаю, что этот ответ немного не в тему, но он может быть действительно полезным для кого-то.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...