При выходе из системы очистите стек истории действий, чтобы кнопка «Назад» не открывала вход в систему только для входа.

235

Все действия в моем приложении требуют, чтобы пользователь был авторизован для просмотра. Пользователи могут выйти практически из любой активности. Это требование приложения. В любой момент, если пользователь выходит из системы, я хочу отправить пользователя в логин Activity. На данный момент я хочу, чтобы это действие было в нижней части стека истории, чтобы при нажатии кнопки «назад» пользователь возвращался на домашний экран Android.

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

Я попытался открыть действие входа в систему, установив его Intentфлаги, FLAG_ACTIVITY_CLEAR_TOPкоторые, как кажется, описываются в документации, но не достигает моей цели - поместить действие входа в конец стека истории и не позволяет пользователю вернуться назад. к ранее увиденным вошедшим в систему действиям. Я также пытался использоватьandroid:launchMode="singleTop" для входа в активность в манифесте, но это также не достигает моей цели (и, похоже, в любом случае не имеет никакого эффекта).

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

Один из вариантов состоит в том, чтобы иметь onCreateстатус входа для каждой проверки активности , иfinish() если нет - для входа. Мне не нравится эта опция, так как кнопка «Назад» все еще будет доступна для использования, навигация назад, когда действия закрываются.

Следующим вариантом является сохранение LinkedListссылок на все открытые действия, которые статически доступны из любого места (возможно, с использованием слабых ссылок). При выходе я получу доступ к этому списку и переберу все ранее открытые действия, вызываяfinish() на каждое из них. Я, вероятно, скоро начну реализовывать этот метод.

Однако я бы предпочел использовать некоторую Intentхитрость для флага. Я был бы очень рад обнаружить, что могу выполнить требования своего приложения, не используя ни один из двух методов, которые я описал выше.

Есть ли способ сделать это с помощью Intentнастроек или манифеста, или мой второй вариант - поддерживать LinkedListоткрытые действия? Или есть другой вариант, который я полностью пропускаю?

Скайлер
источник

Ответы:

213

Я могу предложить вам другой подход, ИМХО более надежный. По сути, вам необходимо передать сообщение о выходе всем вашим действиям, которым необходимо оставаться в состоянии входа в систему. Таким образом, вы можете использовать sendBroadcastи установить BroadcastReceiverво всех ваших Actvities. Что-то вроде этого:

/** on your logout method:**/
Intent broadcastIntent = new Intent();
broadcastIntent.setAction("com.package.ACTION_LOGOUT");
sendBroadcast(broadcastIntent);

Получатель (обеспеченная активность):

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    /**snip **/
    IntentFilter intentFilter = new IntentFilter();
    intentFilter.addAction("com.package.ACTION_LOGOUT");
    registerReceiver(new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            Log.d("onReceive","Logout in progress");
            //At this point you should start the login activity and finish this one
            finish();
        }
    }, intentFilter);
    //** snip **//
}
Франческо Лаурита
источник
27
@Christopher, каждое действие регистрируется для трансляции, когда оно создается. Когда он переходит в фоновый режим (т. Е. Новое действие попадает на вершину стека), вызывается его onStop (), но он все еще может получать широковещательные сообщения. Вам просто нужно убедиться, что вы вызываете unregisterReceiver () в onDestroy (), а не в onStop ().
Рассел Дэвис
9
Работает ли это, если действие где-то в стеке было закрыто ОС для восстановления памяти? То есть. Будет ли система считать, что она действительно завершена после отправки вышеприведенной трансляции, и не будет воссоздавать ее при нажатии кнопки «Назад»?
Ян Янковский
5
Хотя это кажется элегантным решением, важно отметить, что это не синхронно.
Че Джами
34
Хорошее решение, но вместо регистрации получателя Broadcast, как описано в коде выше, вы должны использовать LocalBroadcastManager.getInstance (this) .registerReceiver (...) и LocalBroadcastManager.getInstance (this) .unregisterReceiver (..) , В противном случае ваше приложение может получить намерения от любого другого приложения (проблемы безопасности)
Uku Loskit
5
@Warlock Вы правы. Подводный камень в этом подходе - это когда активность в заднем стеке уничтожается системой (это может произойти по разным причинам, например, в сценарии с нехваткой памяти). В этом случае экземпляр Activity не будет доступен для получения широковещательной рассылки. Но система все равно будет перемещаться назад через это действие, воссоздавая его. Это можно проверить / воспроизвести, включив настройку разработчика «Не держать действия»
Эрик Шленц,
151

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

