Bagaimana cara menangani AsyncTask selama Rotasi Layar?

88

Saya banyak membaca tentang cara menyimpan status instance saya atau cara menangani aktivitas saya yang dimusnahkan selama rotasi layar.

Tampaknya ada banyak kemungkinan, tetapi saya belum menemukan kemungkinan mana yang paling cocok untuk mengambil hasil dari AsyncTask.

Saya memiliki beberapa AsyncTasks yang hanya dimulai lagi dan memanggil isFinishing()metode aktivitas dan jika aktivitas selesai, mereka tidak akan memperbarui apa pun.

Masalahnya adalah saya memiliki satu Tugas yang melakukan permintaan ke layanan web yang dapat gagal atau berhasil dan memulai ulang tugas tersebut akan mengakibatkan kerugian finansial bagi pengguna.

Bagaimana Anda mengatasi ini? Apa keuntungan atau kerugian dari solusi yang mungkin?

Janusz
sumber
1
Lihat jawaban saya di sini . Anda mungkin juga menemukan informasi tentang apa yang setRetainInstance(true)sebenarnya berguna ini.
Timmmm
yang akan saya lakukan hanyalah mengimplementasikan layanan lokal yang melakukan pemrosesan (dalam utas) yang dilakukan asyncTask Anda. Untuk menampilkan hasilnya, siarkan data ke aktivitas Anda. Sekarang aktivitas hanya bertanggung jawab untuk menampilkan data dan pemrosesan tidak pernah terganggu oleh rotasi layar.
Seseorang di suatu tempat
Bagaimana dengan menggunakan AsyncTaskLoader daripada AsyncTask ??
Sourangshu Biswas

Jawaban:

6

Saran pertama saya adalah memastikan Anda benar-benar membutuhkan aktivitas Anda untuk disetel ulang pada rotasi layar (perilaku default). Setiap kali saya mengalami masalah dengan rotasi, saya telah menambahkan atribut ini ke <activity>tag saya di AndroidManifest.xml, dan baik-baik saja.

android:configChanges="keyboardHidden|orientation"

Ini terlihat aneh, tetapi apa yang dilakukannya diserahkan ke onConfigurationChanged()metode Anda , jika Anda tidak menyediakannya, itu hanya tidak melakukan apa-apa selain mengukur ulang tata letak, yang tampaknya merupakan cara yang sangat memadai untuk menangani rotasi sebagian besar waktu .

Jim Blackler
sumber
5
Tapi itu akan mencegah Aktivitas mengubah tata letak. Dan oleh karena itu memaksa pengguna untuk menggunakan perangkatnya dalam orientasi tertentu yang ditentukan oleh aplikasi Anda dan bukan oleh kebutuhannya.
Janusz
77
Menggunakan teknik ini mencegah Anda dengan mudah menggunakan sumber daya konfigurasi tertentu. Misalnya, jika Anda ingin tata letak atau sumber daya dapat digambar atau string atau apa pun yang berbeda dalam potret dan lanskap, Anda akan menginginkan perilaku default. Mengganti perubahan konfigurasi hanya boleh dilakukan dalam kasus yang sangat spesifik (game, browser web, dll.) Dan bukan karena kemalasan atau kenyamanan karena Anda membatasi diri.
Romain Guy
38
Nah itu saja, Romain. "Jika Anda ingin tata letak atau drawable atau string atau apa pun yang berbeda dalam potret dan lanskap, Anda pasti menginginkan perilaku default", saya yakin itu kasus penggunaan yang jauh lebih jarang daripada yang Anda bayangkan. Yang Anda sebut "kasus yang sangat spesifik" adalah sebagian besar pengembang yang saya yakini. Menggunakan tata letak relatif yang berfungsi di semua dimensi adalah praktik terbaik dan tidak terlalu sulit. Bicara kemalasan sangat salah arah, teknik ini untuk meningkatkan pengalaman pengguna bukan mengurangi waktu pengembangan.
Jim Blackler
2
Saya telah menemukan bahwa ini berfungsi sempurna untuk LinearLayout, tetapi saat menggunakan RelativeLayout, ini tidak menggambar ulang tata letak dengan benar saat beralih ke mode lanskap (setidaknya tidak di N1). Lihat pertanyaan ini: stackoverflow.com/questions/2987049/…
JohnRock
9
Saya setuju dengan Romain (dia tahu apa yang dia bicarakan, dia mengembangkan OS). Apa yang terjadi jika Anda ingin mem-port aplikasi Anda ke tablet dan UI Anda terlihat buruk saat diregangkan? Jika Anda mengambil pendekatan dari jawaban ini, Anda perlu mengkodekan ulang seluruh solusi Anda karena Anda menggunakan peretasan malas ini.
Austyn Mahoney
46

