Arka plan görevi, ilerleme iletişim kutusu, yön değiştirme -% 100 çalışma çözümü var mı?


235

İnternetten bazı verileri arka plan iş parçacığında indiriyorum (kullanıyorum AsyncTask) ve indirirken bir ilerleme iletişim kutusu görüntülüyorum. Yön değişikliği, Etkinlik yeniden başlatılır ve sonra AsyncTask'ım tamamlanır - Progess iletişim kutusunu kapatmak ve yeni bir Etkinlik başlatmak istiyorum. Ancak dismissDialog'u çağırmak bazen bir istisna atar (muhtemelen Etkinlik yok edildiğinden ve yeni Etkinlik henüz başlatılmadığından).

Bu tür bir sorunu ele almanın en iyi yolu nedir (kullanıcı yönlendirmeyi değiştirse bile çalışan arka plan iş parçacığından kullanıcı arayüzünü güncelleme)? Google'dan biri "resmi çözüm" sağladı mı?


4
Benim blog yazısı bu konu kudreti yardımına. Konfigürasyon değişikliklerinde uzun süren görevleri tutmakla ilgilidir.
Alex Lockwood

1
Bu soru da ilişkilidir.
Alex Lockwood

Sadece FTR burada ilgili bir gizem var .. stackoverflow.com/q/23742412/294884
Fattie

Yanıtlar:


336

Adım # 1: Make AsyncTaskbir staticiç içe sınıf veya tamamen ayrı bir sınıf, sadece değil bir iç (statik olmayan iç içe) sınıfı.

Adım # 2: Oluşturucu ve bir ayarlayıcı aracılığıyla ayarlanan bir veri üyesi aracılığıyla AsyncTaskbekletmeyi Activitysağlayın.

Adım # 3: Oluştururken AsyncTaskakımı Activitykurucuya verin.

Adım # 4: Orijinal, şimdi devam eden aktiviteden çıkardıktan sonra onRetainNonConfigurationInstance(), iade edin AsyncTask.

Adım 5: In onCreate()eğer getLastNonConfigurationInstance()değil null, senin için döküm AsyncTasksınıf ve görev ile yeni aktiviteyi ilişkilendirme ayarlayıcı arayın.

Adım # 6: adlı etkinlik verisi üyesine başvurmayın doInBackground().

Yukarıdaki tarifi takip ederseniz, hepsi işe yarayacaktır. onProgressUpdate()ve bir onPostExecute()sonrakinin başlangıcı ile onRetainNonConfigurationInstance()bitişi arasında askıya alınır onCreate().

İşte tekniği gösteren örnek bir proje .

Başka bir yaklaşım da AsyncTaskişinizi bir işe sokmak ve taşımaktır IntentService. Bu, yapılacak işin uzun sürebileceği ve kullanıcının faaliyetler açısından ne yaptığına bakılmaksızın (örneğin, büyük bir dosya indirme) devam etmesi gerektiğinde özellikle yararlıdır. IntentEtkinliğin yapılmakta olan işe yanıt vermesini sağlamak için (hâlâ ön plandaysa) sıralı bir yayın kullanabilir ya da Notificationkullanıcıya işin yapılıp yapılmadığını bildirmek için a işareti yükseltebilirsiniz . İşte bu model hakkında daha fazla bilgi içeren bir blog yazısı .


8
Bu ortak soruna verdiğiniz harika yanıt için çok teşekkürler! Tam olarak söylemek gerekirse, AsyncTask'taki etkinliği ayırmamız (boş olarak ayarlamamız) gereken 4. adıma ekleyebilirsiniz. Yine de bu örnek projede iyi bir şekilde gösterilmiştir.
Kevin Gaudin

3
Peki, Aktivite üyelerine erişmem gerekirse ne olur?
Eugene

3
@Andrew: Statik bir iç sınıf veya birkaç nesneye tutunan bir şey oluşturun ve geri gönderin.
CommonsWare

