İlerleme durumu iletişim kutusu ve arka plan iş parçacığı etkinken ekran yönü değişikliği nasıl yapılır?


524

Programım bir arka plan iş parçacığında bazı ağ etkinliği yapar. Başlamadan önce bir ilerleme iletişim kutusu açılır. İletişim kutusu işleyicide kapatılır. Diyalog açıkken (ve arka plan iş parçacığı devam ederken) ekran yönünün değişmesi dışında, tüm bunlar iyi çalışır. Bu noktada uygulama çöküyor veya kilitlenmeler veya tüm evreler öldürülünceye kadar uygulamanın hiç çalışmadığı garip bir aşamaya giriyor.

Ekran yönü değişikliğini nazikçe nasıl yapabilirim?

Aşağıdaki örnek kod kabaca gerçek programımın yaptığı ile eşleşiyor:

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

Stack:

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 ilerleme iletişim kutusunu kapatmaya çalıştım, ama bu sadece hemen çökmesini önler. Arka plan iş parçacığı hala devam ediyor ve kullanıcı arayüzü kısmen çizilmiş durumda. Tekrar çalışmaya başlamadan önce tüm uygulamayı öldürmeniz gerekiyor.


1
Aldığınız cevapları göz önüne alarak, kabul edilen yanıtı en iyiler lehine değiştirmelisiniz, değil mi?
rds

Ayrıca bkz. Eski bir soru stackoverflow.com/questions/456211/…
rds

3
Hepsi, bu soruna gerçekten harika bir açıklama ve olası çözümler var. Git aracılığıyla http://blog.doityourselfandroid.com/2010/11/14/handling-progress-dialogs-and-screen-orientation-changes/ bu yardım ederse Lemme bilmek.
arcamax

2
Bu blog yazısında ekran yönelimlerinde eşzamansız arka plan görevlerinin nasıl tutulacağına dair oldukça eksiksiz bir açıklama var . Bunu kontrol et!
Adrian Monk

Basitçe android: configChanges = "orientation | screenSize" öğesini manifest'teki Activity'ye ayarlayın. Aktivitesini yeniden oluşturmak için android'i durduracak
Jawad Zeb

Yanıtlar:


155

Yönleri değiştirdiğinizde Android yeni bir Görünüm oluşturur. Arka plan iş parçacığınız eskisi üzerindeki durumu değiştirmeye çalıştığı için muhtemelen çöküyor olabilirsiniz. (Arka plan iş parçacınız UI iş parçacığında olmadığından da sorun olabilir)

Ben mHandler uçucu hale getirme ve yön değiştiğinde güncelleme öneririz.


14
Kazanın nedenini tespit etmiş olabilirsiniz. Kazadan kurtuldum, ancak hala UI'yi oryantasyon değişikliğinden önce bulunduğu duruma güvenilir bir şekilde nasıl geri yükleyeceğimi bilmiyorum. Ama cevabın beni ileriye götürdü, bu yüzden cevap olarak verdim.
Heikki Toivonen

4
Yön değiştiğinde etkinliğinizde bir onStart almalısınız. Esasen, eski verileri kullanarak görünümü yeniden yapılandırmanız gerekir. Bu yüzden, ilerleme çubuğundan sayısal durum talepleri talep etmenizi ve yeni bir 'onStart' elde ettiğinizde yeni bir görünüm oluşturmanızı öneririm. Yeni bir etkinlik alırsanız da hazırlıksız hatırlayamıyorum, ancak belgelerin içinden bazı avlar yardımcı olacaktır.
haseman

6
Son zamanlarda onunla oynadıktan sonra, uygulamanızın yönünü değiştirdiğinde yeni bir etkinlik aldığınızı aktarabilirim. (Ayrıca yeni bir görünüm elde edersiniz) Eski görünümü güncellemeye çalışırsanız, eski görünümün geçersiz bir uygulama bağlamı (eski etkinliğiniz) olduğu için bir istisna alırsınız. MyActivity.getApplicationContext () ileterek bu sorunu çözebilirsiniz. etkinliğin kendisine bir işaretçi yerine.
haseman

1
Birisi bu bağlamda
uçucunun

2
@Nepster Evet, ben de bunu merak ediyordum. Birisi uçucu hakkında açıklama yaparsa harika olur.
RestInPeace

261

Düzenleme: Google mühendisleri , bu StackOverflow yayınında Dianne Hackborn (aka hackbod ) tarafından açıklandığı gibi bu yaklaşımı önermez . Daha fazla bilgi için bu blog yayınına göz atın .


Bunu manifest'teki etkinlik bildirimine eklemelisiniz:

android:configChanges="orientation|screenSize"

bu yüzden benziyor

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

Mesele, yapılandırmada bir değişiklik meydana geldiğinde sistemin etkinliği yok etmesidir. Bkz. ConfigurationChanges .

Bu yüzden bunu yapılandırma dosyasına koymak, sistemin etkinliğinizi yok etmesini önler. Bunun yerine onConfigurationChanged(Configuration)yöntemi çağırır .


21
Bu kesinlikle en iyi çözümdür; basitçe düzeni döndürür (ilk etapta beklediğiniz davranış). Sadece android koyduğunuzdan emin olun: configChanges = "orientation | keyboardHidden" (yatay klavyeye sahip telefonlar nedeniyle)
nikib3ro