Anda dapat memeriksa bagaimana saya menangani AsyncTaskperubahan orientasi dan di code.google.com/p/shelves . Ada berbagai cara untuk melakukannya, yang saya pilih di aplikasi ini adalah membatalkan tugas yang sedang berjalan, menyimpan statusnya, dan memulai yang baru dengan status tersimpan saat yang baru Activitydibuat. Ini mudah dilakukan, berfungsi dengan baik dan sebagai bonus, ia menangani penghentian tugas Anda saat pengguna meninggalkan aplikasi.

Anda juga dapat menggunakan onRetainNonConfigurationInstance()untuk meneruskan AsyncTaskke yang baru Activity(hati-hati jangan sampai bocor sebelumnya Activitydengan cara ini.)

Romain Guy
sumber
1
saya mencobanya dan memutar selama pencarian buku mengganggu dan memberi saya hasil yang lebih sedikit daripada saat tidak berputar,
sayang
1
Saya tidak dapat menemukan satu pun penggunaan AsyncTask dalam kode itu. Ada kelas UserTask yang terlihat serupa. Apakah project ini sudah ada sebelum AsyncTask?
devconsole
7
AsyncTask berasal dari UserTask. Saya awalnya menulis UserTask untuk aplikasi saya sendiri dan kemudian mengubahnya menjadi AsyncTask. Maaf tentang itu saya lupa namanya diganti.
Romain Guy
@RomainGuy Hai, semoga Anda baik-baik saja. Menurut kode Anda, permintaan 2 dikirim ke server meskipun pada tugas pertama dibatalkan tetapi tidak berhasil dibatalkan. Saya tidak tahu kenapa. Tolong beri tahu saya apakah ada cara untuk menyelesaikan ini.
iamcrypticcoder
10

Ini adalah pertanyaan paling menarik yang pernah saya lihat tentang Android !!! Sebenarnya saya sudah mencari solusinya selama beberapa bulan terakhir. Masih belum terpecahkan.

Hati-hati, cukup menimpa

android:configChanges="keyboardHidden|orientation"

barang tidak cukup.

Pertimbangkan kasus ketika pengguna menerima panggilan telepon saat AsyncTask Anda sedang berjalan. Permintaan Anda sedang diproses oleh server, jadi AsyncTask menunggu tanggapan. Saat ini aplikasi Anda berjalan di latar belakang, karena aplikasi Telepon baru saja muncul di latar depan. OS dapat menghentikan aktivitas Anda karena berada di latar belakang.

Vit Khudenko
sumber
6

Mengapa Anda tidak selalu menyimpan referensi ke AsyncTask saat ini di Singleton yang disediakan oleh Android?

Kapan pun tugas dimulai, di PreExecute atau di builder, Anda menentukan:

((Application) getApplication()).setCurrentTask(asyncTask);

Setiap kali selesai Anda mengaturnya ke nol.

Dengan begitu, Anda selalu memiliki referensi yang memungkinkan Anda melakukan sesuatu seperti, onCreate atau onResume yang sesuai dengan logika spesifik Anda:

this.asyncTaskReference = ((Application) getApplication()).getCurrentTask();

Jika nol, Anda tahu bahwa saat ini tidak ada yang berjalan!

:-)

neteinstein.dll
sumber
Akankah ini berhasil? Apakah ada yang menguji ini? Akankah tugas tetap dimatikan oleh sistem jika terjadi gangguan panggilan telepon atau jika kita menavigasi ke aktivitas baru lalu kembali?
Robert
6
ApplicationInstance memiliki siklus hidupnya sendiri - ia juga dapat dimatikan oleh OS, jadi solusi ini dapat menyebabkan bug yang sulit direproduksi.
Vit Khudenko
7
Saya berpikir: jika Aplikasi dimatikan, seluruh aplikasi dimatikan (dan dengan demikian, semua AsyncTasks juga)?
manmal
Saya pikir aplikasi itu dapat dimatikan tanpa adanya asynctasks (sangat jarang). Tetapi @Arhimed dengan beberapa verifikasi yang mudah dilakukan di awal dan akhir setiap asynctask Anda dapat menghindari bug.
neteinstein
3

