進行状況ダイアログとバックグラウンドスレッドがアクティブなときに画面の向きの変更を処理する方法


524

私のプログラムはバックグラウンドスレッドでいくつかのネットワークアクティビティを実行します。開始する前に、進捗ダイアログをポップアップします。ダイアログはハンドラーで閉じられます。ダイアログが表示されているときに(およびバックグラウンドスレッドが表示されているときに)画面の向きが変わる場合を除いて、これはすべて正常に機能します。この時点で、アプリはクラッシュまたはデッドロックするか、すべてのスレッドが終了するまでアプリがまったく機能しないという奇妙な段階に入ります。

画面の向きの変更を適切に処理するにはどうすればよいですか?

以下のサンプルコードは、私の実際のプログラムが行うこととほぼ一致します。

public class MyAct extends Activity implements Runnable {
    public ProgressDialog mProgress;

    // UI has a button that when pressed calls send

    public void send() {
         mProgress = ProgressDialog.show(this, "Please wait", 
                      "Please wait", 
                      true, true);
        Thread thread = new Thread(this);
        thread.start();
    }

    public void run() {
        Thread.sleep(10000);
        Message msg = new Message();
        mHandler.sendMessage(msg);
    }

    private final Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            mProgress.dismiss();
        }
    };
}

スタック:

E/WindowManager(  244): Activity MyAct has leaked window com.android.internal.policy.impl.PhoneWindow$DecorView@433b7150 that was originally added here
E/WindowManager(  244): android.view.WindowLeaked: Activity MyAct has leaked window com.android.internal.policy.impl.PhoneWindow$DecorView@433b7150 that was originally added here
E/WindowManager(  244):     at android.view.ViewRoot.<init>(ViewRoot.java:178)
E/WindowManager(  244):     at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:147)
E/WindowManager(  244):     at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:90)
E/WindowManager(  244):     at android.view.Window$LocalWindowManager.addView(Window.java:393)
E/WindowManager(  244):     at android.app.Dialog.show(Dialog.java:212)
E/WindowManager(  244):     at android.app.ProgressDialog.show(ProgressDialog.java:103)
E/WindowManager(  244):     at android.app.ProgressDialog.show(ProgressDialog.java:91)
E/WindowManager(  244):     at MyAct.send(MyAct.java:294)
E/WindowManager(  244):     at MyAct$4.onClick(MyAct.java:174)
E/WindowManager(  244):     at android.view.View.performClick(View.java:2129)
E/WindowManager(  244):     at android.view.View.onTouchEvent(View.java:3543)
E/WindowManager(  244):     at android.widget.TextView.onTouchEvent(TextView.java:4664)
E/WindowManager(  244):     at android.view.View.dispatchTouchEvent(View.java:3198)

onSaveInstanceStateの進行状況ダイアログを閉じようとしましたが、それはすぐにクラッシュするのを防ぐだけです。バックグラウンドスレッドはまだ進行中であり、UIは部分的に描画された状態です。アプリが再び動作する前に、アプリ全体を強制終了する必要があります。


1
あなたが受け取った答えを考慮して、あなたは最高のものを支持して受け入れられた答えを変更するべきですよね?
rds

また、元の質問を参照してくださいstackoverflow.com/questions/456211/...を
RDS

3
すべて、この問題に対する本当に素晴らしい説明と可能な解決策を得ました。通過http://blog.doityourselfandroid.com/2010/11/14/handling-progress-dialogs-and-screen-orientation-changes/これが助けた場合レムは知っています。
arcamax

2
このブログの投稿には、画面の向きを変えても非同期のバックグラウンドタスクを保持する方法に関するかなり完全な説明があります。見てみな!
エイドリアンモンク

マニフェストでandroid:configChanges = "orientation | screenSize"をActivityに設定するだけです。アクティビティを再作成するためにandroidを停止します
Jawad Zeb

回答:


155

方向を切り替えると、Androidは新しいビューを作成します。バックグラウンドスレッドが古いスレッドの状態を変更しようとしているため、おそらくクラッシュしています。(バックグラウンドスレッドがUIスレッド上にないため、問題が発生している可能性もあります)

そのmHandlerを揮発性にして、向きが変わったときに更新することをお勧めします。


14
クラッシュの理由を特定した可能性があります。クラッシュを解消しましたが、方向を変更する前の状態にUIを信頼できる方法で復元する方法はまだわかりません。しかし、あなたの答えは私を前進させたので、それを答えとして与えました。
Heikki Toivonen

4
向きが変わると、アクティビティにonStartが表示されます。基本的に、古いデータを使用してビューを再構成する必要があります。したがって、新しい「onStart」を取得したら、プログレスバーから数値ステータスudpatesを要求し、新しいビューを再構築することをお勧めします。新しいアクティビティも取得した場合、オフハンドを覚えることはできませんが、ドキュメントのいくつかの調査が役立つはずです。
haseman、2009

6
最近それを使って、アプリの向きが変わったときに新しいアクティビティが発生することを伝えます。(新しいビューも取得します)古いビューを更新しようとすると、古いビューに無効なアプリケーションコンテキスト(古いアクティビティ)があるため、例外が発生します。myActivity.getApplicationContext()を渡すことで、これを回避できます。アクティビティ自体へのポインタの代わりに。
haseman 2009

1
誰かがこのコンテキストでのvolatileの使用/利点を説明できますか
Jawad Zeb 2015

2
@ネプスターええ、私もそれについて疑問に思っていました。誰かが揮発性について説明した場合、それは素晴らしいでしょう。
RestInPeace

261

編集:このStackOverflowの投稿でDianne Hackborn(別名hackbod)が説明しているように、Googleのエンジニアはこのアプローチを推奨していません。詳細については、このブログ投稿をご覧ください。


これをマニフェストのアクティビティ宣言に追加する必要があります。

android:configChanges="orientation|screenSize"

ので

<activity android:label="@string/app_name" 
        android:configChanges="orientation|screenSize|keyboardHidden" 
        android:name=".your.package">

問題は、構成の変更が発生すると、システムがアクティビティを破棄することです。ConfigurationChangesを参照してください。

したがって、それを構成ファイルに入れると、システムがアクティビティを破壊することを回避できます。代わりに、onConfigurationChanged(Configuration)メソッドを呼び出します。


21
これは間違いなく最良の解決策です。単純にレイアウトを回転させるため(最初に期待する動作)。android:configChanges = "orientation | keyboardHidden"を必ず入れてください(横向きのキーボードを備えた携帯電話のため)
nikib3ro

24
これは私が期待する動作のようです。ただし、ドキュメントには、「レイアウトファイルを含むすべてのアプリケーションリソースが構成値に基づいて変更される可能性があるため、アクティビティが破棄されることが示されています。したがって、構成変更を処理する唯一の安全な方法は、すべてのリソースを再取得することです」とあります。それに加えてorientation、設定を変更する理由は他にもたくさんあります:(keyboardHidden私はすでにwikiの回答を編集しました)、uiMode(たとえば、車のモードに入る、または車から出る、ナイトモードの変更など)。良い答え。
rds

116
これは許容できる解決策ではありません。それは本当の問題を覆い隠すだけです。
rf43 '10 / 10/13

18
機能しますが、Googleは推奨しません。
Ed Burnette、2011年

21
ここでは、このアプローチを実行しないでください。DDosAttackは完全に正しいです。ダウンロードなど、長時間かかる何かの進行状況ダイアログを作成しているとします。ユーザーとして、あなたはその活動に留まり、それを見つめることはありません。ホーム画面に切り替えるか、ゲームや電話などの別のアプリに切り替えるか、リソースが不足して最終的にアクティビティが破壊される可能性があります。そしてそれから?あなたは同じ古い問題に直面していますが、それはその巧妙な小さなトリックでは解決されません。アクティビティは、ユーザーが戻ってきたときに最初から作成されます。
tiguchi