24
Bu beklediğim davranış gibi görünüyor. Bununla birlikte, belgeler etkinliğin yok edildiğini gösterir "çünkü yerleşim dosyaları da dahil olmak üzere herhangi bir uygulama kaynağı herhangi bir yapılandırma değerine göre değişebilir. Bu nedenle bir yapılandırma değişikliğini işlemenin tek güvenli yolu tüm kaynakları yeniden almaktır". Ayrıca orientation, yapılandırmanın değişmesi için daha birçok neden keyboardHiddenvar : (wiki cevabını zaten düzenledim), uiMode(Örneğin, araba moduna girme veya çıkma; gece modu değiştirme), vb. iyi bir cevap.
rds

116
Bu kabul edilebilir bir çözüm değildir. Sadece gerçek meseleyi maskeliyor.
rf43

18
Çalışır, ancak Google tarafından önerilmez.
Ed Burnette

21
Lütfen bu yaklaşımı burada takip etmeyin. DDosAttack tamamen doğru. Bir indirme veya uzun süren başka bir şey için ilerleme durumu iletişim kutusu oluşturduğunuzu düşünün. Kullanıcı olarak bu etkinlikte kalmayacak ve ona bakmayacaksınız. Ana ekrana veya oyun ya da telefon görüşmesi gelebilecek ya da sonunda etkinliğinizi yok edecek başka bir kaynak aç gibi başka bir uygulamaya geçersiniz. Ve ondan sonra? O temiz küçük hile ile çözülmemiş aynı eski sorunla karşı karşıyasınız. Etkinlik, kullanıcı geri geldiğinde yeniden oluşturulur.
tiguchi

68

İşlerin 'Android Way'i ile uyumlu olan bu sorunlar için sağlam bir çözüm buldum. IntentService desenini kullanarak uzun süredir tüm işlemlerim var.

Yani, etkinliklerim niyetleri yayınlıyor, IntentService işi yapıyor, veriyi DB'ye kaydediyor ve daha sonra yapışkan hedefler yayınlıyor . Yapışkan kısım önemlidir, böylece kullanıcı işi başlattıktan sonraki süre boyunca Etkinlik duraklatılmış ve IntentService'ten gerçek zamanlı yayını kaçırsa bile, yine de çağıran Aktiviteden gelen verileri yanıtlayabilir ve alabiliriz. ProgressDialogs bu desen ile oldukça güzel çalışabilir onSaveInstanceState().

Temel olarak, kaydedilen örnek paketinde çalışan bir ilerleme iletişim kutusu olan bir bayrağı kaydetmeniz gerekir. Etmeyin Tüm bu Aktivite sızıntı çünkü ilerleme iletişim nesneyi kaydedin. İlerleme iletişim kutusunda kalıcı bir tanıtıcı olması için, uygulama nesnesinde zayıf bir başvuru olarak saklıyorum. Yön değişikliği veya Etkinliğin duraklamasına (telefon görüşmesi, kullanıcı eve vurur vb.) Neden olan ve devam eden başka bir şeyde, eski iletişim kutusunu kapatır ve yeni oluşturulan Etkinlik'te yeni bir iletişim kutusu yeniden oluştururum.

Belirsiz ilerleme iletişim kutuları için bu kolaydır. İlerleme çubuğu stili için, ilerlemeyi takip etmek için pakette bilinen son ilerlemeyi ve etkinlikte yerel olarak kullandığınız her türlü bilgiyi koymanız gerekir. İlerlemeyi geri yüklerken, bu bilgiyi ilerleme çubuğunu öncekiyle aynı şekilde yeniden oluşturmak ve ardından mevcut durumlara göre güncellemek için kullanırsınız.

Özetlemek gerekirse, uzun süren görevleri bir IntentService içine akılcı şekilde kullanmakla birleştirmek onSaveInstanceState(), diyalogları etkin bir şekilde takip etmenizi ve daha sonra Etkinlik yaşam döngüsü olayları boyunca geri yüklemenizi sağlar. Etkinlik kodunun ilgili parçaları aşağıdadır. Ayrıca Yapışkan hedefleri uygun şekilde işlemek için BroadcastReceiver'ınızda mantığa ihtiyacınız olacaktır, ancak bunun kapsamı dışındadır.

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

güzel bir çözüm gibi görünüyor
Derekyy

"IntentService kalıbını kullanarak uzun süredir devam eden tüm işlemlerim var." Bu mükemmel bir çözüm değildir, çünkü
toplardan

28

Ben de aynı problemle karşılaştım. Etkinliğimin bir URL'den bazı verileri ayrıştırması gerekiyor ve yavaş. Bunu yapmak için bir iş parçacığı oluşturmak, sonra bir ilerleme iletişim kutusu göstermek. İş parçacığının, Handlertamamlandığında UI iş parçacığına bir ileti göndermesine izin verdim . İçinde Handler.handleMessage, veri nesnesi (şimdi hazır) iş parçacığından almak ve kullanıcı arabirimine doldurmak. Yani örneğinize çok benziyor.

Çok fazla deneme yanılma sonrasında bir çözüm bulduğum anlaşılıyor. En azından şimdi, iş parçacığı yapılmadan önce veya sonra herhangi bir anda ekranı döndürebilirim. Tüm testlerde iletişim kutusu düzgün şekilde kapatılır ve tüm davranışlar beklendiği gibi olur.