Во-первых, нет никакого очевидного или немедленного способа сделать это согласно моему исследованию. (as of September 2012).Вы могли бы подумать, что можете просто, startActivity(new Intent(this, LoginActivity.class), CLEAR_STACK)но нет .

Вы можете сделать startActivity(new Intent(this, LoginActivity.class))с FLAG_ACTIVITY_CLEAR_TOP- и это заставит рамки для поиска вниз по стеку, вы найдете ранее оригинальный экземпляр LoginActivity, воссоздать его и очистить остальную часть (вверх) стека. А поскольку логин находится, вероятно, в нижней части стека, у вас теперь есть пустой стек, а кнопка «Назад» просто выходит из приложения.

НО - это работает, только если вы ранее оставили этот оригинальный экземпляр LoginActivity в основе своего стека. Если, как и многие программисты, вы выбрали finish()это LoginActivityпосле того, как пользователь успешно вошел в систему, то это больше не основано на стеке, и FLAG_ACTIVITY_CLEAR_TOPсемантика не применяется ... в итоге вы создаете новый LoginActivityповерх существующего стека. Это почти наверняка НЕ ​​то, что вы хотите (странное поведение, когда пользователь может «вернуться» из входа в систему на предыдущем экране).

Так что, если вы ранее finish()делали это LoginActivity, вам нужно использовать какой-то механизм для очистки стека и запуска нового LoginActivity. Похоже, что ответ @doreamonв этой теме является лучшим решением (по крайней мере, на мой смиренный взгляд):

https://stackoverflow.com/a/9580057/614880

Я сильно подозреваю, что хитрые последствия того, что вы оставите LoginActivity в живых, вызывают много этой путаницы.

Удачи.

Майк Репасс
источник
5
Хороший ответ. Уловка FLAG_ACTIVITY_CLEAR_TOP, которую большинство людей советуют использовать, просто не работает, если вы завершили LoginActivity.
Konsumierer
Спасибо за ответ. Другой сценарий похож на то, когда есть сеанс (например, fb), даже если мы не вызываем активность входа в систему, поэтому в стеке нет точки активности входа. Тогда вышеупомянутый подход бесполезен.
Прашант Деббадвар
118

ОБНОВИТЬ

супер finishAffinity()метод поможет уменьшить код, но достичь того же. Он завершит текущее действие, а также все действия в стеке, используйте, getActivity().finishAffinity()если вы находитесь во фрагменте.

finishAffinity(); 
startActivity(new Intent(mActivity, LoginActivity.class));

ОРИГИНАЛЬНЫЙ ОТВЕТ

Предположим, что LoginActivity -> HomeActivity -> ... -> Настройки CallAutOut ():

void signOut() {
    Intent intent = new Intent(this, HomeActivity.class);
    intent.putExtra("finish", true);
    intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); // To clean up all activities
    startActivity(intent);
    finish();
}

HomeActivity:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    boolean finish = getIntent().getBooleanExtra("finish", false);
    if (finish) {
        startActivity(new Intent(mContext, LoginActivity.class));
        finish();
        return;
    }
    initializeView();
}

Это работает для меня, надеюсь, что это полезно и для вас. :)

