Bagaimana cara menangani orientasi layar berubah ketika dialog progres dan utas latar belakang aktif?

524

Program saya melakukan beberapa aktivitas jaringan di utas latar belakang. Sebelum memulai, muncul dialog progres. Dialog diberhentikan pada pawang. Ini semua berfungsi dengan baik, kecuali ketika orientasi layar berubah saat dialog naik (dan utas latar berjalan). Pada titik ini aplikasi mengalami crash, atau deadlock, atau masuk ke tahap aneh di mana aplikasi tidak bekerja sama sekali sampai semua utas telah terbunuh.

Bagaimana saya bisa menangani perubahan orientasi layar dengan anggun?

Kode contoh di bawah ini kira-kira cocok dengan yang dilakukan oleh program saya yang sebenarnya:

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

Tumpukan:

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)

Saya telah mencoba untuk mengabaikan dialog progres di onSaveInstanceState, tetapi itu hanya mencegah crash segera. Utas latar belakang masih berjalan, dan UI dalam kondisi sebagian ditarik. Harus mematikan seluruh aplikasi sebelum mulai bekerja lagi.

Heikki Toivonen
sumber
1
Mengingat jawaban yang telah Anda terima, Anda harus mengubah jawaban yang diterima untuk yang terbaik, bukan?
rds
Lihat juga pertanyaan sebelumnya stackoverflow.com/questions/456211/…
rds
3
Semua, Mendapat penjelasan yang sangat bagus dan kemungkinan solusi untuk masalah ini. Pergi melalui http://blog.doityourselfandroid.com/2010/11/14/handling-progress-dialogs-and-screen-orientation-changes/ Biar saya tahu jika ini membantu.
arcamax
2
Ada penjelasan yang agak lengkap tentang cara mempertahankan tugas latar belakang yang tidak sinkron di seluruh orientasi layar dalam posting blog ini . Coba lihat!
Adrian Monk
Cukup set android: configChanges = "orientasi | screenSize" ke Activity in manifest.It akan menghentikan android untuk membuat ulang aktivitasnya
Zar E Ahmer

Jawaban:

155

Saat Anda mengalihkan orientasi, Android akan membuat Tampilan baru. Anda mungkin mengalami crash karena utas latar belakang Anda mencoba mengubah status pada yang lama. (Mungkin juga ada masalah karena utas latar belakang Anda tidak ada pada utas UI)

Saya sarankan membuat mHandler itu tidak stabil dan memperbaruinya ketika orientasi berubah.

Haseman
sumber
14
Anda mungkin telah menunjukkan alasan kecelakaan itu. Saya menyingkirkan crash, tapi saya masih belum menemukan cara mengembalikan UI ke keadaan sebelum orientasi berubah dengan cara yang dapat diandalkan. Tapi jawaban Anda membuat saya maju, jadi memberikannya sebagai jawaban.
Heikki Toivonen
4
Anda harus mendapatkan onStart dalam aktivitas Anda ketika orientasi berubah. Pada dasarnya, Anda harus mengkonfigurasi ulang tampilan menggunakan data lama. Jadi saya sarankan meminta status numerik udpates dari progress bar dan membangun kembali pandangan baru ketika Anda mendapatkan 'onStart' yang baru. Saya tidak dapat mengingat begitu saja jika Anda mendapatkan aktivitas baru juga, tetapi beberapa perburuan melalui dokumentasi harus membantu.
haseman
6
Setelah bermain dengannya baru-baru ini, saya dapat menyampaikan bahwa Anda mendapatkan aktivitas baru saat aplikasi Anda mengubah orientasi. (Anda juga mendapatkan tampilan baru) Jika Anda mencoba memperbarui tampilan lama, Anda akan mendapatkan pengecualian karena tampilan lama memiliki konteks aplikasi yang tidak valid (aktivitas lama Anda). Anda dapat menyiasatinya dengan memasukkan myActivity.getApplicationContext () bukannya penunjuk ke aktivitas itu sendiri.
haseman
1
Adakah yang bisa menjelaskan penggunaan / manfaat volatile dalam konteks ini
Zar E Ahmer
2
@Nepster Ya saya juga bertanya-tanya tentang itu. Akan lebih bagus jika seseorang menjelaskan tentang volatile.
RestInPeace
261

Sunting: Insinyur Google tidak merekomendasikan pendekatan ini, seperti yang dijelaskan oleh Dianne Hackborn (alias hackbod ) dalam posting StackOverflow ini . Lihat posting blog ini untuk informasi lebih lanjut.


Anda harus menambahkan ini ke deklarasi aktivitas di manifes:

android:configChanges="orientation|screenSize"

jadi sepertinya

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

Masalahnya adalah bahwa sistem menghancurkan aktivitas ketika perubahan dalam konfigurasi terjadi. Lihat Perubahan Konfigurasi .

Jadi dengan meletakkannya di file konfigurasi, hindari sistem untuk menghancurkan aktivitas Anda. Sebaliknya itu memanggil onConfigurationChanged(Configuration)metode.