Yaptığım şey aşağıda gösterilmiştir. Amaç, veri modelimi ( mDataObject) doldurup ardından kullanıcı arayüzüne yerleştirmektir. Sürpriz olmadan ekran döndürmeye izin vermelidir.

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

Bu benim için işe yarıyor. Bu Android tarafından tasarlanan "doğru" yöntem olup olmadığını bilmiyorum - onlar bu "ekran döndürme sırasında etkinliği yok / yeniden oluşturmak" aslında işleri kolaylaştırır, bu yüzden çok zor olmamalı sanırım.

Kodumda bir sorun görürseniz bana bildirin. Yukarıda belirtildiği gibi, herhangi bir yan etkisi olup olmadığını gerçekten bilmiyorum.


1
Çok teşekkürler! Sorunumu çözmem için ipucu onRetainNonConfigurationInstance()ve getLastNonConfigurationInstance()bana yardımcı oldu. Başparmak havaya!
sven

15

Algılanan orijinal sorun, kodun bir ekran yönlendirme değişikliğinden sağ çıkmayacağıydı. Görünüşe göre bu, programın UI çerçevesinin bunu yapmasına izin vermek yerine ekran yönünü değiştirmesini sağlayarak (onDestroy'u çağırarak) "çözüldü".

Altta yatan sorun, programın onDestroy () 'da hayatta kalmayacağı ise, kabul edilen çözümün, programı diğer ciddi sorunlar ve güvenlik açıklarıyla terk eden bir geçici çözüm olduğunu söyleyebilirim. Android çerçevesinin, kontrolünüz dışındaki durumlar nedeniyle etkinliğinizin neredeyse her zaman yok edilme riski altında olduğunu özellikle belirttiğinizi unutmayın. Bu nedenle, etkinliğiniz yalnızca ekran yönelimi değişikliği değil, herhangi bir nedenle onDestroy () ve sonraki onCreate () yöntemlerinden sağ çıkabilmelidir.

OP'nin sorununu çözmek için ekran yönü değişikliklerini kendiniz halletmeyi kabul edecekseniz, onDestroy () 'un diğer nedenlerinin aynı hatayla sonuçlanmadığını doğrulamanız gerekir. Bunu yapabilir misin? Değilse, "kabul edilen" cevabın gerçekten çok iyi olup olmadığını sorgulayacağım.


14

Benim çözümüm ProgressDialogkendi sınıfımı almak için sınıfı genişletmekti MyProgressDialog.
Yeniden tanımladım show()ve dismiss()göstermeden önce yönü kilitlemek ve reddedildiğinde Dialogkilidini açmak için yöntemler Dialog. Bu nedenle Dialog, gösterildiğinde ve cihazın yönü değiştiğinde, ekranın yönü dismiss()çağrılana kadar kalır , ardından ekran yönü sensör-değerlerine / cihaz yönüne göre değişir.

İşte benim kod:

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

Aynı sorunla karşılaştım ve ProgressDialog'u kullanmaya başlamayan bir çözüm buldum ve daha hızlı sonuç aldım.

Ne yaptım içinde bir ProgressBar olan bir düzen oluşturmak oldu.

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

Sonra onCreate yönteminde aşağıdakileri yapın

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

Sonra bir iş parçacığında uzun görev yapmak ve bittiğinde bir Runnable içerik görünümü bu etkinlik için kullanmak istediğiniz gerçek düzen ayarlayın.

Örneğin:

mHandler.post(new Runnable(){

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

Yaptığım şey bu ve ProgressDialog'u göstermekten daha hızlı çalıştığını gördüm ve daha az müdahaleci ve bence daha iyi bir görünüme sahip.

Ancak, ProgressDialog'u kullanmak istiyorsanız, bu cevap sizin için değildir.


Bu çözüm basit bir kullanım durumunda zariftir, ancak dezavantajları vardır. Tam içerik görünümünü yeniden oluşturmanız gerekir. setContentView(R.layout.my_layout);yeterli değil; tüm dinleyicileri ayarlamanız, verileri sıfırlamanız vb. gerekir
rds

@rds haklısın. Bu gerçekten sadece basit bir durum için bir çözümdür veya görünümünüzü göstermeden önce onCreate yönteminizde ağır bir kaldırma yapmanız gerekiyorsa.
Pzanno

Tam anlamıyorum. OnCreate () 'deki dinleyiciler yerine, normalde yaptığımız gibi, onları run ()' da kurabiliriz. Burada bir şey mi eksik?
Code Poet

7

Henüz başka bir yerde görmediğim bir çözüm buldum. Oryantasyon değişikliğinde yok edilen ve yeniden oluşturulan etkinliklerde bunu yapmaya çalışmak yerine arka plan görevlerinizin olup olmadığını bilen özel bir uygulama nesnesi kullanabilirsiniz. Burada bununla ilgili blog yazdım .


1
Özel oluşturma Application, normalde genel uygulama durumunu korumak için kullanılır. İşe yaramadığını söylemiyorum, ama aşırı karmaşık görünüyor. "Normalde Uygulama alt sınıfına gerek yoktur." Sonxurxo'nun cevabını büyük ölçüde tercih ederim.
rds

7

Bu rotasyon sorununu ele alma yaklaşımım için katkıda bulunacağım. Bu, kullanmadığı için OP ile ilgili olmayabilir AsyncTask, ancak belki de diğerleri yararlı bulacaktır. Oldukça basit ama bu işi benim için yapıyor gibi görünüyor:

Adlı bir iç içe AsyncTasksınıf ile bir giriş etkinliği var BackgroundLoginTask.

Benim 's görevden BackgroundLoginTaskçağırma üzerine bir boş kontrol eklemek dışında sıradan dışında bir şey yapmıyorum ProgressDialog:

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

Bunun amacı, Activitygörünür olmadığı sırada arka plan görevinin bittiği durumu ele almaktır ve bu nedenle, ilerleme iletişim kutusu zaten onPause()yöntem tarafından kapatılmıştır .

Sonra, ana Activitysınıfımda, sınıfım için genel statik tutamaçlar oluşturuyorum AsyncTaskve ProgressDialog( AsyncTaskiç içe geçmiş olan, bu değişkenlere erişebilir):

private static BackgroundLoginTask backgroundLoginTask;
private static ProgressDialog pleaseWaitDialog;

Bu iki amaca hizmet eder: Birincisi, nesneye Activityher zaman AsyncTaskyeni, döndürülmüş bir etkinlikten bile erişmeme izin verir . İkincisi, bir döndürmeden sonra bile BackgroundLoginTaskerişime ve reddetmeye izin veriyor ProgressDialog.

Sonra, bunu ön plana çıkarken (bu çirkin "kapanmaya zorla" çökmesini önleyerek) onPause()ilerleme iletişim kutusunun kaybolmasına neden olarak ekliyorum Activity:

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

Son olarak, onResume()yöntemimde aşağıdakiler var :

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

Bu , yeniden oluşturulduktan Dialogsonra yeniden görünmesini sağlar Activity.

İşte tüm sınıf:

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

    }
}

Hiçbir şekilde deneyimli bir Android geliştiricisiyim, bu yüzden yorum yapmaktan çekinmeyin.


1
İlginç! Özellikle AsyncTask kullananlarımız için. Sadece çözümünüzü denedim ve çoğunlukla işe yarıyor gibi görünüyor. Bir sorun var: ProgressDialog bir rotasyondan sonra biraz erken sona eriyor gibi görünüyor. Tam olarak ne olduğunu ve nasıl çözüleceğini görmek için oynayacağım. Ama artık bu kazaları yapmıyorum!
Scott Biggs

1
Bir düzeltme buldum. Görünüşe göre buradaki sorun statik ProgressDialog. Döndürmeler ProgressDialog'u böldüğünde, bazen yeni Etkinlik'te yeniden başlatıldıktan sonra .dismiss () yöntemini çağırır. ProgressDialog'u her bir Etkinlikle yaratarak, bu yeni ProgressDialog'un eski Etkinlikle birlikte öldürülmemesini sağlıyoruz. Ayrıca, ProgressDialog'un her ne zaman işten çıkarıldığında null değerine ayarlandığından emin oldum (çöp toplamaya yardımcı olmak için). Burada bir çözümümüz var! AsyncTask kullananlara şerefe!
Scott Biggs

4

Uzun görevi ayrı bir sınıfa taşıyın. Bir konu-gözlemci modeli olarak uygulayın. Aktivite her oluşturulduğunda kayıt olurken ve kapatırken görev sınıfıyla olan kaydı silin. Görev sınıfı AsyncTask kullanabilir.


1
Bunun nasıl yardımcı olacağını görmüyorum. Bunun gördüğüm sorunları nasıl önlediğini daha ayrıntılı açıklar mısınız?
Heikki Toivonen

1
Haseman'ın arka ucun UI öğelerine erişmesini engellediğini ve UI'yi arka uçtan ayırabileceğimiz gibi, arka uç ayrı iş parçacığında çalışır ve ekran yeniden yönlendirildikten ve Durum güncellemeleri için Arka Uç görevi ile Kayıt Ol / Kaydı Sil . Bunu kullanarak çözdüğüm gerçek örnek bir indirme görev var, ben ayrı bir iş parçacığına taşındı, iş parçacığı oluşturulduğunda her zaman onunla kayıt-kayıt.
Vinay

Tamam, bu konuyu tekrar gözden geçiriyorum ve hala bu cevabı tam olarak anladığımı sanmıyorum. Ekran yönlendirmesi değişikliği sırasında kesmek istemediğimiz uzun soluklu bir ağ işlemi yapmak için asyncTask'ı başlatan ana etkinliğimizi varsayalım. Yeni etkinliğin eski etkinliğin başlattığı AsyncTask'a nasıl ileti gönderebileceğini göremiyorum. Kod örneği verebilir misiniz?
Heikki Toivonen

@ Heikki, benim uygulamam ne demek istediğinin altında mı?
beetstra

4

Hile, onPreExecute / onPostExecute sırasında AsyncTask içindeki iletişim kutusunu her zamanki gibi göstermektir / reddetmektir, ancak yön değiştirme durumunda, etkinlikte iletişim kutusunun yeni bir örneğini oluşturur / gösterir ve referansını göreve iletir.

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

Ben böyle yaptım:

    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 {

                }
            }
        }

    }