68

私は、これらの問題に対して「Androidの方法」に準拠した強固なソリューションを思い付きました。IntentServiceパターンを使用して、長時間実行されるすべての操作を実行しています。

つまり、私のアクティビティはインテントをブロードキャストし、IntentServiceが作業を行い、データをDBに保存してから、スティッキーインテントをブロードキャストします。スティッキー部分は重要です。たとえば、ユーザーが作業を開始した後の間にアクティビティが一時停止され、IntentServiceからのリアルタイムブロードキャストを見逃した場合でも、呼び出し側のアクティビティから応答してデータを取得できます。ProgressDialogsは、でこのパターンを非常にうまく処理できonSaveInstanceState()ます。

基本的に、保存されたインスタンスバンドルで進行ダイアログが実行されていることを示すフラグを保存する必要があります。アクティビティ全体がリークするため、進行状況ダイアログオブジェクトを保存しないでください。進行状況ダイアログへの永続的なハンドルを持たせるために、アプリケーションオブジェクトに弱い参照として保存します。方向を変更したり、アクティビティを一時停止させたり(電話、ユーザーが家に帰るなど)した後は、古いダイアログを閉じて、新しく作成したアクティビティで新しいダイアログを再作成します。

無期限の進捗ダイアログの場合、これは簡単です。進行状況バーのスタイルの場合、進行状況を追跡するために、バンドル内の最後の既知の進行状況と、ローカルでアクティビティで使用している情報を配置する必要があります。進行状況を復元する際に、この情報を使用して、以前と同じ状態で進行状況バーを再起動し、現在の状態に基づいて更新します。

まとめると、長期実行タスクをIntentServiceに入れて、を適切に使用onSaveInstanceState()することで、ダイアログを効率的に追跡し、アクティビティのライフサイクルイベント全体を復元できます。アクティビティコードの関連ビットを以下に示します。また、スティッキーインテントを適切に処理するには、BroadcastReceiverにロジックが必要ですが、これは範囲外です。

public void doSignIn(View view) {
    waiting=true;
    AppClass app=(AppClass) getApplication();
    String logingon=getString(R.string.signon);
    app.Dialog=new WeakReference<ProgressDialog>(ProgressDialog.show(AddAccount.this, "", logingon, true));
    ...
}

@Override
protected void onSaveInstanceState(Bundle saveState) {
    super.onSaveInstanceState(saveState);
    saveState.putBoolean("waiting",waiting);
}

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    if(savedInstanceState!=null) {
        restoreProgress(savedInstanceState);    
    }
    ...
}

private void restoreProgress(Bundle savedInstanceState) {
    waiting=savedInstanceState.getBoolean("waiting");
    if (waiting) {
        AppClass app=(AppClass) getApplication();
        ProgressDialog refresher=(ProgressDialog) app.Dialog.get();
        refresher.dismiss();
        String logingon=getString(R.string.signon);
        app.Dialog=new WeakReference<ProgressDialog>(ProgressDialog.show(AddAccount.this, "", logingon, true));
    }
}

いい解決策のようです
Derekyy、2015年

「私はIntentServiceパターンを使用して、長時間実行するすべての操作を行っています。」それはより多くのあなたが見ることができるため、スズメや多くの定型的なコードの中に大砲を撃つのが好きであるので、これは完璧なソリューションではありません youtube.com/watch?v=NJsq0TU0qeg
カミルNekanowicz

28

同じ問題に遭遇しました。私のアクティビティはURLから一部のデータを解析する必要があり、それは遅いです。そのため、そのためのスレッドを作成してから、進行状況ダイアログを表示します。スレッドにUIスレッドにメッセージを投稿させるHandler。ではHandler.handleMessage、スレッドからデータオブジェクト(今すぐ準備)を取得してUIに入力します。ですから、あなたの例と非常に似ています。

多くの試行錯誤の後、私は解決策を見つけたようです。少なくとも今は、スレッドが完了する前でも後でも、いつでも画面を回転できます。すべてのテストで、ダイアログは適切に閉じられ、すべての動作は期待どおりです。

私がやったことを以下に示します。目標は、データモデル(mDataObject)に入力し、それをUIに入力することです。予期せずにいつでも画面の回転を許可する必要があります。

class MyActivity {

    private MyDataObject mDataObject = null;
    private static MyThread mParserThread = null; // static, or make it singleton

    OnCreate() {
        ...
        Object retained = this.getLastNonConfigurationInstance();
        if(retained != null) {
            // data is already completely obtained before config change
            // by my previous self.
            // no need to create thread or show dialog at all
            mDataObject = (MyDataObject) retained;
            populateUI();
        } else if(mParserThread != null && mParserThread.isAlive()){
            // note: mParserThread is a static member or singleton object.
            // config changed during parsing in previous instance. swap handler
            // then wait for it to finish.
            mParserThread.setHandler(new MyHandler());
        } else {
            // no data and no thread. likely initial run
            // create thread, show dialog
            mParserThread = new MyThread(..., new MyHandler());
            mParserThread.start();
            showDialog(DIALOG_PROGRESS);
        }
    }

    // http://android-developers.blogspot.com/2009/02/faster-screen-orientation-change.html
    public Object onRetainNonConfigurationInstance() {
        // my future self can get this without re-downloading
        // if it's already ready.
        return mDataObject;
    }

    // use Activity.showDialog instead of ProgressDialog.show
    // so the dialog can be automatically managed across config change
    @Override
    protected Dialog onCreateDialog(int id) {
        // show progress dialog here
    }

    // inner class of MyActivity
    private class MyHandler extends Handler {
        public void handleMessage(msg) {
            mDataObject = mParserThread.getDataObject();
            populateUI();
            dismissDialog(DIALOG_PROGRESS);
        }
    }
}

class MyThread extends Thread {
    Handler mHandler;
    MyDataObject mDataObject;

    // constructor with handler param
    public MyHandler(..., Handler h) {
        ...
        mHandler = h;
    }

    public void setHandler(Handler h) { mHandler = h; } // for handler swapping after config change
    public MyDataObject getDataObject() { return mDataObject; } // return data object (completed) to caller

    public void run() {
        mDataObject = new MyDataObject();
        // do the lengthy task to fill mDataObject with data
        lengthyTask(mDataObject);
        // done. notify activity
        mHandler.sendEmptyMessage(0); // tell activity: i'm ready. come pick up the data.
    }
}

それが私にとってうまくいきます。これがAndroidによって設計された「正しい」方法であるかどうかはわかりません-彼らは、この「画面の回転中にアクティビティを破棄/再作成する」ことで実際に物事が簡単になると主張しています。

コードに問題がある場合はお知らせください。上記のように、副作用があるかどうかは本当にわかりません。


1
どうもありがとう!問題を解決するためのヒントonRetainNonConfigurationInstance()getLastNonConfigurationInstance()私を助けてくれました。いいぞ!
スヴェン

15

元々認識されていた問題は、コードが画面の向きの変更に対応できないことでした。どうやらこれは、UIフレームワークに(onDestroyを呼び出すことで)実行させる代わりに、プログラムが画面の向きの変更自体を処理するようにすることで「解決」されました。