sonxurxo
sumber
21
Ini jelas merupakan solusi terbaik; karena hanya memutar tata letak (perilaku yang Anda harapkan di tempat pertama). Pastikan untuk meletakkan Android: configChanges = "orientasi | keyboardHidden" (karena ponsel yang memiliki keyboard lansekap)
nikib3ro
24
Ini sepertinya perilaku yang saya harapkan. Namun, dokumentasi menunjukkan bahwa aktivitas dihancurkan "karena sumber daya aplikasi apa pun, termasuk file tata letak, dapat berubah berdasarkan nilai konfigurasi apa pun. Dengan demikian satu-satunya cara aman untuk menangani perubahan konfigurasi adalah dengan mengambil kembali semua sumber daya". Dan selain itu orientation, ada banyak alasan untuk konfigurasi berubah: keyboardHidden(Saya sudah mengedit jawaban wiki), uiMode(Misalnya, masuk atau keluar dari mode mobil; mode malam berubah), dll. Sekarang saya bertanya-tanya apakah ini sebenarnya jawaban yang bagus
rds
116
Ini bukan solusi yang bisa diterima. Itu hanya menutupi masalah sebenarnya.
rf43
18
Bekerja, tetapi tidak direkomendasikan oleh Google.
Ed Burnette
21
Tolong jangan ikuti pendekatan ini di sini. DDosAttack sepenuhnya benar. Bayangkan Anda sedang membuat dialog progres untuk mengunduh atau hal lain yang membutuhkan waktu lama. Sebagai pengguna Anda tidak akan tetap pada aktivitas itu dan menatapnya. Anda akan beralih ke layar beranda atau ke aplikasi lain seperti game atau panggilan telepon yang masuk atau sesuatu yang lapar sumber daya yang pada akhirnya akan menghancurkan aktivitas Anda. Lalu bagaimana? Anda menghadapi masalah lama yang sama yang TIDAK diselesaikan dengan trik kecil yang rapi. Aktivitas akan diciptakan kembali lagi ketika pengguna kembali.
tiguchi
68

Saya datang dengan solusi solid untuk masalah ini yang sesuai dengan 'Android Way'. Saya memiliki semua operasi jangka panjang saya menggunakan pola IntentService.

Artinya, kegiatan saya disiarkan maksud, yang IntentService melakukan pekerjaan, menyimpan data dalam DB dan kemudian menyiarkan lengket maksud. Bagian lengket penting, sedemikian sehingga bahkan jika Kegiatan dijeda selama selama waktu setelah pengguna memulai pekerjaan dan melewatkan siaran waktu nyata dari IntentService kita masih dapat menanggapi dan mengambil data dari Kegiatan panggilan. ProgressDialogs dapat bekerja dengan pola ini dengan cukup baik onSaveInstanceState().

Pada dasarnya, Anda perlu menyimpan flag yang sedang Anda jalankan dialognya dalam bundel instance tersimpan. Jangan simpan objek dialog progres karena ini akan membocorkan seluruh aktivitas. Untuk memiliki pegangan yang persisten pada dialog progres, saya menyimpannya sebagai referensi yang lemah di objek aplikasi. Saat perubahan orientasi atau hal lain yang menyebabkan Aktivitas berhenti (panggilan telepon, pengguna menekan rumah, dll.) Dan kemudian melanjutkan, saya mengabaikan dialog lama dan membuat kembali dialog baru di Aktivitas yang baru dibuat.

Untuk dialog kemajuan yang tidak terbatas ini mudah. Untuk gaya bilah kemajuan, Anda harus meletakkan kemajuan yang diketahui terakhir di bundel dan informasi apa pun yang Anda gunakan secara lokal di aktivitas untuk melacak kemajuan. Pada memulihkan kemajuan, Anda akan menggunakan informasi ini untuk menelurkan kembali progress bar dalam keadaan yang sama seperti sebelumnya dan kemudian memperbarui berdasarkan keadaan saat ini.

Jadi untuk meringkas, menempatkan tugas yang sudah berjalan lama ke IntentService ditambah dengan penggunaan yang bijaksana onSaveInstanceState()memungkinkan Anda untuk secara efisien melacak dialog dan memulihkan kemudian di seluruh kegiatan siklus hidup Aktivitas. Bit kode aktivitas yang relevan ada di bawah ini. Anda juga akan memerlukan logika di BroadcastReceiver Anda untuk menangani maksud Sticky secara tepat, tetapi itu berada di luar cakupannya.

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));
    }
}
jfelectron
sumber
tampaknya solusi yang bagus
Derekyy
"Saya memiliki semua operasi jangka panjang saya menggunakan pola IntentService." Ini bukan solusi yang sempurna karena seperti menembakkan meriam ke burung pipit dan banyak kode boilerplate, untuk lebih banyak Anda bisa menonton youtube.com/watch?v=NJsq0TU0qeg
Kamil Nekanowicz
28

Saya menemui masalah yang sama. Aktivitas saya perlu mem-parsing beberapa data dari URL dan lambat. Jadi saya membuat utas untuk melakukannya, lalu tampilkan dialog progres. Saya membiarkan utas memposting pesan kembali ke utas UI melalui Handlersaat selesai. Di Handler.handleMessage, saya mendapatkan objek data (siap sekarang) dari utas dan mengisinya ke UI. Jadi sangat mirip dengan contoh Anda.

Setelah banyak trial and error sepertinya saya menemukan solusi. Setidaknya sekarang saya bisa memutar layar kapan saja, sebelum atau setelah utas selesai. Dalam semua tes, dialog ditutup dengan benar dan semua perilaku seperti yang diharapkan.