Ayrıca sizin için işe yarayıp yaramadığını bana bildirmeyi deneyebilirsiniz


OnDestroy kodu, Geliştirici sayfasından hiç çalıştırılamayabilir : Yukarıdaki tabloda yer alan "Killable" sütununa dikkat edin - bu yöntem killable olarak işaretlenen yöntemler için, bu yöntem, sistemi herhangi bir zamanda kodunun başka bir satırı yürütülmeden "
ilomambo

Ben sıkıca bu "onRetainNonConfigurationInstance ()" Bu gibi durumlarda kullanılmak üzere bir yöntem ... nyc çalışma olduğuna inanıyoruz
Nitin Bansal

Sachin Gurnani'nin sorunumu düzeltmek için statik bildirimi kullandığından benzer bir sorunla karşılaşıyorum. stackoverflow.com/questions/12058774/…
Steven Du

2

ServiceTüm ağır kaldırma işlemlerini gerçekleştiren bir arka plan oluşturursanız (tcp istekleri / yanıt, utanmaz) Viewve Activitypencereden sızmadan veya veri kaybetmeden imha edilebilir ve yeniden oluşturulabilir. Bu, Android tarafından önerilen davranışa izin verir; bu, her yapılandırma değişikliğindeki (örneğin her yönlendirme değişikliği için) bir Etkinliği yok etmektir .

