web-dev-qa-db-ja.com

ログアウト時に、アクティビティ履歴スタックをクリアし、「戻る」ボタンがログインのみのアクティビティを開かないようにします

アプリケーションのすべてのアクティビティを表示するには、ユーザーがログインする必要があります。ユーザーは、ほぼすべてのアクティビティからログアウトできます。これはアプリケーションの要件です。ユーザーがログアウトした場合はいつでも、ログインActivityにユーザーを送信します。この時点で、「戻る」ボタンを押すとユーザーがAndroidのホーム画面に戻るように、このアクティビティを履歴スタックの一番下に配置する必要があります。

この質問がいくつかの異なる場所で尋ねられ、すべてが同様の回答(ここで概要を説明します)で回答されたのを見てきましたが、フィードバックを収集するためにここで提示したいと思います。

IntentフラグをFLAG_ACTIVITY_CLEAR_TOPに設定することでLoginアクティビティを開こうとしましたが、これはドキュメントで概説されているように見えますが、Loginアクティビティを履歴スタックの一番下に置き、ユーザーが以前に表示されたログインアクティビティに戻る。また、マニフェストのログインアクティビティにAndroid:launchMode="singleTop"を使用しようとしましたが、これでも目標を達成できません(とにかく効果がないようです)。

履歴スタックをクリアするか、以前に開いたすべてのアクティビティを完了する必要があると思います。

1つのオプションは、各アクティビティのonCreateにログインステータスをチェックさせ、ログインしていない場合はfinish()をチェックさせることです。戻るボタンは引き続き使用でき、アクティビティが閉じると戻るように戻るため、このオプションは好きではありません。

次のオプションは、どこからでも静的にアクセス可能なすべてのオープンアクティビティへの参照のLinkedListを維持することです(おそらく、弱い参照を使用します)。ログアウトすると、このリストにアクセスして、以前に開いたすべてのアクティビティを繰り返し処理し、各アクティビティでfinish()を呼び出します。おそらくすぐにこのメソッドの実装を開始するでしょう。

ただし、Intentフラグトリックを使用してこれを実現します。上記で説明した2つの方法のいずれかを使用せずに、アプリケーションの要件を満たすことができることを非常にうれしく思います。

Intentまたはマニフェスト設定を使用してこれを達成する方法はありますか?または、開かれたアクティビティのLinkedListを維持する2番目のオプションが最良のオプションですか?または、私が完全に見落としている別のオプションがありますか?

224
skyler

私は、より堅牢な別のアプローチを提案することができます。基本的に、ログイン状態を維持する必要があるすべてのアクティビティにログアウトメッセージをブロードキャストする必要があります。したがって、sendBroadcastを使用して、すべてのActvitiesにBroadcastReceiverをインストールできます。このようなもの:

/** 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 **//
}
206

新しいAndroidプログラマーがこの問題の調査とこれらのStackOverflowスレッドのすべての読み取りに1日を費やすのは通過儀礼のようです。私は今、新しく始められました。そして、ここに私の謙虚な経験の痕跡を残して、将来の巡礼者を助けます。

最初に、私の研究ごとにこれを行う明白な方法または即時の方法はありません(as of September 2012).あなたはstartActivity(new Intent(this, LoginActivity.class), CLEAR_STACK)を単純化できると思うでしょうが、no

FLAG_ACTIVITY_CLEAR_TOPstartActivity(new Intent(this, LoginActivity.class))を実行できます。これにより、フレームワークがスタックを検索し、LoginActivityの以前の元のインスタンスを見つけ、それを再作成して、残りの(上位の)スタックをクリアします。また、ログインはおそらくスタックの一番下にあるため、空のスタックがあり、戻るボタンを押すだけでアプリケーションが終了します。

しかし-これは、LoginActivityの元のインスタンスをスタックのベースに以前に残した場合にのみ機能します。多くのプログラマーのように、ユーザーが正常にログインした後に_LoginActivityfinish()に選択した場合、スタックのベースから外れ、FLAG_ACTIVITY_CLEAR_TOPセマンティクスが適用されなくなります... LoginActivityは既存のスタックの上にあります。これはほとんど間違いなくあなたが望むものではありません(ユーザーが前の画面にログインしてから「戻る」ことができる奇妙な動作)。