根本的な問題がプログラムがonDestroy()を乗り越えられないことである場合、受け入れられた解決策は、プログラムに他の深刻な問題と脆弱性を残す回避策に過ぎないと私は考えます。Androidフレームワークは、あなたの活動があなたのコントロールできない状況のためにほとんどいつでも破壊される危険にさらされていると明確に述べていることを覚えておいてください。したがって、アクティビティは、画面の向きの変更だけでなく、何らかの理由でonDestroy()およびその後のonCreate()を乗り切ることができる必要があります。

OPの問題を解決するために画面の向きの変更の処理を自分で受け入れる場合は、onDestroy()の他の原因が原因で同じエラーが発生しないことを確認する必要があります。これはできますか?そうでない場合、「受け入れられた」回答が本当に非常に良いものであるかどうかを質問します。


14

私の解決策は、ProgressDialogクラスを拡張して自分のクラスを取得することでしたMyProgressDialog
私は再定義しshow()dismiss()表示する前に向きをロックし、Dialogときにロックを解除するメソッドDialogが却下された。そのため、Dialogが表示され、デバイスの向きが変わるdismiss()と、画面の向きはが呼び出されるまで残り、画面の向きはセンサー値/デバイスの向きに応じて変化します。

これが私のコードです:

public class MyProgressDialog extends ProgressDialog {
private Context mContext;

public MyProgressDialog(Context context) {
    super(context);
    mContext = context;
}

public MyProgressDialog(Context context, int theme) {
    super(context, theme);
    mContext = context;
}

public void show() {
    if (mContext.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT)
        ((Activity) mContext).setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
    else
        ((Activity) mContext).setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
    super.show();
}

public void dismiss() {
    super.dismiss();
    ((Activity) mContext).setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR);
}

}

8

私はこれと同じ問題に直面し、ProgressDialogを使用しても無効にならない解決策を思いついたので、より速い結果が得られました。

私が行ったのは、ProgressBarを含むレイアウトを作成することでした。

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<ProgressBar
    android:id="@+id/progressImage"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_centerInParent="true"
    />
</RelativeLayout>

次に、onCreateメソッドで次のようにします。

public void onCreate(Bundle icicle) {
    super.onCreate(icicle);
    setContentView(R.layout.progress);
}

次に、スレッドで長いタスクを実行し、完了したらRunnableでコンテンツビューをこのアクティビティに使用する実際のレイアウトに設定します。

例えば:

mHandler.post(new Runnable(){

public void run() {
        setContentView(R.layout.my_layout);
    } 
});

これは私がやったことであり、ProgressDialogを表示するよりも実行速度が速く、邪魔にならず、私の見た目がよくなっていることがわかりました。

ただし、ProgressDialogを使用する場合は、この答えは適切ではありません。


このソリューションは単純な使用例ではエレガントですが、欠点があります。完全なコンテンツビューを再構築する必要があります。setContentView(R.layout.my_layout);十分ではありません。すべてのリスナーの設定、データのリセットなどが必要
rds

@rdsあなたは正しい。これは実際には、単純なケースの解決策、またはビューを表示する前にonCreateメソッドを大幅に強化する必要がある場合の解決策です。
Pzanno

よくわかりません。通常行うように、onCreate()でリスナーを設定する代わりに、run()でそれらを設定できます。ここで何か不足していますか?
Code Poet

7

私は他の場所でまだ見たことがないこの解決策を発見しました。バックグラウンドタスクが実行されているかどうかを判断するカスタムアプリケーションオブジェクトを使用して、方向を変更したときに破棄されて再作成されるアクティビティでこれを実行する代わりに使用できます。私はここでこれについてブログを書い


1
カスタムの作成Applicationは通常、グローバルなアプリケーションの状態を維持するために使用されます。うまくいかないとは言いませんが、複雑すぎるようです。ドキュメントから「通常、アプリケーションをサブクラス化する必要はありません。」私は主にsonxurxoの答えを好みます。
rds

7

私は、このローテーション問題を処理するための私のアプローチに貢献します。彼はを使用AsyncTaskしていないため、これはOPには関係ないかもしれませんが、多分他の人がそれを便利だと思うでしょう。とてもシンプルですが、私にとってはうまくいくようです:

AsyncTask呼ばれるネストされたクラスを持つログインアクティビティがありますBackgroundLoginTask

私でBackgroundLoginTaskは、ProgressDialogのdismiss を呼び出すときにnullチェックを追加する以外は、通常のことをしていません。

@Override
protected void onPostExecute(Boolean result)
{    
if (pleaseWaitDialog != null)
            pleaseWaitDialog.dismiss();
[...]
}

これはActivity、が表示されていないときにバックグラウンドタスクが完了し、そのため進行状況ダイアログがonPause()メソッド。

次に、親Activityクラスで、AsyncTaskクラスとmyにグローバルな静的ハンドルを作成しますProgressDialogAsyncTask入れ子になっているはこれらの変数にアクセスできます)。

private static BackgroundLoginTask backgroundLoginTask;
private static ProgressDialog pleaseWaitDialog;

これには2つの目的があります。1つ目は、回転後の新しいアクティビティからでも、Activity常にAsyncTaskオブジェクトにアクセスできるようにすることです。第二に、それは私BackgroundLoginTaskProgressDialog回転した後でもアクセスして却下できるようにします。

次に、これをに追加してonPause()Activityフォアグラウンドを離れるときに進行ダイアログを非表示にします(醜い「強制終了」クラッシュを防止します)。

    if (pleaseWaitDialog != null)
    pleaseWaitDialog.dismiss();

最後に、私は私のonResume()メソッドに以下を持っています:

if ((backgroundLoginTask != null) && (backgroundLoginTask.getStatus() == Status.RUNNING))
        {
           if (pleaseWaitDialog != null)
             pleaseWaitDialog.show();
        }

これにより、Dialogが再Activity作成された後にが再び表示されます。

ここにクラス全体があります:

public class NSFkioskLoginActivity extends NSFkioskBaseActivity {
    private static BackgroundLoginTask backgroundLoginTask;
    private static ProgressDialog pleaseWaitDialog;
    private Controller cont;