Biraz daha karmaşıktır, ancak sunucu isteğini, veri öncesi / sonrası işlemeyi vb. Çağırmanın en iyi yoludur.

ServiceHer isteği bir sunucuya sıralamak için bile kullanabilirsiniz , bu nedenle bu şeyleri işlemeyi kolay ve verimli hale getirir.

Geliştirici kılavuzunun tam bir bölümü varServices .


A Service, bir çalışmadan daha fazla iştir, AsyncTaskancak bazı durumlarda daha iyi bir yaklaşım olabilir. Mutlaka daha iyi değil, değil mi? Bununla birlikte, bunun anadan ProgressDialogsızan sorunun nasıl çözüldüğünü anlamıyorum Activity. Nerede başlıyorsunuz ProgressDialog? Nereden çıkarıyorsunuz?
rds

2

Etkinliğin bir ekran yönlendirme değişikliğinde yok edilmesine izin veren bir uygulama var, ancak yine de yeniden oluşturulan etkinlikteki iletişim kutusunu başarıyla yok ediyor. ...NonConfigurationInstanceArka plan görevini yeniden oluşturulan etkinliğe eklemek için kullanıyorum . Normal Android çerçevesi, iletişim kutusunun kendisini yeniden oluşturuyor, orada hiçbir şey değişmiyor.

AsyncTask'ı 'sahip olma' etkinliği için bir alan ve bu sahibi güncelleştirme yöntemi ekleyerek alt sınıflara ayırdım.

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

  ...
}

Etkinlik sınıfımda backgroundTask'ait' arka plan görevine başvuran bir alan ekledim ve bu alanı onRetainNonConfigurationInstanceve tuşlarını kullanarak güncelliyorum 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;
  }
  ...
}

Daha da geliştirilmesi için öneriler:

  • backgroundTaskGörev bittikten sonra, onunla ilişkili bellek veya diğer kaynakları serbest bırakmak için etkinlikteki başvuruyu temizleyin .
  • ownerActivityHemen yeniden oluşturulmaması durumunda etkinlik yok edilmeden önce arka plan görevindeki referansı temizleyin .
  • Oluşturmak BackgroundTaskFarklı görev türlerinin aynı sahiplik etkinliğinden çalıştırılmasına izin vermek için arabirim ve / veya koleksiyon .

2

İki mizanpajınız varsa, tüm UI iş parçacığı sonlandırılmalıdır.

AsynTask kullanıyorsanız, yöntemi geçerli etkinlik .cancel()yöntemi içinde kolayca çağırabilirsiniz 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 için buradaki "Görevi iptal etme" bölümünde daha fazla bilgi bulabilirsiniz .

Güncelleme: Yalnızca çalışma durumundayken iptal edilebildiği için durumu kontrol etmek için koşul eklendi. Ayrıca AsyncTask'in yalnızca bir kez yürütülebileceğini unutmayın.


2

Jfelectron'un çözümünü uygulamaya çalıştı çünkü bu konulara 'şeylerin' Android Yolu 'ile uyumlu olan " sağlam bir çözüm " ama bahsedilen tüm unsurları aramak ve bir araya getirmek biraz zaman aldı. Bu biraz farklı sona erdi ve bence daha zarif, burada bütünüyle yayınlanan bir çözüm.

Uzun süren görevi ayrı bir iş parçacığında gerçekleştirmek için bir etkinlikten tetiklenen bir IntentService kullanır. Hizmet, iletişim kutusunu güncelleyen etkinliğe yapışkan Yayın Amaçlarını geri gönderir. Etkinlik, uygulama nesnesinde veya savedInstanceState paketinde kalıcı verilerin geçirilmesi gereğini ortadan kaldırmak için showDialog (), onCreateDialog () ve onPrepareDialog () yöntemlerini kullanır. Bu, uygulamanızın nasıl kesintiye uğradığına bakılmaksızın çalışmalıdır.

Etkinlik Sınıfı:

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 Sınıfı:

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

Bildirim dosya girişleri:

uygulama bölümünden önce:

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