thanhbinh84
источник
1
Я думаю, что ваше решение предполагает, что пользователь нажмет signOut и вернется к одному действию (HomeActivity). Что делать, если у вас есть 10 действий в стеке?
Игорь Ганапольский
11
Если у вас есть 10 действий в верхней части HomeActivity, флаг FLAG_ACTIVITY_CLEAR_TOP поможет очистить все из них.
thanhbinh84
1
Это может работать только в том случае, если при запуске HomeActivity вы получаете OnCreate of HomeActivity. Простое начало домашней деятельности не обязательно воссоздает ее, если она не была закончена или уже разрушена. Если HomeActivity не нужно воссоздавать, OnCreate не будет вызываться, и после выхода из системы вы будете заняты домашним делом.
topwik
1
Это возможное решение, которое включает в себя гораздо меньше кодирования простой (для пользователя) функции, такой как выход из системы.
Субин Себастьян
2
Флаг Intent.FLAG_ACTIVITY_CLEAR_TOP помогает очистить все действия, включая HomeActivity, поэтому при повторном запуске этого действия будет вызван метод onCreate ().
thanhbinh84
73

Если вы используете API 11 или выше, вы можете попробовать это: FLAG_ACTIVITY_CLEAR_TASK- Кажется, он решает именно вашу проблему. Очевидно, что толпа до API 11 должна была использовать некоторую комбинацию, когда все действия проверяют дополнительную, как предполагает @doreamon, или какую-то другую хитрость.

(Также обратите внимание: чтобы использовать это, вы должны пройти FLAG_ACTIVITY_NEW_TASK)

Intent intent = new Intent(this, LoginActivity.class);
intent.putExtra("finish", true); // if you are checking for this in your other Activities
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | 
                Intent.FLAG_ACTIVITY_CLEAR_TASK |
                Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
finish();
xbakesx
источник
1
Работать как шарм. Большое спасибо! Поскольку я разрабатываю минимальный API 14, это единственное, что нужно реализовать.
AndyB
1
Я думаю, что нам не нужно FLAG_ACTIVITY_CLEAR_TOP при использовании этого решения для LoginActivity.
Бхарат Додея
Я думаю, что просто finish();сделает работу, чтобы предотвратить возвращение после выхода из системы. Зачем нужно устанавливать флаги?
Панкадж
Это создает исключение. Для вызова startActivity () вне контекста Activity требуется флаг FLAG_ACTIVITY_NEW_TASK
Aman
31

Я тоже потратил на это несколько часов ... и согласен с тем, что FLAG_ACTIVITY_CLEAR_TOP звучит так, как вы бы хотели: очистить весь стек, кроме запускаемого действия, чтобы кнопка «Назад» выходила из приложения. Тем не менее, как отметил Майк Репасс , FLAG_ACTIVITY_CLEAR_TOP работает только тогда, когда запускаемое вами действие уже находится в стеке; когда активности нет, флаг ничего не делает.

Что делать? Поместите запускаемое действие в стек с помощью FLAG_ACTIVITY_NEW_TASK , что делает это действие началом новой задачи в стеке истории. Затем добавьте флаг FLAG_ACTIVITY_CLEAR_TOP.

Теперь, когда FLAG_ACTIVITY_CLEAR_TOP отправляется на поиски нового действия в стеке, оно будет там и будет извлечено до того, как все остальное будет очищено.

Вот моя функция выхода из системы; Параметр View - это кнопка, к которой прикреплена функция.

public void onLogoutClick(final View view) {
    Intent i = new Intent(this, Splash.class);
    i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
    startActivity(i);
    finish();
}
christinac
источник
1
Не будет работать над API <= 10, так как FLAG_ACTIVITY_CLEAR_TASKон еще не был добавлен
Youans
1
@christinac Вы говорите о FLAG_ACTIVITY_CLEAR_TOP, и ваш фрагмент кода имеет FLAG_ACTIVITY_CLEAR_TASK; что тогда действительно?
Мариан Паджиох,
10

Много ответов. Может быть, этот тоже поможет

Intent intent = new Intent(activity, SignInActivity.class)
                .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
this.startActivity(intent);
this.finish();

Котлин версия-

Intent(this, SignInActivity::class.java).apply {
    addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
    addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}.also { startActivity(it) }
finish()
Gulshan
источник
4