    // This is the app entry point.
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        if (CredentialsAvailableAndValidated())
        {
        //Go to main menu and don't run rest of onCreate method.
            gotoMainMenu();
            return;
        }
        setContentView(R.layout.login);
        populateStoredCredentials();   
    }

    //Save current progress to options when app is leaving foreground
    @Override
    public void onPause()
    {
        super.onPause();
        saveCredentialsToPreferences(false);
        //Get rid of progress dialog in the event of a screen rotation. Prevents a crash.
        if (pleaseWaitDialog != null)
        pleaseWaitDialog.dismiss();
    }

    @Override
    public void onResume()
    {
        super.onResume();
        if ((backgroundLoginTask != null) && (backgroundLoginTask.getStatus() == Status.RUNNING))
        {
           if (pleaseWaitDialog != null)
             pleaseWaitDialog.show();
        }
    }

    /**
     * Go to main menu, finishing this activity
     */
    private void gotoMainMenu()
    {
        startActivity(new Intent(getApplicationContext(), NSFkioskMainMenuActivity.class));
        finish();
    }

    /**
     * 
     * @param setValidatedBooleanTrue If set true, method will set CREDS_HAVE_BEEN_VALIDATED to true in addition to saving username/password.
     */
    private void saveCredentialsToPreferences(boolean setValidatedBooleanTrue)
    {
        SharedPreferences settings = getSharedPreferences(APP_PREFERENCES, MODE_PRIVATE);
        SharedPreferences.Editor prefEditor = settings.edit();
        EditText usernameText = (EditText) findViewById(R.id.editTextUsername);
        EditText pswText = (EditText) findViewById(R.id.editTextPassword);
        prefEditor.putString(USERNAME, usernameText.getText().toString());
        prefEditor.putString(PASSWORD, pswText.getText().toString());
        if (setValidatedBooleanTrue)
        prefEditor.putBoolean(CREDS_HAVE_BEEN_VALIDATED, true);
        prefEditor.commit();
    }

    /**
     * Checks if user is already signed in
     */
    private boolean CredentialsAvailableAndValidated() {
        SharedPreferences settings = getSharedPreferences(APP_PREFERENCES,
                MODE_PRIVATE);
        if (settings.contains(USERNAME) && settings.contains(PASSWORD) && settings.getBoolean(CREDS_HAVE_BEEN_VALIDATED, false) == true)
         return true;   
        else
        return false;
    }

    //Populate stored credentials, if any available
    private void populateStoredCredentials()
    {
        SharedPreferences settings = getSharedPreferences(APP_PREFERENCES,
            MODE_PRIVATE);
        settings.getString(USERNAME, "");
       EditText usernameText = (EditText) findViewById(R.id.editTextUsername);
       usernameText.setText(settings.getString(USERNAME, ""));
       EditText pswText = (EditText) findViewById(R.id.editTextPassword);
       pswText.setText(settings.getString(PASSWORD, ""));
    }

    /**
     * Validate credentials in a seperate thread, displaying a progress circle in the meantime
     * If successful, save credentials in preferences and proceed to main menu activity
     * If not, display an error message
     */
    public void loginButtonClick(View view)
    {
        if (phoneIsOnline())
        {
        EditText usernameText = (EditText) findViewById(R.id.editTextUsername);
        EditText pswText = (EditText) findViewById(R.id.editTextPassword);
           //Call background task worker with username and password params
           backgroundLoginTask = new BackgroundLoginTask();
           backgroundLoginTask.execute(usernameText.getText().toString(), pswText.getText().toString());
        }
        else
        {
        //Display toast informing of no internet access
        String notOnlineMessage = getResources().getString(R.string.noNetworkAccessAvailable);
        Toast toast = Toast.makeText(getApplicationContext(), notOnlineMessage, Toast.LENGTH_SHORT);
        toast.show();
        }
    }

    /**
     * 
     * Takes two params: username and password
     *
     */
    public class BackgroundLoginTask extends AsyncTask<Object, String, Boolean>
    {       
       private Exception e = null;

       @Override
       protected void onPreExecute()
       {
           cont = Controller.getInstance();
           //Show progress dialog
           String pleaseWait = getResources().getString(R.string.pleaseWait);
           String commWithServer = getResources().getString(R.string.communicatingWithServer);
            if (pleaseWaitDialog == null)
              pleaseWaitDialog= ProgressDialog.show(NSFkioskLoginActivity.this, pleaseWait, commWithServer, true);

       }

        @Override
        protected Boolean doInBackground(Object... params)
        {
        try {
            //Returns true if credentials were valid. False if not. Exception if server could not be reached.
            return cont.validateCredentials((String)params[0], (String)params[1]);
        } catch (Exception e) {
            this.e=e;
            return false;
        }
        }

        /**
         * result is passed from doInBackground. Indicates whether credentials were validated.
         */
        @Override
        protected void onPostExecute(Boolean result)
        {
        //Hide progress dialog and handle exceptions
        //Progress dialog may be null if rotation has been switched
        if (pleaseWaitDialog != null)
             {
            pleaseWaitDialog.dismiss();
                pleaseWaitDialog = null;
             }

        if (e != null)
        {
         //Show toast with exception text
                String networkError = getResources().getString(R.string.serverErrorException);
                Toast toast = Toast.makeText(getApplicationContext(), networkError, Toast.LENGTH_SHORT);
            toast.show();
        }
        else
        {
            if (result == true)
            {
            saveCredentialsToPreferences(true);
            gotoMainMenu();
            }
            else
            {
            String toastText = getResources().getString(R.string.invalidCredentialsEntered);
                Toast toast = Toast.makeText(getApplicationContext(), toastText, Toast.LENGTH_SHORT);
            toast.show();
            } 
        }
        }

    }
}

私は決してベテランのAndroid開発者ではないので、気軽にコメントしてください。


1
面白い!特にAsyncTaskを使用している私たちにとっては。ちょうどあなたの解決策を試しました、そしてそれはほとんどうまくいくようです。1つの問題があります。ProgressDialogがまだアクティブな間、ProgressDialogはローテーションの少し前に終了するようです。私は何が起こっているのか、そしてそれをどのように修正するのかを正確に確認するために遊んでいきます。しかし、私はそれらのクラッシュをもう受けていません!
Scott Biggs

1
修正を見つけました。ここでの問題は静的なProgressDialogのようです。回転によってProgressDialogが中断されると、新しいActivityで再起動された後、.dismiss()メソッドが呼び出されることがあります。各アクティビティで作成されたProgressDialogを作成することにより、この新しいProgressDialogが古いアクティビティと一緒に強制終了されないようにします。また、(ガベージコレクションを支援するために)ProgressDialogが閉じられると必ずnullに設定されるようにしました。ここに解決策があります!AsyncTaskを使用している人に乾杯!
スコットビッグス

4

長いタスクを別のクラスに移動します。サブジェクトオブザーバーパターンとして実装します。アクティビティが作成されるたびに登録し、閉じるときにタスククラスへの登録を解除します。タスククラスはAsyncTaskを使用できます。


1
それがどのように役立つかわかりません。これにより、私が目にしている問題をどのように防ぐことができるかを詳しく説明していただけますか。
Heikki Toivonen

1
HasemanがバックエンドがUI要素にアクセスすることを防ぎ、UIをバックエンドから分離できると述べたように、バックエンドは別のスレッドで実行され、画面が再配向され、ステータスの更新のためにバックエンドタスクでRegister-UnRegisterした後も引き続き実行されます。これを使用して解決した実際の例は、ダウンロードタスクがあり、それを別のスレッドに移動したことです。スレッドが作成されるたびに、登録と登録解除を行います。
Vinay、

わかりました、私はこの問題を再検討しています、そして私はまだこの答えを完全に理解しているとは思いません。メインアクティビティでAsyncTaskを開始して、画面の向きの変更中に中断したくない長時間のネットワーク操作を実行するとします。新しいアクティビティが、古いアクティビティによって開始されたAsyncTaskにメッセージを送信する方法がわかりません。コード例を教えてください。
Heikki Toivonen

@Heikki、私の実装はあなたが何を意味しているのですか?
ビートラ

4

トリックは、通常どおりonPreExecute / onPostExecute中にAsyncTask内でダイアログを表示/閉じることですが、方向変更の場合、アクティビティでダイアログの新しいインスタンスを作成/表示し、その参照をタスクに渡します。

public class MainActivity extends Activity {
    private Button mButton;
    private MyTask mTask = null;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        MyTask task = (MyTask) getLastNonConfigurationInstance();
        if(task != null){
            mTask = task;
            mTask.mContext = this;
            mTask.mDialog = ProgressDialog.show(this, "", "", true);        
        }

        mButton = (Button) findViewById(R.id.button1);
        mButton.setOnClickListener(new View.OnClickListener(){
            public void onClick(View v){
                mTask = new MyTask(MainActivity.this);
                mTask.execute();
            }
        });
    }


    @Override
    public Object onRetainNonConfigurationInstance() {
        String str = "null";
        if(mTask != null){
            str = mTask.toString();
            mTask.mDialog.dismiss();
        }
        Toast.makeText(this, str, Toast.LENGTH_SHORT).show();
        return mTask;
    }



    private class MyTask extends AsyncTask<Void, Void, Void>{
        private ProgressDialog mDialog;
        private MainActivity mContext;


        public MyTask(MainActivity context){
            super();
            mContext = context;
        }


        protected void onPreExecute() {
            mDialog = ProgressDialog.show(MainActivity.this, "", "", true);
        }

        protected void onPostExecute(Void result) {
            mContext.mTask = null;
            mDialog.dismiss();
        }


        @Override
        protected Void doInBackground(Void... params) {
            SystemClock.sleep(5000);
            return null;
        }       
    }
}