uygulama içi

service android:name=".MyService"

2

Bu benim önerdiğim çözüm:

  • AsyncTask veya İş Parçasını burada açıklandığı gibi tutulan bir Fragmana taşıyın . Tüm ağ çağrılarını parçalara taşımanın iyi bir uygulama olduğuna inanıyorum. Halihazırda fragman kullanıyorsanız, bunlardan biri aramalardan sorumlu olabilir. Aksi takdirde, bağlantılı makalenin önerdiği gibi, yalnızca isteği yapmak için bir parça oluşturabilirsiniz.
  • Parça, görevin tamamlandığını / başarısız olduğunu bildirmek için bir dinleyici arabirimi kullanır. Oradaki oryantasyon değişiklikleri için endişelenmenize gerek yok. Parça her zaman geçerli etkinliğe doğru bağlantıya sahip olacaktır ve ilerleme iletişim kutusu güvenli bir şekilde devam ettirilebilir.
  • İlerleme iletişim kutunuzu sınıfınızın bir üyesi yapın. Aslında bunu tüm diyaloglar için yapmalısınız. OnPause yönteminde bunları kapatmalısınız, aksi takdirde yapılandırma değişikliğinde bir pencere sızdırırsınız. Meşgul hal parça tarafından tutulmalıdır. Parça etkinliğe eklendiğinde, çağrı devam ediyorsa ilerleme iletişim kutusunu tekrar açabilirsiniz. void showProgressDialog()Bu amaçla fragman-aktivite dinleyici arayüzüne bir yöntem eklenebilir.

Mükemmel bir çözüm, ama bu cevabın neden diğerlerinden gölgelendiğini anlamıyorum !!!
blackkara

2

Aynı durumla karşılaştım. Yaptığım şey, tüm uygulamadaki ilerleme iletişim kutusu için yalnızca bir örnek almaktı.

İlk olarak, sadece bir örnek almak için bir DialogSingleton sınıfı oluşturdum (Singleton kalıp)

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

Bu sınıfta gösterdiğim gibi, özellik olarak ilerleme iletişim kutusu var. Bir ilerleme iletişim kutusu göstermem gerektiğinde, benzersiz örneği alıyorum ve yeni bir ProgressDialog oluşturuyorum.

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

Arka plan göreviyle işim bittiğinde, benzersiz örneği tekrar çağırır ve iletişim kutusunu kapatırım.

DialogSingleton.GetInstance().DialogDismiss(this);

Arka plan görev durumunu paylaşılan tercihlerime kaydediyorum. Ekranı döndürdüğümde, bu etkinlik için çalışan bir görevim olup olmadığını soruyorum: (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).

Bir arka plan görevi çalıştırmaya başladığımda:

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

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

Bir arka plan görevi çalıştırmayı bitirdiğimde:

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

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

Umut ediyorum bu yardım eder.


2

Bu, kenar çubuğunda bir nedenden dolayı ortaya çıkan çok eski bir soru.

Arka plan görevinin yalnızca etkinlik ön planda iken hayatta kalması gerekiyorsa, "yeni" çözüm, arka plan iş parçacığını (veya tercihen AsyncTask) bu geliştirici kılavuzunda ve çok sayıda Soru-Cevap bölümünde açıklandığı gibi tutulan bir fragmanda barındırmaktır .

Korunan bir fragman, aktivite bir konfigürasyon değişikliği için imha edilirse hayatta kalır, fakat aktivite arka planda veya arka istifte imha edildiğinde olmaz . Bu nedenle, isChangingConfigurations()yanlışsa arka plan görevi yine de kesilmelidir onPause().


2

Ben android bir taze ve bunu denedim ve çalıştı.

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

HER ŞEYİ denedim. Denemeyi günler geçirdim. Etkinliğin dönmesini engellemek istemedim. Benim senaryom:

  1. Kullanıcıya dinamik bilgileri gösteren bir ilerleme iletişim kutusu. Örn: "Sunucuya bağlanıyor ...", "Veriler indiriliyor ..." vb.
  2. Ağır işleri yapan ve iletişim kutusunu güncelleyen bir iş parçacığı
  3. Kullanıcı arayüzünü en sondaki sonuçlarla güncelleme.

Sorun, ekranı döndürürken kitaptaki her çözümün başarısız olmasıydı. Bu durumlarla başa çıkmanın doğru Android yolu olan AsyncTask sınıfıyla bile. Ekranı döndürürken, başlangıç ​​iş parçacığının çalıştığı mevcut Bağlam kaybolur ve gösterilen iletişim kutusuyla karışır. Kodda kaç tane numara eklediğim önemli değil (sorunların iş parçacıklarına geçmesi, iş parçacığı durumlarının döndürülerek tutulması, vb.) Ne olursa olsun sorun her zaman Diyalog'du. Sondaki kod karmaşıklığı her zaman çok büyüktü ve her zaman yanlış gidebilecek bir şey vardı.