Используйте это, это должно быть полезно для вас. Слегка модифицированный ответ xbakesx.

Intent intent = new Intent(this, LoginActivity.class);
if(Build.VERSION.SDK_INT >= 11) {
    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_CLEAR_TASK);
} else {
    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
}
startActivity(intent);
Мохамед Ибрагим
источник
4

Принятое решение не является правильным, оно имеет проблемы, так как использование широковещательного приемника не является хорошей идеей для этой проблемы. Если ваша деятельность уже вызвала метод onDestroy (), вы не получите получателя. Лучшее решение - иметь логическое значение в ваших общих настройках и проверять его в методе onCreate () вашего актива. Если это не должно быть вызвано, когда пользователь не вошел в систему, тогда закончите деятельность. Вот пример кода для этого. Так просто и работает для любых условий.

protected void onResume() {
  super.onResume();
  if (isAuthRequired()) {
    checkAuthStatus();
  }
}

private void checkAuthStatus() {
  //check your shared pref value for login in this method
  if (checkIfSharedPrefLoginValueIsTrue()) {
    finish();
  }
}

boolean isAuthRequired() {
  return true;
}
Екмер Симсек
источник
1
Прошли годы, но я верю, что сделал и то, и другое. Каждая активность расширяла LoggedInActivity, которая проверяла состояние входа пользователя в систему в onCreate (). LoggedInActivity также прослушивала широковещательную рассылку «пользователь вышел из системы» и делала все, что нужно для ответа на это.
Скайлер
2
более вероятно, вы должны поместить checkAuthStatus в методе onResume(). Потому что, когда вы нажимаете кнопку «Назад», действие, скорее всего, будет возобновлено, а не создано.
Майси
1
@maysi Спасибо за предложение, да, кнопка обратного хода тоже будет работать правильно, я обновил запись
Yekmer Simsek
4

Когда-то finish() не работает

Я решил эту проблему с

finishAffinity()

Аджай
источник
3

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

@Override
public void onBackPressed() {
    // disable going back to the MainActivity
    moveTaskToBack(true);
}

Я предполагаю, что LoginActivity завершается сразу после входа пользователя в систему, поэтому он не может вернуться к нему позже, нажав кнопку «Назад». Вместо этого пользователь должен нажать кнопку выхода из приложения внутри приложения, чтобы правильно выйти из системы. Эта кнопка выхода из системы реализует простое намерение:

Intent intent = new Intent(this, LoginActivity.class);
startActivity(intent);
finish();

Все предложения приветствуются.

ноль
источник
2

Выбранный ответ умный и хитрый. Вот как я это сделал:

LoginActivity - это корневая активность задачи, установите для нее android: noHistory = "true" в Manifest.xml; Скажем, вы хотите выйти из SettingsActivity, вы можете сделать это, как показано ниже:

    Intent i = new Intent(SettingsActivity.this, LoginActivity.class);
    i.addFlags(IntentCompat.FLAG_ACTIVITY_CLEAR_TASK
            | Intent.FLAG_ACTIVITY_NEW_TASK);
    startActivity(i);
т-гао
источник
2

Вот решение, которое я нашел в своем приложении.

В моей функции LoginActivity после успешной обработки входа в систему я запускаю следующий по-разному в зависимости от уровня API.

Intent i = new Intent(this, MainActivity.class);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
    startActivity(i);
    finish();
} else {
    startActivityForResult(i, REQUEST_LOGIN_GINGERBREAD);
}

Затем в методе onActivityForResult моего LoginActivity:

if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB &&
        requestCode == REQUEST_LOGIN_GINGERBREAD &&
        resultCode == Activity.RESULT_CANCELED) {
    moveTaskToBack(true);
}

Наконец, после обработки выхода из любого другого действия:

Intent i = new Intent(this, LoginActivity.class);
i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(i);

Когда на Gingerbread, делает так, если я нажимаю кнопку «Назад» в MainActivity, LoginActivity сразу скрывается. На Honeycomb и более поздних версиях я просто заканчиваю LoginActivity после обработки имени входа, и оно правильно воссоздается после обработки выхода из системы.