Apa yang saya lakukan ditunjukkan di bawah ini. Tujuannya adalah untuk mengisi model data saya ( mDataObject) dan kemudian mengisinya ke UI. Seharusnya memungkinkan rotasi layar setiap saat tanpa kejutan.

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.
    }
}

Itu yang berhasil untuk saya. Saya tidak tahu apakah ini metode "benar" seperti yang dirancang oleh Android - mereka mengklaim ini "hancurkan / buat kembali aktivitas selama rotasi layar" sebenarnya membuat segalanya lebih mudah, jadi saya kira itu tidak boleh terlalu rumit.

Beri tahu saya jika Anda melihat masalah dalam kode saya. Seperti yang dikatakan di atas, saya tidak benar-benar tahu apakah ada efek samping.

samsonsu
sumber
1
Terima kasih banyak! Petunjuk onRetainNonConfigurationInstance()dan getLastNonConfigurationInstance()membantu saya untuk menyelesaikan masalah saya. Jempolan!
sven
15

Masalah yang dirasakan asli adalah bahwa kode tidak akan selamat dari perubahan orientasi layar. Rupanya ini "diselesaikan" dengan meminta program menangani perubahan layar itu sendiri, alih-alih membiarkan kerangka UI melakukannya (melalui panggilan onDestroy)).

Saya akan menyampaikan bahwa jika masalah yang mendasarinya adalah bahwa program tidak akan bertahan diDestroy (), maka solusi yang diterima hanyalah solusi yang meninggalkan program dengan masalah serius lainnya dan kerentanan. Ingatlah bahwa kerangka kerja Android secara khusus menyatakan bahwa aktivitas Anda berisiko dihancurkan hampir setiap saat karena keadaan di luar kendali Anda. Oleh karena itu, aktivitas Anda harus dapat bertahan hidup diDestroy () dan onCreate () berikutnya dengan alasan apa pun, bukan hanya perubahan orientasi layar.

Jika Anda akan menerima penanganan sendiri perubahan orientasi layar untuk menyelesaikan masalah OP, Anda perlu memverifikasi bahwa penyebab lain dari onDestroy () tidak menghasilkan kesalahan yang sama. Apakah kamu bisa melakukan ini? Jika tidak, saya akan mempertanyakan apakah jawaban "diterima" benar-benar jawaban yang sangat baik.

sepatu olahraga
sumber
14

Solusi saya adalah memperluas ProgressDialogkelas untuk mendapatkan kelas saya sendiri MyProgressDialog.
Saya mendefinisikan ulang show()dan dismiss()metode untuk mengunci orientasi sebelum menunjukkan Dialogdan membuka kembali ketika Dialogdiberhentikan. Jadi ketika Dialogditampilkan dan orientasi perangkat berubah, orientasi layar tetap sampai dismiss()dipanggil, kemudian orientasi layar berubah sesuai dengan nilai sensor / orientasi perangkat.

Ini kode saya:

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

}
Oli
sumber
8

Saya menghadapi masalah yang sama, dan saya datang dengan solusi yang tidak melibatkan menggunakan ProgressDialog dan saya mendapatkan hasil yang lebih cepat.

Apa yang saya lakukan adalah membuat tata letak yang memiliki ProgressBar di dalamnya.

<?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>

Kemudian dalam metode onCreate lakukan hal berikut

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

Kemudian lakukan tugas panjang dalam utas, dan ketika itu selesai memiliki Runnable atur tampilan konten ke tata letak nyata yang ingin Anda gunakan untuk aktivitas ini.

Sebagai contoh:

mHandler.post(new Runnable(){

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

Inilah yang saya lakukan, dan saya menemukan bahwa ini berjalan lebih cepat daripada menunjukkan ProgressDialog dan itu kurang mengganggu dan memiliki pandangan yang lebih baik menurut saya.

Namun, jika Anda ingin menggunakan ProgressDialog, maka jawaban ini bukan untuk Anda.

Pzanno
sumber
Solusi ini elegan dalam use case yang sederhana, tetapi memiliki kelemahan. Anda perlu membangun kembali tampilan konten lengkap. setContentView(R.layout.my_layout);Tidak cukup; Anda perlu mengatur semua pendengar, mengatur ulang data, dll.
rds
@rds Anda benar. Ini benar-benar hanya solusi untuk kasing sederhana, atau jika Anda perlu melakukan sedikit peningkatan pada metode onCreate Anda sebelum menampilkan tampilan Anda.
Pzanno
Saya tidak mengerti. Alih-alih pengaturan pendengar di onCreate (), seperti yang biasanya kita lakukan, kita dapat mengaturnya dalam menjalankan (). Apakah saya melewatkan sesuatu di sini?
Code Poet
7

Saya menemukan solusi untuk ini yang belum saya lihat di tempat lain. Anda dapat menggunakan objek aplikasi kustom yang tahu jika Anda memiliki tugas latar belakang, alih-alih mencoba melakukan ini dalam aktivitas yang dihancurkan dan diciptakan kembali saat perubahan orientasi. Saya membuat blog tentang ini di sini .

Heikki Toivonen
sumber
1
Membuat custom Applicationbiasanya digunakan untuk mempertahankan status aplikasi global. Saya tidak mengatakan itu tidak berhasil, tetapi tampaknya terlalu rumit. Dari doc "Biasanya tidak perlu untuk subkelas Aplikasi." Saya lebih suka jawaban sonxurxo.
rds
7

Saya akan berkontribusi dalam pendekatan saya untuk menangani masalah rotasi ini. Ini mungkin tidak relevan dengan OP karena dia tidak menggunakan AsyncTask, tetapi mungkin orang lain akan merasa berguna. Ini cukup sederhana tetapi tampaknya melakukan pekerjaan untuk saya:

Saya memiliki aktivitas masuk dengan AsyncTaskkelas bersarang yang disebut BackgroundLoginTask.

Dalam saya, BackgroundLoginTasksaya tidak melakukan sesuatu yang luar biasa kecuali menambahkan cek nol pada ProgressDialogpemecatan panggilan :

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

Ini untuk menangani kasus di mana tugas latar belakang selesai sementara Activitytidak terlihat dan, oleh karena itu, dialog progres telah diberhentikan oleh onPause()metode.

Selanjutnya, di Activitykelas orang tua saya , saya membuat pegangan statis global untuk AsyncTaskkelas saya dan saya ProgressDialog(yang AsyncTask, bersarang, dapat mengakses variabel-variabel ini):

private static BackgroundLoginTask backgroundLoginTask;
private static ProgressDialog pleaseWaitDialog;

Ini melayani dua tujuan: Pertama, ini memungkinkan saya Activityuntuk selalu mengakses AsyncTaskobjek bahkan dari aktivitas baru, setelah diputar. Kedua, memungkinkan saya BackgroundLoginTaskuntuk mengakses dan memberhentikan ProgressDialogbahkan setelah rotasi.

Selanjutnya, saya menambahkan ini ke onPause(), menyebabkan dialog kemajuan menghilang ketika kita Activitymeninggalkan latar depan (mencegah crash "paksa tutup" yang jelek):

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

Akhirnya, saya memiliki yang berikut dalam onResume()metode saya :

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

Ini memungkinkan Dialoguntuk muncul kembali setelah Activitydiciptakan kembali.

Inilah seluruh kelas:

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

    }
}

Saya bukan pengembang Android berpengalaman, jadi jangan ragu untuk berkomentar.

Anders
sumber
1
Menarik! Terutama bagi kita yang menggunakan AsyncTask. Baru saja mencoba solusi Anda, dan tampaknya sebagian besar berhasil. Ada satu masalah: ProgressDialog tampaknya berakhir sedikit lebih awal setelah rotasi SAAT ProgressDialog masih aktif. Saya akan bermain-main untuk melihat apa yang sebenarnya terjadi dan bagaimana cara memperbaikinya. Tapi saya tidak lagi mendapatkan crash itu!
Scott Biggs
1
Menemukan perbaikan. Tampaknya masalah di sini adalah ProgressDialog statis. Ketika rotasi mengganggu ProgressDialog, kadang-kadang mendapat metode .dismiss () dipanggil setelah di-restart di Activity baru. Dengan membuat ProgressDialog dibuat dengan setiap Kegiatan, kami memastikan bahwa ProgressDialog yang baru ini tidak terbunuh bersama dengan Aktivitas yang lama. Saya juga memastikan bahwa ProgressDialog diatur ke nol setiap kali diberhentikan (untuk membantu pengumpulan sampah). Jadi kami punya solusi di sini! Salam bagi mereka yang menggunakan AsyncTask!
Scott Biggs
4

Pindahkan tugas panjang ke kelas terpisah. Menerapkannya sebagai pola subjek-pengamat. Setiap kali kegiatan dibuat register dan sambil menutup batalkan registrasi dengan kelas tugas. Kelas tugas dapat menggunakan AsyncTask.

Vinay
sumber
1
Saya tidak melihat bagaimana itu akan membantu. Bisakah Anda menjelaskan lebih detail bagaimana ini mencegah masalah yang saya lihat.
Heikki Toivonen
1
Seperti yang dikatakan Haseman mencegah backend untuk mengakses elemen UI dan kita dapat memisahkan UI dari backend, backend berjalan dalam utas terpisah dan terus berjalan bahkan setelah layar berorientasi ulang dan Mendaftar-Tidak Mendaftar dengan tugas Backend untuk pembaruan status . Contoh nyata yang telah saya pecahkan dengan menggunakan ini adalah bahwa saya memiliki Tugas Unduhan, saya telah memindahkannya ke utas terpisah, setiap kali utas dibuat, saya mendaftar-tidak mendaftar.
Vinay
Oke, saya meninjau kembali masalah ini dan saya rasa saya masih belum sepenuhnya memahami jawaban ini. Misalkan kita memiliki aktivitas utama memulai AsyncTask untuk melakukan operasi jaringan yang sudah berjalan lama yang tidak ingin kita interupsi selama perubahan orientasi layar. Saya tidak melihat bagaimana aktivitas baru dapat mengirim pesan ke AsyncTask yang dimulai oleh aktivitas lama. Bisakah Anda memberikan contoh kode?
Heikki Toivonen
@ Heikki, apakah implementasi saya di bawah ini yang Anda maksud?
beetstra
4

Caranya adalah dengan menampilkan / mengabaikan dialog dalam AsyncTask selama onPreExecute / onPostExecute seperti biasa, meskipun dalam hal perubahan orientasi buat / tampilkan contoh baru dari dialog dalam aktivitas dan berikan referensi untuk tugas tersebut.

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;
        }       
    }
}
n224576
sumber
4