11
onRetainNonConfigurationInstance()kullanımdan kaldırılmıştır ve önerilen alternatif kullanmaktır setRetainInstance(), ancak bir nesne döndürmez. asyncTaskKonfigürasyon değişikliği ile aşağıdakileri yapmak mümkün müdür setRetainInstance()?
Indrek Kõue

10
@SYLARRR: Kesinlikle. Var Fragmenttutun AsyncTask. Var Fragmentçağrıyı setRetainInstance(true)kendi üzerine. İle AsyncTasksadece konuşun Fragment. Şimdi, bir yapılandırma değişikliğinde, Fragment(etkinlik olmasına rağmen) yok edilmez ve yeniden oluşturulmaz ve böylece AsyncTaskyapılandırma değişikliği boyunca korunur.
CommonsWare

13

Kabul edilen cevap çok yardımcı oldu, ancak bir ilerleme iletişim kutusu yok.

Neyse ki sizin için okuyucu, ilerleme iletişim kutusu olan bir AsyncTask için son derece kapsamlı ve çalışan bir örnek oluşturdum !

  1. Döndürme çalışır ve diyalog devam eder.
  2. Görevi ve iletişim kutusunu geri düğmesine basarak iptal edebilirsiniz (bu davranışı istiyorsanız).
  3. Parçalar kullanır.
  4. Etkinliğin altındaki parçanın düzeni, cihaz döndüğünde düzgün bir şekilde değişir.

Kabul edilen cevap statik sınıflarla ilgilidir (üyelerle değil). Ve bunlar, AsyncTask'ın dış sınıf örneğine (gizli) bir işaretçiye sahip olmasını önlemek için gereklidir ; bu, etkinliği yok etmekte bellek sızıntısı haline gelir.
Bananeweizen

Bunu neden statik üyelere koyduğumdan emin değilim, çünkü aslında onları da kullandım ... tuhaf. Düzenlenmiş cevap.
Timmmm

Lütfen bağlantınızı güncelleyebilir misiniz? Buna gerçekten ihtiyacım var.
Romain Pellerin

Maalesef, web sitemi geri yüklemek için uğraşmadım - yakında yapacağım! Ancak bu arada temel olarak bu
yanıttaki

1
Bağlantı sahte; kodun nerede olduğuna dair hiçbir belirti olmadan işe yaramaz bir dizine götürür.
FraktalBob

9

Manifest dosyasını düzenlemeden bu ikileme bir çözüm bulmak için bir hafta çalıştım. Bu çözüm için varsayımlar:

  1. Her zaman bir ilerleme iletişim kutusu kullanmanız gerekir
  2. Bir seferde yalnızca bir görev gerçekleştirilir
  3. Telefon döndürüldüğünde ve ilerleme iletişim kutusunun otomatik olarak kapatılması durumunda görevin devam etmesi gerekir.

uygulama

Bu yazının altında bulunan iki dosyayı çalışma alanınıza kopyalamanız gerekir. Şunlardan emin olun:

  1. Tüm Activitys'niz genişlemeliBaseActivity

  2. 'De onCreate(), super.onCreate()üyeleriniz tarafından erişilmesi gereken üyeleri başlattıktan sonra çağrılmalıdır ASyncTask. Ayrıca, getContentViewId()form düzeni kimliği sağlamak için geçersiz kılın .

  3. Etkinlik tarafından yönetilen iletişim kutuları oluşturmak için onCreateDialog() her zamanki gibi geçersiz kılın .

  4. AsyncTasks'ınızı yapmak için örnek bir statik iç sınıf için aşağıdaki koda bakın. Daha sonra erişmek için sonucunuzu mResult içinde saklayabilirsiniz.


final static class MyTask extends SuperAsyncTask<Void, Void, Void> {

    public OpenDatabaseTask(BaseActivity activity) {
        super(activity, MY_DIALOG_ID); // change your dialog ID here...
                                       // and your dialog will be managed automatically!
    }