Masuk Pro android 4. Penulis telah menyarankan cara yang bagus, yang harus Anda gunakan weak reference.

Catatan referensi yang lemah

hqt
sumber
3

Menurut pandangan saya, lebih baik menyimpan asynctask melalui onRetainNonConfigurationInstancedecoupling dari objek Activity saat ini dan mengikatnya ke objek Activity baru setelah orientasi berubah. Di sini saya menemukan contoh yang sangat bagus bagaimana bekerja dengan AsyncTask dan ProgressDialog.

Yury
sumber
2

Android: pemrosesan latar belakang / Async Opeartion dengan perubahan konfigurasi

Untuk mempertahankan status operasi asinkron selama proses latar belakang: Anda dapat menggunakan bantuan fragmen.

Lihat langkah-langkah berikut ini:

Langkah 1: Buat fragmen tanpa header, katakanlah tugas latar belakang dan tambahkan kelas tugas asinkron pribadi dengan di dalamnya.

Langkah 2 (Langkah Opsional): jika Anda ingin meletakkan kursor pemuatan di atas aktivitas Anda gunakan kode di bawah ini:

Langkah 3: Dalam aktivitas utama Anda, implementasikan antarmuka BackgroundTaskCallbacks yang ditentukan pada langkah 1

class BackgroundTask extends Fragment {
public BackgroundTask() {

}

// Add a static interface 

static interface BackgroundTaskCallbacks {
    void onPreExecute();

    void onCancelled();

    void onPostExecute();

    void doInBackground();
}

private BackgroundTaskCallbacks callbacks;
private PerformAsyncOpeation asyncOperation;
private boolean isRunning;
private final String TAG = BackgroundTask.class.getSimpleName();

/**
 * Start the async operation.
 */
public void start() {
    Log.d(TAG, "********* BACKGROUND TASK START OPERATION ENTER *********");
    if (!isRunning) {
        asyncOperation = new PerformAsyncOpeation();
        asyncOperation.execute();
        isRunning = true;
    }
    Log.d(TAG, "********* BACKGROUND TASK START OPERATION EXIT *********");
}

/**
 * Cancel the background task.
 */
public void cancel() {
    Log.d(TAG, "********* BACKGROUND TASK CANCEL OPERATION ENTER *********");
    if (isRunning) {
        asyncOperation.cancel(false);
        asyncOperation = null;
        isRunning = false;
    }
    Log.d(TAG, "********* BACKGROUND TASK CANCEL OPERATION EXIT *********");
}

/**
 * Returns the current state of the background task.
 */
public boolean isRunning() {
    return isRunning;
}

/**
 * Android passes us a reference to the newly created Activity by calling
 * this method after each configuration change.
 */
public void onAttach(Activity activity) {
    Log.d(TAG, "********* BACKGROUND TASK ON ATTACH ENTER *********");
    super.onAttach(activity);
    if (!(activity instanceof BackgroundTaskCallbacks)) {
        throw new IllegalStateException(
                "Activity must implement the LoginCallbacks interface.");
    }

    // Hold a reference to the parent Activity so we can report back the
    // task's
    // current progress and results.
    callbacks = (BackgroundTaskCallbacks) activity;
    Log.d(TAG, "********* BACKGROUND TASK ON ATTACH EXIT *********");
}

public void onCreate(Bundle savedInstanceState) {
    Log.d(TAG, "********* BACKGROUND TASK ON CREATE ENTER *********");
    super.onCreate(savedInstanceState);
    // Retain this fragment across configuration changes.
    setRetainInstance(true);
    Log.d(TAG, "********* BACKGROUND TASK ON CREATE EXIT *********");
}

public void onDetach() {
    super.onDetach();
    callbacks = null;
}

private class PerformAsyncOpeation extends AsyncTask<Void, Void, Void> {
    protected void onPreExecute() {
        Log.d(TAG,
                "********* BACKGROUND TASK :-> ASYNC OPERATION :- > ON PRE EXECUTE ENTER *********");
        if (callbacks != null) {
            callbacks.onPreExecute();
        }
        isRunning = true;
        Log.d(TAG,
                "********* BACKGROUND TASK :-> ASYNC OPERATION :- > ON PRE EXECUTE EXIT *********");
    }