Saya telah melakukannya seperti ini:

    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 {

                }
            }
        }

    }

Anda juga dapat mencoba dan memberi tahu saya bahwa itu cocok untuk Anda atau tidak

Sachin Gurnani
sumber
Kode onDestroy mungkin tidak dapat dieksekusi sama sekali, dari halaman Pengembang : "Perhatikan kolom" Dapat dibunuh "pada tabel di atas - untuk metode yang ditandai sebagai dapat dimatikan, setelah metode itu mengembalikan proses yang menjadi tuan rumah aktivitas tersebut dapat dibunuh oleh sistem kapan saja tanpa baris kode lainnya dieksekusi "
ilomambo
Saya sangat percaya bahwa "onRetainNonConfigurationInstance ()" adalah metode yang akan digunakan untuk kasus-kasus seperti ... nyc work
Nitin Bansal
Saya menghadapi masalah yang sama, karena Sachin Gurnani saya menggunakan deklarasi statis untuk memperbaiki masalah saya. stackoverflow.com/questions/12058774/…
Steven Du
2

Jika Anda membuat latar belakang Serviceyang melakukan semua pengangkatan berat (permintaan / respons tcp, unmarshalling), Viewdan Activitydapat dihancurkan dan dibuat kembali tanpa membocorkan jendela atau kehilangan data. Ini memungkinkan perilaku yang disarankan Android, yaitu untuk menghancurkan Aktivitas pada setiap perubahan konfigurasi (mis. Untuk setiap perubahan orientasi).

Ini sedikit lebih rumit, tetapi ini adalah cara terbaik untuk memohon permintaan server, data pra / pasca pemrosesan, dll.

Anda bahkan dapat menggunakan Service untuk mengantri setiap permintaan ke server, sehingga memudahkan dan efisien untuk menangani hal-hal itu.

Panduan dev memiliki babServices lengkap .

acardenas89
sumber
A Servicelebih banyak pekerjaan daripada sebuah AsyncTasktetapi bisa menjadi pendekatan yang lebih baik dalam beberapa situasi. Belum tentu lebih baik, kan? Yang sedang berkata, saya tidak mengerti bagaimana ini memecahkan masalah ProgressDialogyang bocor dari utama Activity. Di mana Anda memulai ProgressDialog? Di mana Anda menolaknya?
rds
2

Saya memiliki implementasi yang memungkinkan aktivitas dihancurkan pada perubahan orientasi layar, tetapi berhasil menghancurkan dialog dalam aktivitas yang dibuat ulang. Saya gunakan ...NonConfigurationInstanceuntuk melampirkan tugas latar belakang ke aktivitas yang dibuat kembali. Kerangka kerja Android normal menangani menciptakan kembali dialog itu sendiri, tidak ada yang berubah di sana.

Saya mensubklasifikasikan AsyncTask menambahkan bidang untuk aktivitas 'memiliki', dan metode untuk memperbarui pemilik ini.

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

  ...
}

Di kelas aktivitas saya, saya menambahkan bidang yang backgroundTaskmerujuk ke backgroundtask 'milik', dan saya memperbarui bidang ini menggunakan onRetainNonConfigurationInstancedan 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;
  }
  ...
}

Saran untuk perbaikan lebih lanjut:

  • Kosongkan backgroundTaskreferensi dalam aktivitas setelah tugas selesai untuk melepaskan memori atau sumber daya lain yang terkait dengannya.
  • Kosongkan ownerActivity referensi di backgroundtask sebelum aktivitas dimusnahkan seandainya tidak akan segera dibuat kembali.
  • Buat BackgroundTaskantarmuka dan / atau koleksi untuk memungkinkan berbagai jenis tugas dijalankan dari aktivitas kepemilikan yang sama.
beetstra
sumber
2

Jika Anda mempertahankan dua tata letak, semua utas UI harus dihentikan.

Jika Anda menggunakan AsynTask, maka Anda dapat dengan mudah memanggil .cancel()metode di dalam onDestroy()metode aktivitas saat ini.

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

Untuk AsyncTask, baca lebih lanjut di bagian "Membatalkan tugas" di sini .

Pembaruan: Menambahkan kondisi untuk memeriksa status, karena itu hanya dapat dibatalkan jika sedang dalam keadaan berjalan. Perhatikan juga bahwa AsyncTask hanya dapat dijalankan satu kali.

Vara
sumber
2

Mencoba untuk mengimplementasikan solusi jfelectron karena ini adalah " solusi yang kuat untuk masalah ini yang sesuai dengan 'Android Way' hal " tetapi butuh beberapa waktu untuk mencari dan mengumpulkan semua elemen yang disebutkan. Berakhir dengan ini sedikit berbeda, dan saya pikir lebih elegan, solusi diposting di sini secara keseluruhan.

Menggunakan IntentService yang dipecat dari suatu aktivitas untuk melakukan tugas jangka panjang pada utas terpisah. Layanan ini menjalankan kembali maksud Siaran yang lengket ke aktivitas yang memperbarui dialog. Aktivitas menggunakan showDialog (), onCreateDialog () dan onPrepareDialog () untuk menghilangkan kebutuhan untuk memiliki data persisten yang dilewatkan dalam objek aplikasi atau bundel SimpananInstanceState yang disimpan. Ini akan berfungsi tidak masalah bagaimana aplikasi Anda terganggu.