4

私はそれを次のようにしています:

    package com.palewar;
    import android.app.Activity;
    import android.app.ProgressDialog;
    import android.os.Bundle;
    import android.os.Handler;
    import android.os.Message;

    public class ThreadActivity extends Activity {


        static ProgressDialog dialog;
        private Thread downloadThread;
        final static Handler handler = new Handler() {

            @Override
            public void handleMessage(Message msg) {

                super.handleMessage(msg);

                dialog.dismiss();

            }

        };

        protected void onDestroy() {
    super.onDestroy();
            if (dialog != null && dialog.isShowing()) {
                dialog.dismiss();
                dialog = null;
            }

        }

        /** Called when the activity is first created. */
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.main);

            downloadThread = (Thread) getLastNonConfigurationInstance();
            if (downloadThread != null && downloadThread.isAlive()) {
                dialog = ProgressDialog.show(ThreadActivity.this, "",
                        "Signing in...", false);
            }

            dialog = ProgressDialog.show(ThreadActivity.this, "",
                    "Signing in ...", false);

            downloadThread = new MyThread();
            downloadThread.start();
            // processThread();
        }

        // Save the thread
        @Override
        public Object onRetainNonConfigurationInstance() {
            return downloadThread;
        }


        static public class MyThread extends Thread {
            @Override
            public void run() {

                try {
                    // Simulate a slow network
                    try {
                        new Thread().sleep(5000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    handler.sendEmptyMessage(0);

                } finally {

                }
            }
        }

    }

試してみて、うまくいくかどうかを教えてください


onDestroyコードは、開発者のページからはまったく実行されない可能性があります「上記の表の「強制終了可能」列に注意して
ilomambo

私は「onRetainNonConfigurationInstance()」がそのような場合に使用されるメソッドであると固く信じています... nyc work
Nitin Bansal

Sachin Gurnaniは静的宣言を使用して問題を解決しているため、同様の問題に直面しています。stackoverflow.com/questions/12058774/…–
Steven Du

2

Service面倒な作業(tcp要求/応答、非整列化)をすべて行う背景を作成する場合、ViewおよびActivityを破棄して、ウィンドウのリークやデータの損失なしに再作成できます。これにより、Androidの推奨動作である、構成の変更ごと(方向の変更ごとなど)にアクティビティ破棄することができます

少し複雑ですが、サーバーリクエストやデータの前処理や後処理などを呼び出すための最良の方法です。

を使用してService各リクエストをサーバーにキューイングすることもできるため、これらの処理を簡単かつ効率的に行うことができます。

開発ガイドには、に関する完全な章がありServicesます。


A Serviceはより作業量が多くAsyncTaskなりますが、状況によってはより良いアプローチになる場合があります。必ずしも良いとは限りませんね。そうは言っても、これがProgressDialogメインからリークされる問題をどのように解決するのか理解できませんActivity。どこにインスタンス化しProgressDialogますか?どこで却下しますか?
rds

2

画面の向きが変わったときにアクティビティを破棄できるように実装していますが、再作成されたアクティビティのダイアログは正常に破棄されます。...NonConfigurationInstance再作成したアクティビティにバックグラウンドタスクを添付するために使用します。通常のAndroidフレームワークはダイアログ自体の再作成を処理し、そこで何も変更されません。

「所有」アクティビティのフィールドを追加するAsyncTaskと、この所有者を更新するメソッドをサブクラス化しました。

class MyBackgroundTask extends AsyncTask<...> {
  MyBackgroundTask (Activity a, ...) {
    super();
    this.ownerActivity = a;
  }

  public void attach(Activity a) {
    ownerActivity = a;
  }

  protected void onPostExecute(Integer result) {
    super.onPostExecute(result);
    ownerActivity.dismissDialog(DIALOG_PROGRESS);
  }

  ...
}

私のアクティビティクラスではbackgroundTask、「所有」バックグラウンドタスクを参照するフィールドを追加し、およびを使用onRetainNonConfigurationInstanceしてこのフィールドを更新しましたgetLastNonConfigurationInstance

class MyActivity extends Activity {
  public void onCreate(Bundle savedInstanceState) {
    ...
    if (getLastNonConfigurationInstance() != null) {
      backgroundTask = (MyBackgroundTask) getLastNonConfigurationInstance();
      backgroundTask.attach(this);
    }
  }

  void startBackgroundTask() {
    backgroundTask = new MyBackgroundTask(this, ...);
    showDialog(DIALOG_PROGRESS);
    backgroundTask.execute(...);
  }

  public Object onRetainNonConfigurationInstance() {
    if (backgroundTask != null && backgroundTask.getStatus() != Status.FINISHED)
      return backgroundTask;
    return null;
  }
  ...
}

さらなる改善のための提案:

  • backgroundTaskタスクが完了したら、アクティビティ内の参照をクリアして、それに関連付けられているメモリやその他のリソースを解放します。
  • ownerActivityすぐに再作成されない場合に備えて、アクティビティが破棄される前に、バックグラウンドタスクの参照をクリアします。
  • BackgroundTaskインターフェースやコレクションを作成して、異なるタイプのタスクを同じ所有アクティビティから実行できるようにします。

2

2つのレイアウトを維持する場合は、すべてのUIスレッドを終了する必要があります。

AsynTaskを使用すると、現在のアクティビティの.cancel()メソッド内のonDestroy()メソッドを簡単に呼び出すことができます。

@Override
protected void onDestroy (){
    removeDialog(DIALOG_LOGIN_ID); // remove loading dialog
    if (loginTask != null){
        if (loginTask.getStatus() != AsyncTask.Status.FINISHED)
            loginTask.cancel(true); //cancel AsyncTask
    }
    super.onDestroy();
}

AsyncTaskについては、こちらの「タスクのキャンセル」セクションをご覧ください

更新: 実行状態にある場合にのみキャンセルできるため、ステータスをチェックする条件が追加されました。また、AsyncTaskは1回しか実行できないことに注意してください。


2

jfelectronのソリューションは、「Androidの方法」に準拠したこれらの問題に対する強固なソリューション」であるため、実装しようとしました言及されているすべての要素を調べてまとめるのに時間がかかりました。このわずかに異なる結果に終わった、そして私はよりエレガントな、全体的にここに投稿されたソリューションを考える。

アクティビティから起動されたIntentServiceを使用して、別のスレッドで長時間実行タスクを実行します。サービスは、ダイアログを更新するアクティビティにスティッキーブロードキャストインテントを起動します。アクティビティはshowDialog()、onCreateDialog()、onPrepareDialog()を使用して、アプリケーションオブジェクトまたはsavedInstanceStateバンドルで永続データを渡す必要をなくします。これは、アプリケーションがどのように中断されても機能するはずです。

アクティビティクラス:

public class TesterActivity extends Activity {
private ProgressDialog mProgressDialog;
private static final int PROGRESS_DIALOG = 0;

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    Button b = (Button) this.findViewById(R.id.test_button);
    b.setOnClickListener(new OnClickListener() {
        public void onClick(View v) {
            buttonClick();
        }
    });
}

private void buttonClick(){
    clearPriorBroadcast();
    showDialog(PROGRESS_DIALOG);
    Intent svc = new Intent(this, MyService.class);
    startService(svc);
}

protected Dialog onCreateDialog(int id) {
    switch(id) {
    case PROGRESS_DIALOG:
        mProgressDialog = new ProgressDialog(TesterActivity.this);
        mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
        mProgressDialog.setMax(MyService.MAX_COUNTER);
        mProgressDialog.setMessage("Processing...");
        return mProgressDialog;
    default:
        return null;
    }
}

@Override
protected void onPrepareDialog(int id, Dialog dialog) {
    switch(id) {
    case PROGRESS_DIALOG:
        // setup a broadcast receiver to receive update events from the long running process
        IntentFilter filter = new IntentFilter();
        filter.addAction(MyService.BG_PROCESS_INTENT);
        registerReceiver(new MyBroadcastReceiver(), filter);
        break;
    }
}

public class MyBroadcastReceiver extends BroadcastReceiver{
    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent.hasExtra(MyService.KEY_COUNTER)){
            int count = intent.getIntExtra(MyService.KEY_COUNTER, 0);
            mProgressDialog.setProgress(count);
            if (count >= MyService.MAX_COUNTER){
                dismissDialog(PROGRESS_DIALOG);
            }
        }
    }
}