Benim için işe yarayan tek çözüm Etkinlik / Diyalog hilesiydi. Basit ve deha ve hepsi rotasyona dayanıklı:

  1. Bir Diyalog oluşturmak ve göstermek istemek yerine, android: theme = "@ android: style / Theme.Dialog" ile manifest'te ayarlanmış bir Etkinlik oluşturun. Yani, sadece bir diyalog gibi görünüyor.

  2. ShowDialog (DIALOG_ID) yerine startActivityForResult (yourActivityDialog, kodunuz) yazın;

  3. Yürütme iş parçacığından (hatalar bile) sonuç almak ve kullanıcı arabirimini güncelleştirmek için çağıran Aktivitede onActivityResult öğesini kullanın.

  4. 'ActivityDialog' öğenizde, uzun görevleri yürütmek için iş parçacığı veya AsyncTask ve ekranı döndürürken "iletişim kutusu" durumunu kaydetmek için onRetainNonConfigurationInstance komutunu kullanın.

Bu hızlı ve iyi çalışıyor. Hala diğer görevler için diyaloglar ve ekranda sürekli bir diyalog gerektirmeyen bir şey için AsyncTask kullanıyorum. Ancak bu senaryo ile her zaman Aktivite / İletişim Kutusu modelini tercih ediyorum.

Ve ben denemedim, ama iş parçacığı çalışırken, bir şeyleri hızlandırırken, Arama Etkinliğinin dönmesine izin verirken, Etkinlik / İletişim Kutusunun dönmesini engellemek bile mümkündür.


Bunun dışında parametrelerin izin verilenlerden Intentdaha kısıtlayıcı olan bir ObjectAsyncTask
andan

@Rui Ben de bu yöntemi geçen yıl kullandım. Bu da yanlış olsa da şimdi başıma geldi. Bu sorunu 'çözmenin' yolu Google olsaydı neden bir İletişim Kutusu olsun ki? Gördüğüm sorun, ActivityA'dan (Theme.Dialog) ActivityA'dan açarsanız, ActivityA'nın Etkinlik yığını üzerinde aşağı kaydırıldığından, gerekirse işletim sistemi tarafından öldürmeye hazır olarak işaretlenmiş olmasıdır. Bu nedenle, uzun süren bir işleminiz varsa ve bir çeşit sahte ilerleme 'iletişim kutusu' gösteriyorsanız ve çok uzun sürdüyse ve bellek azaldıysa ... ActivityA öldürüldü ve ilerleme tamamlandığında geri dönecek bir şey yok.
rf43

1

Bugünlerde bu tür konuları ele almanın çok daha farklı bir yolu var. Tipik yaklaşım:

1. Verilerinizin kullanıcı arayüzünden düzgün bir şekilde ayrıldığından emin olun:

Bir muhafaza bir arka plan işlem olmalıdır olan şey Fragment(bu set Fragment.setRetainInstance(). Bu şey verileri tutulur muhafaza edilmesini istediğiniz o dayalı 'kalıcı veri depolama' olur. Oryantasyon değişikliği etkinliğinde sonra bu Fragmenthala orijinal erişilebilir olacaktır bir FragmentManager.findFragmentByTag()çağrı yoluyla belirtin (oluşturduğunuzda , a'ya bağlı olmadığı için bir kimlik değil, bir etiket vermelisiniz View).

Bkz Handling Süre Değişiklikler doğru bunu ve neden en iyi seçenektir hakkında bilgi almak için geliştirilen rehber.

2. Arka plan işlemleri ve kullanıcı arayüzünüz arasında doğru ve güvenli bir şekilde arabirim oluşturduğunuzdan emin olun:

Sen gerekir ters senin bağlama işlemini. Şu anda arka plan işleminiz kendisini a'ya ekler View- bunun yerine kendini arka plan işlemine eklemeniz Viewgerekir. Daha mantıklı değil mi? ViewArka plan işlemi bağımlı değildir, oysa bireyin aksiyon, arka plan işlemi bağlıdır Viewstandart bağlantı değiştirme .Bu vasıtasıyla Listenerarayüz. - (bir olup olmadığını ne olursa olsun sınıf sürecinizi Say AsyncTask, Runnablebir tanımlar ya neyse) OnProcessFinishedListenerişlem varsa o dinleyici çağırmalıdır yapıldığında,.

Bu cevap özel dinleyicilerin nasıl yapılacağına dair kısa ve öz bir açıklamadır.

3. Kullanıcı arayüzü oluşturulduğunda kullanıcı arayüzünüzü veri işlemine bağlayın (yönlendirme değişiklikleri dahil):

Şimdi, arka plan görevini mevcut yapınız ne olursa olsun arayüzlemek konusunda endişelenmelisiniz View. Yönlendirme değişikliklerinizi düzgün bir şekildeconfigChanges yönetiyorsanız (insanların her zaman tavsiye ettiği saldırı değil ), Dialogsisteminiz tarafından yeniden yaratılır. Bu önemlidir, yönelim değişikliğinde tüm Dialogyaşam döngüsü yöntemlerinizin geri çağrıldığı anlamına gelir . Yani bu yöntemlerden herhangi birinde ( onCreateDialoggenellikle iyi bir yerdir), aşağıdaki gibi bir çağrı yapabilirsiniz:

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

Dinleyiciyi ayarlamanın bireysel uygulamanıza en uygun olan yere karar vermek için Parça yaşam döngüsüne bakın .

Bu, bu soruda sorulan genel soruna sağlam ve eksiksiz bir çözüm sağlamak için genel bir yaklaşımdır. Bireysel senaryonuza bağlı olarak muhtemelen bu cevapta birkaç küçük parça eksiktir, ancak bu genellikle yön değiştirme olaylarını doğru bir şekilde işlemek için en doğru yaklaşımdır.


1

yön değiştirirken konuları işlemek için daha kolay bir çözüm buldum. Aktivitenize / parçanıza statik bir referans tutabilir ve kullanıcı arayüzünde işlem yapmadan önce bunun boş olup olmadığını doğrulayabilirsiniz. Ben de bir try catch kullanmanızı öneririz:

 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

Bir iletişim kutusunun yön değiştirme olaylarını tespit etmekte zorlanıyorsanız, AKTİVİTE REFERANSININ BAĞIMSIZLIĞI , bu yöntem heyecan verici bir şekilde işe yarar. Bunu kullanıyorum çünkü birden fazla farklı aktivitede gösterilebilecek kendi iletişim sınıfım var, bu yüzden hangi Etkinliğin gösterildiğini her zaman bilmiyorum. Bu yöntemle AndroidManifest'i değiştirmenize gerek yok, Etkinlik referansları hakkında endişelenmenize, ve özel bir iletişim kutusuna ihtiyacınız yok (benim gibi). Ancak, belirli bir görünümü kullanarak yön değişikliklerini algılayabilmeniz için özel bir içerik görünümüne ihtiyacınız vardır. İşte benim örnek:

Kurmak

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

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

        //DO SOMETHING HERE!! :D
    }
}