Kelas Kegiatan:

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

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

Entri file manifest:

sebelum bagian aplikasi:

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

di dalam bagian aplikasi

service android:name=".MyService"
MindSpiker
sumber
2

Ini adalah solusi yang saya usulkan:

  • Pindahkan AsyncTask atau Thread ke Fragmen yang dipertahankan, seperti dijelaskan di sini . Saya percaya ini adalah praktik yang baik untuk memindahkan semua panggilan jaringan ke fragmen. Jika Anda sudah menggunakan fragmen, salah satunya bisa bertanggung jawab untuk panggilan tersebut. Jika tidak, Anda dapat membuat fragmen hanya untuk melakukan permintaan, seperti yang diusulkan artikel tertaut.
  • Fragmen akan menggunakan antarmuka pendengar untuk menandai penyelesaian / kegagalan tugas. Anda tidak perlu khawatir untuk perubahan orientasi di sana. Fragmen akan selalu memiliki tautan yang benar ke aktivitas saat ini dan dialog progres dapat dilanjutkan dengan aman.
  • Jadikan dialog kemajuan Anda sebagai anggota kelas Anda. Sebenarnya Anda harus melakukan itu untuk semua dialog. Dalam metode onPause Anda harus mengabaikannya, jika tidak, Anda akan membocorkan jendela tentang perubahan konfigurasi. Keadaan sibuk harus dijaga oleh fragmen. Ketika fragmen dilampirkan ke aktivitas, Anda dapat memunculkan dialog progres lagi, jika panggilan masih berjalan. Sebuah void showProgressDialog()metode dapat ditambahkan ke antarmuka pendengar fragmen-kegiatan untuk tujuan ini.
kgiannakakis
sumber
Solusi sempurna, tetapi tidak mengerti mengapa jawaban ini dibayangi oleh orang lain !!!
blackkara
2

Saya menghadapi situasi yang sama. Apa yang saya lakukan adalah hanya mendapatkan satu contoh untuk dialog progres saya di seluruh aplikasi.

Pertama, saya membuat kelas DialogSingleton untuk mendapatkan hanya satu instance (pola Singleton)

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

Seperti yang saya tunjukkan di kelas ini, saya memiliki dialog progres sebagai atribut. Setiap kali saya perlu menampilkan dialog progres, saya mendapatkan contoh unik dan membuat ProgressDialog baru.

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

Ketika saya selesai dengan tugas latar belakang, saya memanggil lagi contoh unik dan mengabaikan dialognya.

DialogSingleton.GetInstance().DialogDismiss(this);

Saya menyimpan status tugas latar belakang di preferensi bersama saya. Ketika saya memutar layar, saya bertanya apakah saya memiliki tugas yang berjalan untuk aktivitas ini: (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).

Ketika saya mulai menjalankan tugas latar belakang:

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

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

Ketika saya selesai menjalankan tugas latar belakang:

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

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

Saya harap ini membantu.

deinier
sumber
2

Ini adalah pertanyaan yang sangat lama yang muncul di sidebar karena alasan tertentu.

Jika tugas latar belakang hanya perlu bertahan saat aktivitas berada di latar depan, solusi "baru" adalah untuk menjadi tuan rumah utas latar belakang (atau, lebih disukai, AsyncTask) dalam fragmen yang dipertahankan , seperti yang dijelaskan dalam panduan pengembang ini dan banyak Tanya Jawab .

Fragmen yang dipertahankan bertahan jika aktivitas dimusnahkan karena perubahan konfigurasi, tetapi tidak ketika aktivitas dimusnahkan di latar belakang atau tumpukan belakang. Oleh karena itu, tugas latar belakang harus tetap terputus jika isChangingConfigurations()salah dalam onPause().

Kevin Krumwiede
sumber
2

Saya lebih segar di android dan saya mencoba ini dan berhasil.

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);
        }
}
Menjemukan
sumber
1

Saya sudah mencoba semuanya. Menghabiskan hari bereksperimen. Saya tidak ingin memblokir aktivitas agar tidak berputar. Skenario saya adalah:

  1. Dialog progres yang menampilkan informasi dinamis kepada pengguna. Misalnya: "Menghubungkan ke server ...", "Mengunduh data ...", dll.
  2. Utas melakukan hal-hal berat dan memperbarui dialog
  3. Memperbarui UI dengan hasil di bagian akhir.

Masalahnya adalah, ketika memutar layar, setiap solusi pada buku gagal. Bahkan dengan kelas AsyncTask, yang merupakan cara Android yang tepat untuk menghadapi situasi ini. Saat memutar layar, Konteks saat ini yang bekerja dengan thread awal, hilang, dan yang mengacaukan dialog yang ditampilkan. Masalahnya selalu Dialog, tidak peduli berapa banyak trik yang saya tambahkan ke kode (melewati konteks baru untuk menjalankan utas, mempertahankan status utas melalui rotasi, dll ...). Kompleksitas kode pada akhirnya selalu besar dan selalu ada sesuatu yang salah.