    protected Void doInBackground(Void... params) {
        Log.d(TAG,
                "********* BACKGROUND TASK :-> ASYNC OPERATION :- > DO IN BACKGROUND ENTER *********");
        if (callbacks != null) {
            callbacks.doInBackground();
        }
        Log.d(TAG,
                "********* BACKGROUND TASK :-> ASYNC OPERATION :- > DO IN BACKGROUND EXIT *********");
        return null;
    }

    protected void onCancelled() {
        Log.d(TAG,
                "********* BACKGROUND TASK :-> ASYNC OPERATION :- > ON CANCEL ENTER *********");
        if (callbacks != null) {
            callbacks.onCancelled();
        }
        isRunning = false;
        Log.d(TAG,
                "********* BACKGROUND TASK :-> ASYNC OPERATION :- > ON CANCEL EXIT *********");
    }

    protected void onPostExecute(Void ignore) {
        Log.d(TAG,
                "********* BACKGROUND TASK :-> ASYNC OPERATION :- > ON POST EXECUTE ENTER *********");
        if (callbacks != null) {
            callbacks.onPostExecute();
        }
        isRunning = false;
        Log.d(TAG,
                "********* BACKGROUND TASK :-> ASYNC OPERATION :- > ON POST EXECUTE EXIT *********");
    }
}

public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    setRetainInstance(true);
}

public void onStart() {
    super.onStart();
}

public void onResume() {
    super.onResume();
}

public void onPause() {
    super.onPause();
}

public void onStop() {
    super.onStop();
}

public class ProgressIndicator extends Dialog {

public ProgressIndicator(Context context, int theme) {
    super(context, theme);
}

private ProgressBar progressBar;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    requestWindowFeature(Window.FEATURE_NO_TITLE);
    setContentView(R.layout.progress_indicator);
    this.setCancelable(false);
    progressBar = (ProgressBar) findViewById(R.id.progressBar);
    progressBar.getIndeterminateDrawable().setColorFilter(R.color.DarkBlue, android.graphics.PorterDuff.Mode.SCREEN);
}

@Override
public void show() {
    super.show();
}

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

@Override
public void cancel() {
    super.cancel();
}