/*
 * Sticky broadcasts persist and any prior broadcast will trigger in the 
 * broadcast receiver as soon as it is registered.
 * To clear any prior broadcast this code sends a blank broadcast to clear 
 * the last sticky broadcast.
 * This broadcast has no extras it will be ignored in the broadcast receiver 
 * setup in onPrepareDialog()
 */
private void clearPriorBroadcast(){
    Intent broadcastIntent = new Intent();
    broadcastIntent.setAction(MyService.BG_PROCESS_INTENT);
    sendStickyBroadcast(broadcastIntent);
}}

IntentServiceクラス:

public class MyService extends IntentService {

public static final String BG_PROCESS_INTENT = "com.mindspiker.Tester.MyService.TEST";
public static final String KEY_COUNTER = "counter";
public static final int MAX_COUNTER = 100;

public MyService() {
  super("");
}

@Override
protected void onHandleIntent(Intent intent) {
    for (int i = 0; i <= MAX_COUNTER; i++) {
        Log.e("Service Example", " " + i);
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        Intent broadcastIntent = new Intent();
        broadcastIntent.setAction(BG_PROCESS_INTENT);
        broadcastIntent.putExtra(KEY_COUNTER, i);
        sendStickyBroadcast(broadcastIntent);
    }
}}

マニフェストファイルエントリ:

アプリケーションセクションの前:

uses-permission android:name="com.mindspiker.Tester.MyService.TEST"
uses-permission android:name="android.permission.BROADCAST_STICKY"

アプリケーションセクション内

service android:name=".MyService"

2

これは私の提案する解決策です:

  • ここで説明するように、AsyncTaskまたはスレッドを保持されているフラグメントに移動します。すべてのネットワークコールをフラグメントに移動することをお勧めします。すでにフラグメントを使用している場合は、フラグメントの1つが呼び出しの責任を負うようにすることができます。それ以外の場合は、リンクされた記事で提案されているように、リクエストを実行するためだけにフラグメントを作成できます。
  • フラグメントはリスナーインターフェイスを使用して、タスクの完了/失敗を通知します。そこで方向が変わることを心配する必要はありません。フラグメントには常に現在のアクティビティへの正しいリンクがあり、進行ダイアログを安全に再開できます。
  • 進捗ダイアログをクラスのメンバーにします。実際、すべてのダイアログでこれを行う必要があります。onPauseメソッドでは、それらを閉じる必要があります。そうしないと、構成変更時にウィンドウがリークします。ビジー状態は、フラグメントによって保持される必要があります。フラグメントがアクティビティに関連付けられている場合、呼び出しがまだ実行されていれば、進行ダイアログを再び表示できます。void showProgressDialog()この方法は、この目的のために断片活性リスナーインターフェースに追加することができます。

完璧な解決策ですが、この答えが他の人から隠されている理由がわかりません!!!
ブラックカラ2017

2

私も同じ状況に直面しました。私がしたことは、アプリケーション全体で、進行状況ダイアログのインスタンスを1つだけ取得することでした。

最初に、1つのインスタンスのみを取得するDialogSingletonクラスを作成しました(シングルトンパターン)

public class DialogSingleton
{
    private static Dialog dialog;

    private static final Object mLock = new Object();
    private static DialogSingleton instance;

    private DialogSingleton()
    {

    }

    public static DialogSingleton GetInstance()
    {
        synchronized (mLock)
        {
            if(instance == null)
            {
                instance = new DialogSingleton();
            }

            return instance;
        }
    }

    public void DialogShow(Context context, String title)
    {
        if(!((Activity)context).isFinishing())
        {
            dialog = new ProgressDialog(context, 2);

            dialog.setCanceledOnTouchOutside(false);

            dialog.setTitle(title);

            dialog.show();
        }
    }

    public void DialogDismiss(Context context)
    {
        if(!((Activity)context).isFinishing() && dialog.isShowing())
        {
            dialog.dismiss();
        }
    }
}

このクラスで示すように、属性として進行状況ダイアログがあります。進捗ダイアログを表示する必要があるたびに、一意のインスタンスを取得して新しいProgressDialogを作成します。

DialogSingleton.GetInstance().DialogShow(this, "My title here!");

バックグラウンドタスクが完了したら、一意のインスタンスを再度呼び出し、そのダイアログを閉じます。

DialogSingleton.GetInstance().DialogDismiss(this);

共有設定にバックグラウンドタスクのステータスを保存します。画面を回転させると、このアクティビティに対して実行中のタスクがあるかどうか尋ねます:(onCreate)

if(Boolean.parseBoolean(preference.GetValue(IS_TASK_NAME_EXECUTED_KEY, "boolean").toString()))
{
    DialogSingleton.GetInstance().DialogShow(this, "Checking credentials!");
} // preference object gets the info from shared preferences (my own implementation to get and put data to shared preferences) and IS_TASK_NAME_EXECUTED_KEY is the key to save this flag (flag to know if this activity has a background task already running).

バックグラウンドタスクの実行を開始すると:

preference.AddValue(IS_TASK_NAME_EXECUTED_KEY, true, "boolean");

DialogSingleton.GetInstance().DialogShow(this, "My title here!");

バックグラウンドタスクの実行が終了したら:

preference.AddValue(IS_TASK_NAME_EXECUTED_KEY, false, "boolean");

DialogSingleton.GetInstance().DialogDismiss(ActivityName.this);

お役に立てば幸いです。


2

これは、何らかの理由でサイドバーに出てきた非常に古い質問です。

アクティビティがフォアグラウンドにあるときにバックグラウンドタスクのみが存続する必要がある場合、「新しい」ソリューションは、この開発者ガイド多数のQ&Aで説明されているようにAsyncTask保持されたフラグメントでバックグラウンドスレッド(または、できれば)をホストすることです。

保持されているフラグメントは、構成変更のためにアクティビティが破棄された場合は存続しますが、バックグラウンドまたはバックスタックでアクティビティが破棄された場合は無効になります。したがって、isChangingConfigurations()でfalseの場合、バックグラウンドタスクは引き続き中断されonPause()ます。


2

私はAndroidの方が新しいので、これを試してみましたが、うまくいきました。