    @Override
    protected Void doInBackground(Void... params) {

        // your task code

        return null;
    }

    @Override
    public boolean onAfterExecute() {
        // your after execute code
    }
}

Ve son olarak, yeni görevinizi başlatmak için:

mCurrentTask = new MyTask(this);
((MyTask) mCurrentTask).execute();

Bu kadar! Umarım bu sağlam çözüm birisine yardım eder.

BaseActivity.java (ithalatı kendiniz organize edin)

protected abstract int getContentViewId();

public abstract class BaseActivity extends Activity {
    protected SuperAsyncTask<?, ?, ?> mCurrentTask;
    public HashMap<Integer, Boolean> mDialogMap = new HashMap<Integer, Boolean>();

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

        setContentView(getContentViewId());

        mCurrentTask = (SuperAsyncTask<?, ?, ?>) getLastNonConfigurationInstance();
        if (mCurrentTask != null) {
            mCurrentTask.attach(this);
            if (mDialogMap.get((Integer) mCurrentTask.dialogId) != null
                && mDialogMap.get((Integer) mCurrentTask.dialogId)) {
        mCurrentTask.postExecution();
            }
        }
    }

    @Override
    protected void onPrepareDialog(int id, Dialog dialog) {
    super.onPrepareDialog(id, dialog);

        mDialogMap.put(id, true);
    }

    @Override
    public Object onRetainNonConfigurationInstance() {
        if (mCurrentTask != null) {
            mCurrentTask.detach();

            if (mDialogMap.get((Integer) mCurrentTask.dialogId) != null
                && mDialogMap.get((Integer) mCurrentTask.dialogId)) {
                return mCurrentTask;
            }
        }

        return super.onRetainNonConfigurationInstance();
    }

    public void cleanupTask() {
        if (mCurrentTask != null) {
            mCurrentTask = null;
            System.gc();
        }
    }
}

SuperAsyncTask.java

public abstract class SuperAsyncTask<Params, Progress, Result> extends AsyncTask<Params, Progress, Result> {
    protected BaseActivity mActivity = null;
    protected Result mResult;
    public int dialogId = -1;

    protected abstract void onAfterExecute();

    public SuperAsyncTask(BaseActivity activity, int dialogId) {
        super();
        this.dialogId = dialogId;
        attach(activity);
    }

    @Override
    protected void onPreExecute() {
        super.onPreExecute();
        mActivity.showDialog(dialogId); // go polymorphism!
    }    

    protected void onPostExecute(Result result) {
        super.onPostExecute(result);
        mResult = result;

        if (mActivity != null &&
                mActivity.mDialogMap.get((Integer) dialogId) != null
                && mActivity.mDialogMap.get((Integer) dialogId)) {
            postExecution();
        }
    };

    public void attach(BaseActivity activity) {
        this.mActivity = activity;
    }

    public void detach() {
        this.mActivity = null;
    }