seastland
источник
Это не работает для меня. В моем случае я использовал фрагментированность. Метод onactivityresult не вызывается.
Мохамед Ибрагим
1

Это сработало для меня:

     // After logout redirect user to Loing Activity
    Intent i = new Intent(_context, MainActivity.class);
    // Closing all the Activities
    i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
    i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);

    // Add new Flag to start new Activity
    i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

    // Staring Login Activity
    _context.startActivity(i);
Preetansh
источник
Не могли бы вы более подробно изложить свой ответ, добавив немного больше описания предлагаемого вами решения?
abarisone
0

Начните свою деятельность с StartActivityForResult, и пока вы выходите из системы, установите свой результат и, в соответствии с вашим результатом, завершите свою деятельность

intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
startActivityForResult(intent, BACK_SCREEN);

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    switch (requestCode) {
    case BACK_SCREEN:
        if (resultCode == REFRESH) {
            setResult(REFRESH);
            finish();
        }
        break;
    }
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
    if (keyCode == KeyEvent.KEYCODE_BACK) {
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        AlertDialog alertDialog = builder.create();

        alertDialog
                .setTitle((String) getResources().getText(R.string.home));
        alertDialog.setMessage((String) getResources().getText(
                R.string.gotoHome));
        alertDialog.setButton(DialogInterface.BUTTON_POSITIVE, "Yes",
                new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog,
                            int whichButton) {

                        setResult(REFRESH);
                        finish();
                    }

                });

        alertDialog.setButton(DialogInterface.BUTTON_NEGATIVE, "No",
                new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog,
                            int whichButton) {
                    }
                });
        alertDialog.show();
        return true;
    } else
        return super.onKeyDown(keyCode, event);

}
Эби
источник
0

Предоставленное решение @doreamon отлично работает во всех случаях, кроме одного:

Если после входа в систему пользователь Killing Login перешел прямо к среднему экрану. Например, в потоке A-> B-> C, перейдите, как: Логин -> B -> C -> Нажмите ярлык для дома. Использование FLAG_ACTIVITY_CLEAR_TOP очищает только действие C, так как Home (A) отсутствует в истории стека. Нажатие Back на экране приведет нас к B.

Чтобы решить эту проблему, мы можем сохранить стек действий (Arraylist), и когда нажата кнопка home, мы должны убить все действия в этом стеке.

Сурендра Кумар
источник
0

Это возможно, управляя флагом в SharedPreferences или в Application Activity.

При запуске приложения (на заставке) установите флаг = false; При событии «Выйти из системы» просто установите флажок «истина» и в OnResume () каждой операции проверьте, установлен ли флажок «истина», затем вызовите finish ().

Работает как часы :)

MrDumb
источник
0

по клику выхода вы можете позвонить

private void GoToPreviousActivity() {
    setResult(REQUEST_CODE_LOGOUT);
    this.finish();
}

onActivityResult () предыдущей операции, вызывайте этот код еще раз, пока вы не завершите все действия.

Mak
источник
1
Это означает, что он должен проходить через все действия линейно, вместо того, чтобы транслировать финиш () одновременно?
Игорь Ганапольский
@ Игорь Григорьевич да, это альтернативный способ сделать финиш последовательно
Мак
-1

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

То, что вы хотите сделать, это вызвать logout () и finish () для ваших методов onStop () или onPause (). Это заставит Android вызывать onCreate () при возобновлении активности, поскольку он больше не будет находиться в стеке своей активности. Затем сделайте, как вы говорите, в onCreate () проверьте статус вошедшего в систему и перейдите на экран входа в систему, если вы не вошли в систему.

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

Рикардо Вильямиль
источник
Я бы предпочел не выходить из системы после каждой паузы или остановки. Кроме того, приложение инициирует выход из системы или вход в систему, поэтому мне не нужно проверять, вошел ли пользователь в систему на самом деле.
Скайлер
@Ricardo: ваше решение не требует BroadcastReceiver?
Игорь Ганапольский