public class loadTotalMemberByBranch extends AsyncTask<Void, Void,Void> {
        ProgressDialog progressDialog = new ProgressDialog(Login.this);
        int ranSucess=0;
        @Override
        protected void onPreExecute() {
            // TODO Auto-generated method stub
            super.onPreExecute();
            progressDialog.setTitle("");    
            progressDialog.isIndeterminate();
            progressDialog.setCancelable(false);
            progressDialog.show();
            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR);

        }
        @Override
        protected Void doInBackground(Void... params) {
            // TODO Auto-generated method stub

            return null;
        }
        @Override
        protected void onPostExecute(Void result) {
            // TODO Auto-generated method stub
            super.onPostExecute(result);
            progressDialog.dismiss();
            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR);
        }
}

1

私はすべてを試しました。実験に費やした日々。アクティビティの回転をブロックしたくありませんでした。私のシナリオは:

  1. ユーザーに動的な情報を表示する進捗ダイアログ。例:「サーバーに接続しています...」、「データをダウンロードしています...」など。
  2. 重いものを実行してダイアログを更新するスレッド
  3. 最後に結果でUIを更新します。

問題は、画面を回転させたときに、本のすべての解決策が失敗したことでした。この状況に対処する正しいAndroidの方法であるAsyncTaskクラスを使用しても。画面を回転させると、開始スレッドが使用している現在のコンテキストがなくなり、表示されているダイアログが混乱します。問題は、コードに追加したトリック(実行中のスレッドに新しいコンテキストを渡す、ローテーションを通じてスレッドの状態を保持するなど)に関係なく、常にダイアログです。最後のコードの複雑さは常に非常に大きく、問題が発生する可能性が常にありました。

私にとってうまくいった唯一の解決策は、アクティビティ/ダイアログのトリックでした。それはシンプルで天才であり、すべての回転証明です:

  1. ダイアログを作成して表示する代わりに、android:theme = "@ android:style / Theme.Dialog"を使用してマニフェストに設定されているアクティビティを作成します。つまり、ダイアログのように見えます。

  2. showDialog(DIALOG_ID)をstartActivityForResult(yourActivityDialog、yourCode);に置き換えます。

  3. 呼び出しアクティビティでonActivityResultを使用して、実行中のスレッドから結果(エラーも含む)を取得し、UIを更新します。

  4. 'ActivityDialog'で、スレッドまたはAsyncTaskを使用して長いタスクを実行し、onRetainNonConfigurationInstanceを使用して画面を回転するときに「ダイアログ」状態を保存します。

これは高速で正常に動作します。私はまだ他のタスクのダイアログを使用し、画面に一定のダイアログを必要としないものにはAsyncTaskを使用しています。ただし、このシナリオでは、常にアクティビティ/ダイアログパターンを使用します。

そして、私はそれを試しませんでしたが、スレッドが実行されているときに、アクティビティ/ダイアログの回転をブロックして、呼び出しアクティビティを回転させながら高速化することもできます。


パラメータがを介して渡される必要があることを除いて素晴らしいIntent。これは、Object許可されている制限よりも制限が厳しいAsyncTask
rds

@Rui昨年もこの方法を使った。これも間違っているのですが、今、私には思いつきました。これがこの問題を「修正」する方法である場合、Googleがダイアログを使用するのはなぜですか?私が目にする問題は、ActivityAからActivityB(Theme.Dialog)を開くと、ActivityAがアクティビティスタックで下に移動されるため、必要に応じてOSによってkillする準備ができているとマークされます。したがって、実行中のプロセスが長く、ある種の偽の進行状況「ダイアログ」を表示していて、時間がかかりすぎてメモリが不足している場合... ActivityAが強制終了され、進行状況の完了時に戻るものはありません。
rf43 '10 / 10/13

1

最近では、これらのタイプの問題を処理するためのより明確な方法があります。典型的なアプローチは次のとおりです。

1.データがUIから適切に分離されていることを確認します。

バックグラウンドプロセスであるものはすべて保持されている必要がありますFragment(これをで設定しFragment.setRetainInstance()ます。これは、保持する必要のあるすべてのデータが保持される「永続的なデータストレージ」になります。方向変更イベントの後、これFragmentは元の状態で引き続きアクセスできます。FragmentManager.findFragmentByTag()呼び出しを介して状態を作成します(作成するときには、IDでなくタグを付ける必要がありViewます。

これを正しく行う方法と、これが最適なオプションである理由については、ランタイム変更処理ガイドを参照してください。

2.バックグラウンドプロセスとUIを正しく安全に接続していることを確認します。

リンクプロセスを逆にする必要があります。現時点では、バックグラウンドプロセスはそれ自体をaにアタッチしViewます。代わりViewに、バックグラウンドプロセスにそれ自体をアタッチする必要があります。それはもっと理にかなっていますか?Viewバックグラウンド・プロセスがに依存しないのに対し、アクションは、バックグラウンドプロセスに依存しているView標準へのリンクを変更.This手段Listenerインターフェイス。(それが何であれ、クラス-それはであるかどうか、あなたのプロセスを言うAsyncTaskRunnableまたは何でも)を定義OnProcessFinishedListenerプロセスは、それが存在する場合、それはそのリスナーを呼び出す必要が行われたときに、。

この回答は、カスタムリスナーの実行方法を簡潔に説明したものです。

3. UIが作成されるたびに(向きの変更を含む)、UIをデータプロセスにリンクします。

次に、現在のView構造が何であれ、バックグラウンドタスクとのインターフェースについて心配する必要があります。向きの変更を適切に処理している場合configChanges人々が常に推奨するハックではありません)、Dialogシステムによって再作成されます。これは重要です。つまり、向きを変更すると、のすべてDialogのライフサイクルメソッドが呼び出されます。したがって、これらのメソッドのいずれでも(onCreateDialog通常は適切な場所です)、次のような呼び出しを行うことができます。

DataFragment f = getActivity().getFragmentManager().findFragmentByTag("BACKGROUND_TAG");
if (f != null) {
    f.mBackgroundProcess.setOnProcessFinishedListener(new OnProcessFinishedListener() {
        public void onProcessFinished() {
            dismiss();
        }
    });
 }

リスナーの設定が個々の実装に最適な場所を決定するには、フラグメントのライフサイクルを参照してください。

これは、この質問で尋ねられた一般的な問題に対する堅牢で完全なソリューションを提供するための一般的なアプローチです。個々のシナリオによっては、この回答にはいくつかのマイナーな部分が欠けている可能性がありますが、これは通常、方向変更イベントを適切に処理するための最も適切なアプローチです。


1

向きが変わったときにスレッドを処理する簡単な解決策を見つけました。アクティビティ/フラグメントへの静的参照を保持し、UIに作用する前にnullかどうかを確認できます。私もトライキャッチを使用することをお勧めします:

 public class DashListFragment extends Fragment {
     private static DashListFragment ACTIVE_INSTANCE;

     @Override
     public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        ACTIVE_INSTANCE = this;

        new Handler().postDelayed(new Runnable() {
            public void run() {
                try {
                        if (ACTIVE_INSTANCE != null) {
                            setAdapter(); // this method do something on ui or use context
                        }
                }
                catch (Exception e) {}


            }
        }, 1500l);

    }

    @Override
    public void onDestroy() {
        super.onDestroy();

        ACTIVE_INSTANCE = null;
    }


}

1

あなたは、ダイアログの検出姿勢変更イベントで苦労している場合AN活性対照とは無関係に、この方法は、エキサイティングなよく働きます。私が使用しているのは、複数の異なるアクティビティで表示できる独自のダイアログクラスがあり、どのアクティビティが表示されているかが常にわからないためです。この方法では、AndroidManifestを変更する必要がなく、アクティビティの参照について心配します。 (私が持っているように)カスタムダイアログは必要ありません。ただし、特定のビューを使用して方向の変更を検出できるように、カスタムコンテンツビューが必要です。これが私の例です:

セットアップ

public class MyContentView extends View{
    public MyContentView(Context context){
        super(context);
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig){
        super.onConfigurationChanged(newConfig);

        //DO SOMETHING HERE!! :D
    }
}

実装1-ダイアログ

Dialog dialog = new Dialog(context);
//set up dialog
dialog.setContentView(new MyContentView(context));
dialog.show();

実装2-AlertDialog.Builder

AlertDialog.Builder builder = new AlertDialog.Builder(context);
//set up dialog builder
builder.setView(new MyContentView(context));        //Can use this method
builder.setCustomTitle(new MycontentView(context)); // or this method
builder.build().show();

実装3-ProgressDialog / AlertDialog

ProgressDialog progress = new ProgressDialog(context);
//set up progress dialog
progress.setView(new MyContentView(context));        //Can use this method
progress.setCustomTitle(new MyContentView(context)); // or this method
progress.show();

1

これは私が直面したときの私の解決策です: ProgressDialogFragment子ではないので、カスタムクラス " ProgressDialogFragment"をDialogFragment代わりに拡張して、構成の変更に対してダイアログを表示したままにすることができます。

import androidx.annotation.NonNull;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.os.Bundle; 
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.FragmentManager;

 /**
 * Usage:
 * To display the dialog:
 *     >>> ProgressDialogFragment.showProgressDialogFragment(
 *              getSupportFragmentManager(), 
 *              "fragment_tag", 
 *              "my dialog title", 
 *              "my dialog message");
 *              
 * To hide the dialog
 *     >>> ProgressDialogFragment.hideProgressDialogFragment();
 */ 


public class ProgressDialogFragment extends DialogFragment {

    private static String sTitle, sMessage;
    private static ProgressDialogFragment sProgressDialogFragment;

    public ProgressDialogFragment() {
    }

    private ProgressDialogFragment(String title, String message) {
        sTitle = title;
        sMessage = message;
    }


    @NonNull
    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        return ProgressDialog.show(getActivity(), sTitle, sMessage);
    }

    public static void showProgressDialogFragment(FragmentManager fragmentManager, String fragmentTag, String title, String message) {
        if (sProgressDialogFragment == null) {
            sProgressDialogFragment = new ProgressDialogFragment(title, message);
            sProgressDialogFragment.show(fragmentManager, fragmentTag);

        } else { // case of config change (device rotation)
            sProgressDialogFragment = (ProgressDialogFragment) fragmentManager.findFragmentByTag(fragmentTag); // sProgressDialogFragment will try to survive its state on configuration as much as it can, but when calling .dismiss() it returns NPE, so we have to reset it on each config change
            sTitle = title;
            sMessage = message;
        }

    }

    public static void hideProgressDialogFragment() {
        if (sProgressDialogFragment != null) {
            sProgressDialogFragment.dismiss();
        }
    }
}

課題は、ダイアログが表示されていても、画面の回転中にダイアログのタイトルとメッセージをデフォルトの空の文字列にリセットするときに保持することでした。

これを解決するには2つの方法があります。

最初のアプローチ: マニフェストファイルの構成変更中に状態を保持するためにダイアログを利用するアクティビティを作成します。

android:configChanges="orientation|screenSize|keyboardHidden"

このアプローチはGoogleでは推奨されていません。

2番目のアプローチ: アクティビティのonCreate()メソッドで、がnullでない場合、次のようにタイトルとメッセージを使用してDialogFragmentを再構築することにより、を保持する必要があります。ProgressDialogFragmentsavedInstanceState

@Override
protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_deal);

 if (savedInstanceState != null) {
      ProgressDialogFragment saveProgressDialog = (ProgressDialogFragment) getSupportFragmentManager()
              .findFragmentByTag("fragment_tag");
      if (saveProgressDialog != null) {
          showProgressDialogFragment(getSupportFragmentManager(), "fragment_tag", "my dialog title", "my dialog message");
      }
  }
}

0

「速すぎて汚い」とは言えないので、欠陥を指摘してください。

AsyncTaskのonPostExecuteメソッド内で、進行状況ダイアログの '.dismiss'をtry / catchブロック(空のcatchで)にラップし、発生した例外を単に無視しました。行うのは間違っているようですが、悪影響はないようです(少なくとも、後で実行している処理で、長時間実行されているクエリの結果をExtraとして渡して別のアクティビティを開始する場合)。


Androidプラットフォームがウィンドウのリークを通知したときに、実際にはメモリリークはないと言っていますか?
rds

私の進行状況ダイアログはアクティビティクラスのメンバー変数なので、アクティビティが破棄されて再作成されるとガベージコレクションされ、リークは発生しないと想定しました。私が間違っている?
Simon

はい、私はそれが間違っていると思います。あなたが言うように、Activityはへの参照を持っていますDialog。設定が変更されると、最初の設定Activityは破棄されnullます。つまり、すべてのフィールドがに設定されます。ただし、低レベルWindowManagerには、Dialog(まだ却下されていないため)への参照もあります。新しいActivityは新しいDialog(でpreExecute())を作成しようとしますが、ウィンドウマネージャーは致命的な例外を発生させ、作成を禁止します。実際、そうした場合、Dialogそれをきれいに破棄する方法はないので、初期への参照が維持されActivityます。私は正しいですか?
rds

0

最も単純で最も柔軟なソリューションは、ProgressBarへの静的参照でAsyncTaskを使用することです。これにより、カプセル化された再利用可能な解決策が方向転換の問題に提供されます。このソリューションは、インターネットのダウンロード、サービスとの通信、ファイルシステムのスキャンなど、さまざまな非同期タスクに役立ちました。このソリューションは、複数のAndroidバージョンと電話モデルで十分にテストされています。DownloadFile.javaに特に関心のある完全なデモがここにあります。

概念例として以下を提示します

public class SimpleAsync extends AsyncTask<String, Integer, String> {
    private static ProgressDialog mProgressDialog = null;
    private final Context mContext;

    public SimpleAsync(Context context) {
        mContext = context;
        if ( mProgressDialog != null ) {
            onPreExecute();
        }
    }

    @Override
    protected void onPreExecute() {
        mProgressDialog = new ProgressDialog( mContext );
        mProgressDialog.show();
    }

    @Override
    protected void onPostExecute(String result) {
        if ( mProgressDialog != null ) {
            mProgressDialog.dismiss();
            mProgressDialog = null;
        }
    }

    @Override
    protected void onProgressUpdate(Integer... progress) {
        mProgressDialog.setProgress( progress[0] );
    }

    @Override
    protected String doInBackground(String... sUrl) {
        // Do some work here
        publishProgress(1);
        return null;
    }

    public void dismiss() {
        if ( mProgressDialog != null ) {
            mProgressDialog.dismiss();
        }
    }
}

Androidアクティビティでの使用は簡単です

public class MainActivity extends Activity {
    DemoServiceClient mClient = null;
    DownloadFile mDownloadFile = null;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate( savedInstanceState );
        setContentView( R.layout.main );
        mDownloadFile = new DownloadFile( this );

        Button downloadButton = (Button) findViewById( R.id.download_file_button );
        downloadButton.setOnClickListener( new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                mDownloadFile.execute( "http://www.textfiles.com/food/bakebred.txt");
            }
        });
    }

    @Override
    public void onPause() {
        super.onPause();
        mDownloadFile.dismiss();
    }
}

0

向きを変更すると、Androidはそのアクティビティを強制終了し、新しいアクティビティを作成します。Rx javaでレトロフィットを使用することをお勧めします。自動的にクラッシュするハンドル。

後付けの呼び出し時にこれらのメソッドを使用します。

.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.