Satu-satunya solusi yang berhasil bagi saya adalah trik Activity / Dialog. Ini sederhana dan jenius dan itu semua bukti rotasi:

  1. Alih-alih membuat Dialog dan meminta untuk menunjukkannya, buat Aktivitas yang telah diatur dalam manifes dengan android: theme = "@ android: style / Theme.Dialog". Jadi, itu hanya terlihat seperti dialog.

  2. Ganti showDialog (DIALOG_ID) dengan startActivityForResult (yourActivityDialog, yourCode);

  3. Gunakan onActivityResult dalam Aktivitas pemanggilan untuk mendapatkan hasil dari utas pelaksana (bahkan kesalahan) dan perbarui UI.

  4. Di 'ActivityDialog' Anda, gunakan utas atau AsyncTask untuk menjalankan tugas yang panjang dan diRetainNonConfigurationInstance untuk menyimpan status "dialog" saat memutar layar.

Ini cepat dan berfungsi dengan baik. Saya masih menggunakan dialog untuk tugas-tugas lain dan AsyncTask untuk sesuatu yang tidak memerlukan dialog konstan di layar. Tetapi dengan skenario ini, saya selalu mencari pola Kegiatan / Dialog.

Dan, saya tidak mencobanya, tetapi bahkan mungkin untuk memblokir Kegiatan / Dialog agar tidak berputar, ketika utas berjalan, mempercepat, sementara memungkinkan Kegiatan pemanggilan berputar.

Rui
sumber
Bagus kecuali bahwa parameter harus dilewatkan melalui Intent, yang lebih ketat daripada yang Objectdiizinkan olehAsyncTask
rds
@Rui Saya juga menggunakan metode ini selama setahun terakhir. Sekarang saya sadar bahwa ini juga salah. Mengapa Google bahkan memiliki Dialog jika ini adalah cara untuk 'memperbaiki' masalah ini? Masalah yang saya lihat adalah bahwa jika Anda membuka ActivityB (Theme.Dialog) dari ActivityA maka ActivityA dipindahkan ke bawah di tumpukan Aktivitas sehingga ditandai sebagai siap untuk dibunuh oleh OS jika perlu. Oleh karena itu, jika Anda memiliki proses yang berjalan lama dan menunjukkan semacam 'dialog' palsu dan butuh waktu terlalu lama dan ingatannya rendah ... ActivityA terbunuh dan tidak ada yang kembali juga setelah kemajuan selesai.
rf43
1

Saat ini ada cara yang jauh lebih berbeda untuk menangani jenis masalah ini. Pendekatan khas adalah:

1. Pastikan data Anda dipisahkan dengan benar dari UI:

Apa pun yang merupakan proses latar belakang harus disimpan Fragment(set ini dengan Fragment.setRetainInstance(). Ini menjadi 'penyimpanan data persisten' Anda di mana data apa pun yang ingin Anda pertahankan disimpan. Setelah acara perubahan orientasi, ini Fragmentmasih akan dapat diakses dengan aslinya nyatakan melalui FragmentManager.findFragmentByTag()panggilan (saat Anda membuatnya, Anda harus memberinya tag bukan ID karena tidak dilampirkan keView ).

Lihat Menangani Runtime Changes panduan yang dikembangkan untuk informasi tentang melakukan ini dengan benar dan mengapa itu adalah pilihan terbaik.

2. Pastikan Anda berinteraksi dengan benar dan aman antara proses latar belakang dan UI Anda:

Anda harus membalikkan proses penautan Anda. Saat ini proses latar belakang Anda menempel pada View- sebagai gantinya Anda Viewharus menempel sendiri ke proses latar belakang. Lebih masuk akal bukan? The View's tindakan tergantung pada proses latar belakang, sedangkan proses latar belakang tidak tergantung pada View.Ini berarti mengubah link ke standar Listenerantarmuka. Katakan proses Anda (apa pun kelasnya - apakah itu sebuah AsyncTask, Runnableatau apa pun) yang mendefinisikan OnProcessFinishedListener, ketika proses itu dilakukan, Anda harus memanggil pendengar itu jika ada.

Jawaban ini adalah deskripsi singkat yang bagus tentang bagaimana melakukan pendengar khusus.

3. Tautkan UI Anda ke dalam proses data setiap kali UI dibuat (termasuk perubahan orientasi):

Sekarang Anda harus khawatir tentang menghubungkan tugas latar belakang dengan apa pun Viewstruktur Anda saat ini. Jika Anda menangani perubahan orientasi dengan benar (tidak configChangesselalu disarankan orang hack), maka Anda Dialogakan dibuat ulang oleh sistem. Ini penting, artinya pada perubahan orientasi, semua Dialogmetode siklus hidup Anda dipanggil kembali. Jadi dalam salah satu metode ini ( onCreateDialogbiasanya merupakan tempat yang baik), Anda dapat melakukan panggilan seperti berikut:

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

Lihat siklus hidup Fragmen untuk menentukan di mana pengaturan pendengar paling cocok dalam implementasi pribadi Anda.

Ini adalah pendekatan umum untuk memberikan solusi yang kuat dan lengkap untuk masalah umum yang diajukan dalam pertanyaan ini. Mungkin ada beberapa bagian kecil yang hilang dalam jawaban ini tergantung pada skenario pribadi Anda, tetapi ini umumnya merupakan pendekatan yang paling tepat untuk menangani acara perubahan orientasi dengan tepat.

BT
sumber
1

saya telah menemukan dan solusi yang lebih mudah untuk menangani utas ketika orientasi berubah. Anda bisa menyimpan referensi statis untuk aktivitas / fragmen Anda dan memverifikasi apakah nol sebelum bertindak pada ui. Saya sarankan menggunakan try catch juga:

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


}
sagit
sumber
1

