Під час виходу очистіть стек історії активності, не дозволяючи кнопці «назад» відкривати дії, які ввійшли лише в систему


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 () буде викликано, але він все ще може приймати трансляції. Вам просто потрібно переконатися, що ви викликаєте undegisterReceiver () в onDestroy (), а не onStop ().
Рассел Девіс

9
Чи працює це, якщо операція десь у стеку була вимкнена ОС для відновлення пам'яті? Тобто чи вважатиме система це справді закінченим після надсилання трансляції вище, і не відтворить його після натискання на кнопку назад
Ян Żankowski

5
Хоча це здається елегантним рішенням, важливо зазначити, що це не синхронно.
Че Джамі

34
Гарне рішення, але замість використання реєстрації приймача широкомовної передачі, як описано у наведеному вище коді, слід використовувати LocalBroadcastManager.getInstance (це) .registerReceiver (...) та LocalBroadcastManager.getInstance (це) .unregisterReceiver (..) . Інші користувачі вашої програми можуть отримувати наміри від будь-якої іншої програми (питання безпеки)
Уку Лоскіт

5
@Warlock Ви маєте рацію. Проблема цього підходу полягає в тому, коли діяльність в задній стеці знищується системою (може статися з різних причин, наприклад, відзначений сценарій з низькою пам'яттю). У такому випадку екземпляр Activity не буде навколо прийому трансляції. Але система все одно повернеться до цієї діяльності, відтворивши її. Це можна перевірити / відтворити, увімкнувши налаштування для розробника "Не продовжуйте діяльність"
Eric Schlenz

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, відтворити його і очистити решту (вгору) стека. А оскільки Login, ймовірно, знаходиться внизу стеку, тепер у вас є порожній стек, а кнопка «Назад» просто закриває додаток.

АЛЕ - це працює лише в тому випадку, якщо раніше ви залишили живий цей оригінальний примірник LoginActivity в основі вашої стеки. Якщо, як і багато програмістів, ви вибрали finish()це, LoginActivityяк тільки користувач успішно увійшов, тоді він більше не знаходиться на основі стека і FLAG_ACTIVITY_CLEAR_TOPсемантика не застосовується ... ви в кінцевому підсумку створюєте новийLoginActivity поверх існуючого стека. Що майже напевно НЕ, що ви хочете (дивна поведінка, коли користувач може «повернути» свій вихід із входу на попередній екран).

Так що, якщо ви раніше finish()«d LoginActivity, ви повинні слідувати будь - то механізм для очищення вашого стека , а потім починає новий LoginActivity. Здається, що відповідь @doreamonу цій темі - найкраще рішення (принаймні, на моє скромне око):

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

Я сильно підозрюю, що хитрі наслідки того, чи залишите ви LoginActivity в живих, викликають багато цієї плутанини.

Щасти.


5
Хороша відповідь. Трюк FLAG_ACTIVITY_CLEAR_TOP, який рекомендують використовувати більшість людей, просто не працює, якщо ви закінчили функцію LoginActivity.
Konsumierer

Дякуємо за відповідь. Інший сценарій подібний, коли відбувається сеанс (наприклад, як fb), навіть ми не викликаємо активність входу, тому в стеці немає жодної точки входу в систему. Тоді вищезгаданий підхід марний.
Prashanth Debbadwar

118

ОНОВЛЕННЯ

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

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

ОРИГІНАЛЬНИЙ ВІДПОВІДЬ

Припустимо, що LoginActivity -> HomeActivity -> ... -> SettingsActivity callOut ():

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();
}

Домашня активність:

@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();
}

Це працює для мене, сподіваюсь, що це буде корисно і для вас. :)


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();

1
Працює як шарм. Велике спасибі! Коли я розробляю min min API 14, це єдине, що потрібно реалізувати.
AndyB

1
Я думаю, що нам не потрібен FLAG_ACTIVITY_CLEAR_TOP при використанні цього рішення для LoginActivity.
Бхарат Додея

Думаю, просто finish();зроблять роботу, щоб не допустити повернення після виходу. У чому необхідність встановлення прапорів?
Панкай

Це створює виняток Для виклику startActivity () поза контекстом діяльності потрібен прапор FLAG_ACTIVITY_NEW_TASK
Аман

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();
}

1
Не працюватиме в API <= 10, оскільки FLAG_ACTIVITY_CLEAR_TASKще не додано
Youans

1
@christinac Ви говорите про FLAG_ACTIVITY_CLEAR_TOP, а фрагмент коду має FLAG_ACTIVITY_CLEAR_TASK; що є дійсним тоді?
Marian Paździoch

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()

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 також слухала трансляцію "користувач, який вийшов із системи", і робила все необхідне, щоб відповісти на це.
skyler

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 після обробки логіна, і він належним чином відтворений після обробки виходу.


Це не працює для мене. У моєму випадку я використовував фрагментарність. Метод результативності результатів не називається.
Мохаммед Ібрагім

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);

Чи можете ви детальніше розглянути свою відповідь, додавши трохи більше опису про рішення, яке ви надаєте?
абарізон

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 надає чудові результати для всіх випадків, крім одного:

Якщо після входу, користувач екрану вбивства перейшов безпосередньо до середнього екрана. наприклад, у потоці A-> B-> C, перейдіть так: Вхід -> B -> C -> Натисніть ярлик додому. Використання FLAG_ACTIVITY_CLEAR_TOP очищає лише діяльність C, оскільки дім (A) не знаходиться в історії стека. Натискання Назад на екрані поверне нас до B.

Щоб вирішити цю проблему, ми можемо зберегти стек активності (Arraylist), і коли натиснеться будинок, ми повинні знищити всі дії в цьому стеку.


0

Це можливо, керуючи прапором у SharedPreferences або в Application Activity.

При запуску програми (на екрані заставки) встановіть прапор = false; Під час виходу події Клацніть просто встановіть прапор істинно, і в OnResume () кожної діяльності перевірте, чи прапор є істинним, а потім зателефонуйте завершити ().

Це працює як шарм :)


0

при натисканні виходу ви можете це зателефонувати

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

onActivityResult () попередньої активності викликайте цей вищезгаданий код ще раз, поки ви не закінчите всі дії.


1
Це означає, що він повинен лінійно проходити всі дії, а не транслювати фініш (() всі відразу?
ІгорГанапольський

@Igor G. так, це альтернативний спосіб зробити фініш послідовно
Мак

-1

Один із варіантів полягає у тому, щоб увімкнути статус реєстрації для реєстрації кожної активності onCreate і закінчити (), якщо не увійшли. Цей варіант мені не подобається, оскільки кнопка "Назад" все ще буде доступна для використання, навігація назад, коли діяльність закриється.

Що ви хочете зробити, це вийти з виклику () та закінчити () на своїх методах onStop () або onPause (). Це змусить Android викликати onCreate (), коли активність буде повернута, оскільки він більше не матиме її в стеці своєї діяльності. Потім зробіть, як ви говорите, у onCreate () встановіть прапорець, що увійшов у систему, і перейдіть до екрана для входу, якщо він не увійшов.

Інша річ, яку ви можете зробити, це перевірити статус входу в onResume (), а якщо не ввійти в систему, закінчіть () і запустіть активацію входу.


Я вважаю за краще не виходити з кожного паузи чи зупинки діяльності. Крім того, програма ініціює вихід із системи чи вхід у систему, тому мені не потрібно перевіряти, чи увійшли в систему.
skyler

@Ricardo: чи не потрібне ваше рішення BroadcastReceiver?
ІгорГанапольський
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.