public class MyActivity extends FragmentActivity implements BackgroundTaskCallbacks,{

private static final String KEY_CURRENT_PROGRESS = "current_progress";

ProgressIndicator progressIndicator = null;

private final static String TAG = MyActivity.class.getSimpleName();

private BackgroundTask task = null;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(//"set your layout here");
    initialize your views and widget here .............



    FragmentManager fm = getSupportFragmentManager();
    task = (BackgroundTask) fm.findFragmentByTag("login");

    // If the Fragment is non-null, then it is currently being
    // retained across a configuration change.
    if (task == null) {
        task = new BackgroundTask();
        fm.beginTransaction().add(task, "login").commit();
    }

    // Restore saved state
    if (savedInstanceState != null) {
        Log.i(TAG, "KEY_CURRENT_PROGRESS_VALUE ON CREATE :: "
                + task.isRunning());
        if (task.isRunning()) {
            progressIndicator = new ProgressIndicator(this,
                    R.style.TransparentDialog);
            if (progressIndicator != null) {
                progressIndicator.show();
            }
        }
    }
}

@Override
protected void onPause() {
    // TODO Auto-generated method stub
    super.onPause();

}

@Override
protected void onSaveInstanceState(Bundle outState) {
    // save the current state of your operation here by saying this 

    super.onSaveInstanceState(outState);
    Log.i(TAG, "KEY_CURRENT_PROGRESS_VALUE ON SAVE INSTANCE :: "
            + task.isRunning());
    outState.putBoolean(KEY_CURRENT_PROGRESS, task.isRunning());
    if (progressIndicator != null) {
        progressIndicator.dismiss();
        progressIndicator.cancel();
    }
    progressIndicator = null;
}


private void performOperation() {

            if (!task.isRunning() && progressIndicator == null) {
                progressIndicator = new ProgressIndicator(this,
                        R.style.TransparentDialog);
                progressIndicator.show();
            }
            if (task.isRunning()) {
                task.cancel();
            } else {
                task.start();
            }
        }


@Override
protected void onDestroy() {
    super.onDestroy();
    if (progressIndicator != null) {
        progressIndicator.dismiss();
        progressIndicator.cancel();
    }
    progressIndicator = null;
}

@Override
public void onPreExecute() {
    Log.i(TAG, "CALLING ON PRE EXECUTE");
}

@Override
public void onCancelled() {
    Log.i(TAG, "CALLING ON CANCELLED");
    if (progressIndicator != null) {
        progressIndicator.dismiss();
        progressIndicator.cancel();

}

public void onPostExecute() {
    Log.i(TAG, "CALLING ON POST EXECUTE");
    if (progressIndicator != null) {
        progressIndicator.dismiss();
        progressIndicator.cancel();
        progressIndicator = null;
    }
}

@Override
public void doInBackground() {
    // put your code here for background operation
}

}

Piyush Gupta
sumber
1

Satu hal yang perlu dipertimbangkan adalah apakah hasil AsyncTask harus tersedia hanya untuk aktivitas yang memulai tugas. Jika ya, maka jawaban Romain Guy adalah yang terbaik. Jika itu harus tersedia untuk aktivitas lain dari aplikasi Anda, maka onPostExecuteAnda dapat menggunakan LocalBroadcastManager.

LocalBroadcastManager.getInstance(getContext()).sendBroadcast(new Intent("finished"));

Anda juga perlu memastikan bahwa aktivitas menangani situasi dengan benar saat siaran dikirim saat aktivitas dihentikan sementara.

Juozas Kontvainis
sumber
1

Lihat posting ini . Postingan ini melibatkan AsyncTask yang menjalankan operasi yang berjalan lama dan kebocoran memori saat rotasi layar terjadi dalam satu aplikasi sampel. Aplikasi sampel tersedia di pemalsuan sumber

Vahid
sumber
0

Solusi saya.

Dalam kasus saya, saya punya rantai AsyncTasks dengan konteks yang sama. Aktivitas hanya memiliki akses ke yang pertama. Untuk membatalkan tugas yang sedang berjalan, saya melakukan hal berikut:

public final class TaskLoader {

private static AsyncTask task;

     private TaskLoader() {
         throw new UnsupportedOperationException();
     }

     public static void setTask(AsyncTask task) {
         TaskLoader.task = task;
     }

    public static void cancel() {
         TaskLoader.task.cancel(true);
     }
}

Tugas doInBackground():

protected Void doInBackground(Params... params) {
    TaskLoader.setTask(this);
    ....
}

Aktivitas onStop()atau onPause():

protected void onStop() {
    super.onStop();
    TaskLoader.cancel();
}
Pitt90
sumber
0
@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    final AddTask task = mAddTask;
    if (task != null && task.getStatus() != UserTask.Status.FINISHED) {
        final String bookId = task.getBookId();
        task.cancel(true);

        if (bookId != null) {
            outState.putBoolean(STATE_ADD_IN_PROGRESS, true);
            outState.putString(STATE_ADD_BOOK, bookId);
        }

        mAddTask = null;
    }
}

@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
    if (savedInstanceState.getBoolean(STATE_ADD_IN_PROGRESS)) {
        final String id = savedInstanceState.getString(STATE_ADD_BOOK);
        if (!BooksManager.bookExists(getContentResolver(), id)) {
            mAddTask = (AddTask) new AddTask().execute(id);
        }
    }
}
Atif Mahmood
sumber
0

Anda juga dapat menambahkan android: configChanges = "keyboardHidden | orientasi | screenSize"

untuk contoh nyata Anda, saya harap ini membantu

 <application
    android:name=".AppController"
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:roundIcon="@mipmap/ic_launcher_round"
    android:supportsRtl="true"
    android:configChanges="keyboardHidden|orientation|screenSize"
    android:theme="@style/AppTheme">
eng mohamed emam
sumber