    public synchronized boolean postExecution() {
        Boolean dialogExists = mActivity.mDialogMap.get((Integer) dialogId);
        if (dialogExists != null || dialogExists) {
            onAfterExecute();
            cleanUp();
    }

    public boolean cleanUp() {
        mActivity.removeDialog(dialogId);
        mActivity.mDialogMap.remove((Integer) dialogId);
        mActivity.cleanupTask();
        detach();
        return true;
    }
}

4

Google'dan biri "resmi çözüm" sağladı mı?

Evet.

Çözüm, sadece bazı kodlardan ziyade bir uygulama mimarisi önerisidir .

Bir uygulamanın, uygulama durumundan bağımsız olarak bir sunucu ile senkronize çalışmasına izin veren 3 tasarım deseni önerdiler (kullanıcı uygulamayı bitirse bile çalışacak, kullanıcı ekranı değiştirecek, uygulama sonlandırılacak, olası tüm diğer durumlar bir arka plan veri işlemi kesintiye uğrayabilir, bu da bunu kapsar)

Teklif, Google I / O 2010 sırasında Android REST istemci uygulamaları konuşmasında Virgil Dobjanschi tarafından açıklandı. 1 saat uzunluğunda, ama izlemeye son derece değer.

Bunun temeli, ağ işlemlerini uygulamadaki Serviceherhangi birine bağımsız çalışan bir ağa soyutlamaktır Activity. Eğer veritabanları ile çalışıyorsanız, kullanımı ContentResolverve Cursorsize bir out-of-the-box verecekti Gözlemci deseni size getirilen uzak verilerle yerel güncellenen veritabanı kez, herhangi aditional mantığı olmadan UI güncelleştirmek için uygundur. Diğer herhangi bir işlem sonrası kodu Service(bunun için bir ResultReceiveralt sınıf kullanıyorum) geçirilen bir geri arama yoluyla çalıştırılır .

Her neyse, benim açıklamam aslında oldukça belirsiz, konuşmayı kesinlikle izlemelisin.


2

Mark'ın (CommonsWare) yanıtı gerçekten de oryantasyon değişiklikleri için işe yarıyor olsa da, Etkinlik doğrudan yok edilirse başarısız olur (telefon görüşmesinde olduğu gibi).

ASyncTask'ınıza başvurmak için bir Uygulama nesnesi kullanarak yön değişikliklerini VE nadir bulunan yok edilen Etkinlik olaylarını işleyebilirsiniz.

Problemin ve çözümün mükemmel bir açıklaması var :

Kredi bunu çözmek için tamamen Ryan'a gider.


1

4 yıl sonra Google, Activity onCreate'de setRetainInstance (true) çağrılması sorununu çözdü. Cihaz döndürme sırasında etkinlik örneğinizi koruyacaktır. Ayrıca eski Android için basit bir çözüm var.


1
İnsanların gözlemlediği sorun, Android'in döndürme, klavye uzantısı ve diğer olaylarda bir etkinlik sınıfını yok etmesinden kaynaklanıyor, ancak zaman uyumsuz bir görev, yok edilen örnek için bir referans tutuyor ve bunu UI güncellemeleri için kullanmaya çalışıyor. Android'e etkinliği açık veya pragmatik olarak yok etmemesi talimatını verebilirsiniz. Bu durumda, zaman uyumsuz görev başvurusu geçerli kalır ve herhangi bir sorun gözlemlenmez. Döndürme, görünümleri yeniden yükleme gibi ek işlemler gerektirebileceğinden, Google etkinliği korumanızı önermez. Böylece siz karar verin.
Singagirl

Teşekkürler, durumu biliyordum ama setRetainInstance () hakkında değil. Anlamadığım şey, Google'ın bunu soruda sorulan sorunları çözmek için kullandığı iddianızdır. Bilgi kaynağını bağlayabilir misiniz? Teşekkürler.
jj_


onRetainNonConfigurationInstance () Bu işlev tamamen bir optimizasyon olarak adlandırılır ve çağrılmasına güvenmemelisiniz. <Aynı kaynaktan: developer.android.com/reference/android/app/…
Dhananjay M

0

etkinlik işleyicisini kullanarak tüm etkinlik eylemlerini çağırmalısınız. Eğer bir iş parçacığında iseniz, bir Runnable oluşturmanız ve Activitie'nin Handler kullanarak ilan gerekir. Aksi takdirde, uygulamanız bazen ölümcül istisna dışında kilitlenir.


0

Bu benim çözümüm: https://github.com/Gotchamoh/Android-AsyncTask-ProgressDialog

Temel olarak adımlar şunlardır:

  1. onSaveInstanceStateHala işleniyorsa görevi kaydetmek için kullanıyorum .
  2. İçinde onCreategörev kaydedildiyse alıyorum.
  3. İçinde gösterilip gösterilmediğini onPauseatın ProgressDialog.
  4. Gelen onResumeGöstermek ProgressDialoggörev hala sürmekte olup olmadığını.
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.