Uygulama 1 - İletişim Kutusu

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

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

Uygulama 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

Karşılaştığımda bu benim çözümüm: ProgressDialogbir Fragmentçocuk değil , bu nedenle yapılandırma sınıfları için gösterilen iletişim kutusunu korumak için özel sınıfım " ProgressDialogFragment" DialogFragmentbunun yerine genişletilebilir .

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

Sorun, diyalog hala gösterilse de, varsayılan boş dizeye sıfırlandıkça ekran döndürme sırasında iletişim kutusu başlığını ve mesajını korumaktı.

Bunu çözmek için 2 yaklaşım vardır:

İlk yaklaşım: Bildirim dosyasında yapılandırma değişikliği sırasında durumu korumak için iletişim kutusunu kullanan etkinliği yapın:

android:configChanges="orientation|screenSize|keyboardHidden"

Bu yaklaşım Google tarafından tercih edilmez.

İkinci yaklaşım: etkinliğin onCreate()yönteminde, boş değilse, başlık ve mesajla tekrar DialogFragmentyeniden oluşturarak hesabınızı korumanız gerekir :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

Gerçek olamayacak kadar çok 'hızlı ve kirli' görünüyor, lütfen kusurları işaret edin, ancak çalıştığım şey ...

AsyncTask'ımın onPostExecute yönteminde, ilerleme iletişim kutusu için '.dismiss'i bir try / catch bloğuna (boş bir yakalama ile) tamamladım ve sonra yükseltilen istisnayı göz ardı ettim. Yapmak yanlış görünüyor ama hiçbir kötü etkisi var gibi görünüyor (en azından sonradan ne yaptığım için ekstra olarak uzun süren sorgu sonucunda geçen başka bir faaliyet başlatmaktır)


Android platformları pencerenin sızdırıldığını söylediğinde aslında bellek sızıntısı olmadığını mı söylüyorsunuz?
rds

Benim ilerleme iletişim aktivite sınıfının bir üye değişkeni olduğunu, bu yüzden etkinlik yok ve yeniden oluşturulduğunda çöp toplanacağını ve hiçbir sızıntı olacağını varsaydım. Yanlış mıyım?
Simon

Evet, bence yanlış. Dediğiniz gibi, Activitybir referans var Dialog. Yapılandırma değiştirildiğinde, ilki Activityyok edilir, yani tüm alanlar ayarlanır null. Ancak düşük seviyenin WindowManagerde bir referansı vardır Dialog(henüz göz ardı edilmediğinden). Yeni Activitybir yeni Dialog(in preExecute()) oluşturmaya çalışır ve pencere yöneticisi bunu yapmanıza engel olan önemli bir istisna oluşturur. Gerçekten de, eğer öyleyse, temiz bir şekilde yok etmenin hiçbir yolu olmazdı, Dialogbu yüzden ilkine bir referans tutmak Activity. Haklı mıyım?
rds

0

En basit ve en esnek çözüm, ProgressBar'a statik başvuru ile bir AsyncTask kullanmaktır . Bu, yön değiştirme problemlerine kapsüllenmiş ve böylece tekrar kullanılabilir bir çözüm sağlar. Bu çözüm, internetten indirmeler, Servislerle iletişim ve dosya sistemi taramaları dahil olmak üzere çeşitli asenkron görevler için bana iyi hizmet etti . Çözüm, birden fazla android sürümü ve telefon modelinde iyi test edilmiştir. DownloadFile.java özel ilgi ile tam bir demo burada bulabilirsiniz

Aşağıdakileri bir konsept örneği olarak sunuyorum

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 Etkinliğinde kullanım basittir

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

Yönleri değiştirdiğinizde Android bu etkinliği öldürür ve yeni etkinlik oluşturur. Rx java ile güçlendirme kullanmanızı öneririm. hangi kolu otomatik olarak çöküyor.

Sonradan arama çağrısında bu yöntemi kullanın.

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

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.