Jika Anda kesulitan mendeteksi perubahan orientasi acara dialog INDEPENDEN DARI REFERENSI AKTIVITAS , metode ini bekerja dengan sangat baik. Saya menggunakan ini karena saya memiliki kelas dialog saya sendiri yang dapat ditampilkan di beberapa Aktivitas yang berbeda jadi saya tidak selalu tahu di mana Kegiatan itu ditampilkan. Dengan metode ini Anda tidak perlu mengubah AndroidManifest, khawatir tentang referensi Kegiatan, dan Anda tidak perlu dialog khusus (seperti yang saya miliki). Namun, Anda memerlukan tampilan konten khusus sehingga Anda dapat mendeteksi perubahan orientasi menggunakan tampilan tertentu. Inilah contoh saya:

Mendirikan

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

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

        //DO SOMETHING HERE!! :D
    }
}

Implementasi 1 - Dialog

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

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

Implementasi 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();
kpninja12
sumber
1

Ini adalah solusi saya ketika saya menghadapinya: ProgressDialogbukan Fragmentanak kecil, jadi " ProgressDialogFragment" kelas khusus saya dapat diperluas DialogFragmentuntuk menjaga agar dialog tetap ditampilkan untuk perubahan konfigurasi.

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

Tantangannya adalah untuk mempertahankan judul dialog & pesan saat rotasi layar saat mereka mereset ke string kosong default, meskipun dialog masih ditampilkan

Ada 2 pendekatan untuk menyelesaikan ini:

Pendekatan pertama: Buat aktivitas yang memanfaatkan dialog untuk mempertahankan status selama konfigurasi perubahan dalam file manifes:

android:configChanges="orientation|screenSize|keyboardHidden"

Pendekatan ini tidak disukai oleh Google.

Pendekatan kedua: pada metode aktivitas onCreate(), Anda perlu mempertahankannya DialogFragmentdengan membangun kembali ProgressDialogFragmentlagi dengan judul & pesan sebagai berikut jika savedInstanceStatetidak nol:

@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");
      }
  }
}
Zain
sumber
0

Tampaknya terlalu 'cepat dan kotor' untuk menjadi kenyataan jadi tolong tunjukkan kekurangannya tetapi apa yang saya temukan berhasil ...

Dalam metode onPostExecute dari AsyncTask saya, saya hanya membungkus '.dismiss' untuk dialog progres di blok coba / tangkap (dengan tangkapan kosong) dan kemudian abaikan saja pengecualian yang dimunculkan. Tampaknya salah untuk dilakukan tetapi tampaknya tidak ada efek buruk (setidaknya untuk apa yang saya lakukan selanjutnya yaitu memulai aktivitas lain yang lulus sebagai hasil dari permintaan saya yang berjalan lama sebagai Ekstra)

Simon
sumber
Apakah Anda mengatakan sebenarnya tidak ada kebocoran memori ketika platform Android memberi tahu jendela tersebut bocor?
rds
Dialog progres saya adalah variabel anggota kelas aktivitas jadi saya berasumsi bahwa ketika aktivitas dihancurkan dan diciptakan kembali, itu akan menjadi sampah yang dikumpulkan dan tidak ada kebocoran. Apakah aku salah?
Simon
Ya, saya pikir itu salah. Seperti yang Anda katakan, Activityreferensi ke Dialog. Ketika konfigurasi diubah, yang pertama Activitydihancurkan, yang berarti semua bidang diatur ke null. Tetapi level rendah WindowManagerjuga memiliki referensi ke Dialog(karena belum diberhentikan). Yang baru Activitymencoba untuk membuat yang baru Dialog(dalam preExecute()) dan manajer jendela menimbulkan pengecualian fatal yang mencegah Anda melakukannya. Memang, jika itu terjadi, tidak akan ada cara untuk menghancurkan dengan bersih Dialogmaka menjaga referensi ke awal Activity. Apakah saya benar?
rds
0

Solusi paling sederhana dan paling fleksibel adalah dengan menggunakan AsyncTask dengan referensi statis ke ProgressBar . Ini memberikan solusi terenkapsulasi dan dengan demikian dapat digunakan kembali untuk masalah perubahan orientasi. Solusi ini telah membantu saya dengan baik untuk berbagai tugas asinkron termasuk unduhan internet, berkomunikasi dengan Layanan , dan pemindaian sistem file. Solusinya telah diuji dengan baik pada beberapa versi Android dan model telepon. Demo lengkap dapat ditemukan di sini dengan minat khusus pada DownloadFile.java

Saya menyajikan berikut ini sebagai contoh konsep

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

Penggunaan dalam Aktivitas Android sederhana

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();
    }
}
Derrick J Wippler
sumber
0

Saat Anda mengubah orientasi, Android membunuh aktivitas itu dan membuat aktivitas baru. Saya menyarankan untuk menggunakan retrofit dengan Rx java. yang menangani crash secara otomatis.

Gunakan metode ini saat retrofit memanggil.

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

priti
sumber