したがって、以前にLoginActivityfinish()した場合は、スタックをクリアしてから新しいLoginActivityを開始するためのメカニズムを追求する必要があります。このスレッドの@doreamonによる答えが最良の解決策のようです(少なくとも私の謙虚な目にとっては):

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

LoginActivityを有効にしたままにするかどうかのトリッキーな意味が、この混乱の多くを引き起こしていると強く思います。

がんばろう。

147
Mike Repass

UPDATE

スーパーfinishAffinity()メソッドは、コードを削減するのに役立ちますが、同じことを実現します。現在のアクティビティとスタック内のすべてのアクティビティを終了します。フラグメントにいる場合はgetActivity().finishAffinity()を使用します。

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

元の回答

LoginActivity-> HomeActivity-> ...-> SettingsActivityがsignOut()を呼び出すと仮定します。

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

これは私にとってはうまくいきます。あなたにも役立つことを願っています。 :)

109
thanhbinh84

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

私もこれに数時間を費やしました...そして、 FLAG_ACTIVITY_CLEAR_TOP があなたが望むもののように聞こえることに同意します:起動されているアクティビティを除いて、スタック全体をクリアして、戻るボタンがアプリケーションを終了します。 Mike Repass に言及したように、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();
}
30
christinac

たくさんの答え。これも役立つかもしれません

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

これを使用して、あなたに役立つはずです。 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);
3
Mohamed Ibrahim

受け入れられた解決策は正しくありません。ブロードキャストレシーバーを使用することはこの問題に対して良い考えではないため、問題があります。アクティビティがすでに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;
}
3
Yekmer Simsek

これが私のアプリで思いついた解決策です。

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

次に、LoginActivityのonActivityForResultメソッドで:

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
seastland

いつかfinish()が機能しない

私はその問題を解決しました

finishAffinity()

1
AJay

選択された答えは巧妙でトリッキーです。以下がその方法です。

LoginActivityはタスクのルートアクティビティです。Manifest.xmlでAndroid:noHistory = "true"に設定します。 SettingsActivityからログアウトしたい場合、以下のようにできます:

    Intent i = new Intent(SettingsActivity.this, LoginActivity.class);
    i.addFlags(IntentCompat.FLAG_ACTIVITY_CLEAR_TASK
            | Intent.FLAG_ACTIVITY_NEW_TASK);
    startActivity(i);
1
t-gao

この質問に対する別のアプローチをお勧めします。たぶんそれは最も効率的なものではないかもしれませんが、私はそれが適用するのが最も簡単で、コードをほとんど必要としないと思います。最初のアクティビティ(私の場合はログインアクティビティ)で次のコードを記述しても、ユーザーはログアウト後に以前に起動したアクティビティに戻ることができません。

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

ユーザーがログインした直後にLoginActivityが終了したため、ユーザーが[戻る]ボタンを押して後で戻ることができないと想定しています。代わりに、ユーザーは適切にログアウトするためにアプリ内のログアウトボタンを押す必要があります。このログアウトボタンが実装するものは、次のような単純な意図です。

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

すべての提案を歓迎します。

1
Nil

ログアウトをクリックすると、これを呼び出すことができます

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

前のアクティビティのonActivityResult()は、すべてのアクティビティが完了するまで、上記のコードを再度呼び出します。

0
Mak

これは私のために働いた:

     // 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
Preetansh

SharedPreferencesまたはApplication Activityでフラグを管理することにより可能です。

(スプラッシュスクリーンで)アプリの起動時に、フラグをfalseに設定します。ログアウトクリックイベントでフラグをtrueに設定し、すべてのアクティビティのOnResume()でフラグがtrueであるかどうかを確認して、finish()を呼び出します。

それは魅力のように機能します:)

0
MrDumb

@doreamonが提供するソリューションは、1つを除くすべてのケースで正常に機能します。

ログイン後、ログイン画面を強制終了したユーザーが直接中央画面に移動した場合。例えばA-> B-> Cのフローで、ログイン-> B-> C->ホームへのショートカットを押します。 FLAG_ACTIVITY_CLEAR_TOPを使用すると、Home(A)はスタック履歴にないため、Cアクティビティのみがクリアされます。 A画面で[戻る]を押すと、Bに戻ります。

この問題に取り組むには、アクティビティスタック(Arraylist)を保持し、ホームが押されたときに、このスタック内のすべてのアクティビティを強制終了する必要があります。

0
Surendra Kumar

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
Eby