AsyncTask dan penanganan kesalahan di Android

147

Saya mengubah kode saya dari menggunakan Handlermenjadi AsyncTask. Yang terakhir sangat bagus dalam fungsinya - pembaruan asinkron dan penanganan hasil di utas UI utama. Apa yang tidak jelas bagi saya adalah bagaimana menangani pengecualian jika ada masalah AsyncTask#doInBackground.

Cara saya melakukannya adalah memiliki Handler kesalahan dan mengirim pesan ke sana. Ini berfungsi dengan baik, tetapi apakah itu pendekatan yang "benar" atau adakah alternatif yang lebih baik?

Saya juga mengerti bahwa jika saya mendefinisikan Handler kesalahan sebagai bidang Kegiatan, itu harus dijalankan di utas UI. Namun, kadang-kadang (sangat tidak terduga) saya akan mendapatkan Pengecualian yang mengatakan bahwa kode yang dipicu dari Handler#handleMessagemengeksekusi di utas yang salah. Haruskah saya menginisialisasi Handler kesalahan Activity#onCreatesebagai gantinya? Menempatkan runOnUiThreadke dalam Handler#handleMessagetampaknya berlebihan tetapi dijalankan dengan sangat andal.

Bostone
sumber
Mengapa Anda ingin mengonversi kode Anda? Apakah ada alasan bagus?
HGPB
4
@ Haraldo itu adalah praktik pengkodean yang lebih baik setidaknya itulah yang saya rasakan
Bostone

Jawaban:

178

Ini berfungsi dengan baik tetapi apakah itu pendekatan yang "benar" dan apakah ada alternatif yang lebih baik?

Saya berpegang pada Throwableatau Exceptiondalam AsyncTaskinstance itu sendiri dan kemudian melakukan sesuatu dengan itu onPostExecute(), sehingga penanganan kesalahan saya memiliki opsi untuk menampilkan dialog di layar.

CommonsWare
sumber
8
Cemerlang! Tidak perlu monyet dengan Penangan lagi
Bostone
5
Apakah ini cara saya harus berpegang pada Throwable atau Exception? "Tambahkan variabel instan ke subkelas AsyncTask Anda sendiri yang akan menampung hasil pemrosesan latar belakang Anda." Saat Anda mendapatkan pengecualian, simpan pengecualian (atau string-kesalahan / kode lain) dalam variabel ini. Ketika onPostExecute dipanggil, lihat apakah variabel instan ini disetel ke beberapa kesalahan. Jika demikian, tampilkan pesan kesalahan. "(Dari pengguna" Streets of Boston " groups.google.com/group/android-developers/browse_thread/thread/… )
OneWorld
1
@OneWorld: Ya, itu tidak masalah.
CommonsWare
2
Hai CW, bisa tolong jelaskan cara Anda melakukan ini secara lebih rinci - mungkin dengan contoh kode singkat? Terima kasih banyak!!
Bruiser
18
@Bruiser: github.com/commonsguy/cw-lunchlist/tree/master/15-Internet/… memiliki AsyncTaskpola berikut yang saya jelaskan.
CommonsWare
140

Buat objek AsyncResult (yang juga dapat Anda gunakan dalam proyek lain)

public class AsyncTaskResult<T> {
    private T result;
    private Exception error;

    public T getResult() {
        return result;
    }

    public Exception getError() {
        return error;
    }

    public AsyncTaskResult(T result) {
        super();
        this.result = result;
    }

    public AsyncTaskResult(Exception error) {
        super();
        this.error = error;
    }
}

Kembalikan objek ini dari metode AsyncTask doInBackground Anda dan periksa di postExecute. (Anda dapat menggunakan kelas ini sebagai kelas dasar untuk tugas async Anda yang lain)

Di bawah ini adalah mockup tugas yang mendapat respons JSON dari server web.

AsyncTask<Object,String,AsyncTaskResult<JSONObject>> jsonLoader = new AsyncTask<Object, String, AsyncTaskResult<JSONObject>>() {

        @Override
        protected AsyncTaskResult<JSONObject> doInBackground(
                Object... params) {
            try {
                // get your JSONObject from the server
                return new AsyncTaskResult<JSONObject>(your json object);
            } catch ( Exception anyError) {
                return new AsyncTaskResult<JSONObject>(anyError);
            }
        }

        protected void onPostExecute(AsyncTaskResult<JSONObject> result) {
            if ( result.getError() != null ) {
                // error handling here
            }  else if ( isCancelled()) {
                // cancel handling here
            } else {

                JSONObject realResult = result.getResult();
                // result handling here
            }
        };

    }
Cagatay Kalan
sumber
1
Saya suka itu. Enkapsulasi yang bagus. Karena ini adalah parafrase dari jawaban asli, jawabannya tetap tetapi ini pasti layak untuk suatu hal
Bostone
Ini adalah demonstrasi yang cukup bagus tentang bagaimana Generics dapat berguna. Ini mengeluarkan aroma aneh dalam hal kompleksitas, tetapi tidak dengan cara yang benar-benar bisa saya artikulasikan.
num1
4
Nice idea, hanya satu pertanyaan: mengapa Anda menelepon super()di AsyncTaskResultsaat kelas tidak memperpanjang apa-apa?
donturner
7
"no harm" - kode redundan selalu berbahaya untuk keterbacaan dan pemeliharaan. Keluar dari sana! :)
donturner
2
Benar-benar menyukai solusinya ... mulai memikirkannya - orang-orang C # menggunakan metode yang persis sama dalam implementasi asli BackgroundTask C # yang sesuai ...
Vova
11

Ketika saya merasa perlu menangani Pengecualian AsyncTaskdengan benar, saya menggunakan ini sebagai kelas super:

public abstract class ExceptionAsyncTask<Params, Progress, Result> extends AsyncTask<Params, Progress, Result> {

    private Exception exception=null;
    private Params[] params;

    @Override
    final protected Result doInBackground(Params... params) {
        try {
            this.params = params; 
            return doInBackground();
        }
        catch (Exception e) {
            exception = e;
            return null;
        }
    }

    abstract protected Result doInBackground() throws Exception;

    @Override
    final protected void onPostExecute(Result result) {
        super.onPostExecute(result);
        onPostExecute(exception, result);
    }

    abstract protected void onPostExecute(Exception exception, Result result);

    public Params[] getParams() {
        return params;
    }

}

Seperti biasa, Anda mengganti doInBackgroundsubclass Anda untuk melakukan pekerjaan latar belakang, dengan senang hati melempar Pengecualian di mana diperlukan. Anda kemudian dipaksa untuk mengimplementasikan onPostExecute(karena abstrak) dan ini dengan lembut mengingatkan Anda untuk menangani semua jenis Exception, yang dilewatkan sebagai parameter. Dalam kebanyakan kasus, Pengecualian menyebabkan beberapa jenis output ui, jadi onPostExecutemerupakan tempat yang sempurna untuk melakukan itu.

sulai
sumber
1
Wah, mengapa tidak meneruskan saja params, jadi lebih mirip dengan yang asli dan lebih mudah untuk dimigrasi?
TWiStErRob
@TWiStErRob tidak ada yang salah dengan ide itu. Ini masalah preferensi pribadi saya kira, karena saya cenderung tidak menggunakan params. Saya lebih suka new Task("Param").execute()lebih new Task().execute("Param").
sulai
5

Jika Anda ingin menggunakan kerangka kerja RoboGuice yang memberi Anda manfaat lain, Anda dapat mencoba RoboAsyncTask yang memiliki Callback tambahan padaException (). Bekerja sangat bagus dan saya menggunakannya. http://code.google.com/p/roboguice/wiki/RoboAsyncTask

ludwigm
sumber
apa pengalamanmu dengan ini? cukup stabil?
nickaknudson
Apakah RoboGuicemasih hidup? Tampaknya belum diperbarui sejak 2012?
Dimitry K
Tidak, RoboGuice sudah mati dan usang. Dagger2 adalah pengganti yang disarankan, tetapi itu adalah perpustakaan DI telanjang-tulang saja.
Avi Cherry
3

Saya membuat subkelas AsyncTask saya sendiri dengan antarmuka yang menentukan panggilan balik untuk keberhasilan dan kegagalan. Jadi jika pengecualian dilemparkan ke AsyncTask Anda, fungsi onFailure akan melewati pengecualian, jika tidak, panggilan balik onSuccess akan melewati hasil Anda. Mengapa android tidak memiliki sesuatu yang lebih baik tersedia di luar saya.

public class SafeAsyncTask<inBackgroundType, progressType, resultType>
extends AsyncTask<inBackgroundType, progressType, resultType>  {
    protected Exception cancelledForEx = null;
    protected SafeAsyncTaskInterface callbackInterface;

    public interface SafeAsyncTaskInterface <cbInBackgroundType, cbResultType> {
        public Object backgroundTask(cbInBackgroundType[] params) throws Exception;
        public void onCancel(cbResultType result);
        public void onFailure(Exception ex);
        public void onSuccess(cbResultType result);
    }

    @Override
    protected void onPreExecute() {
        this.callbackInterface = (SafeAsyncTaskInterface) this;
    }

    @Override
    protected resultType doInBackground(inBackgroundType... params) {
        try {
            return (resultType) this.callbackInterface.backgroundTask(params);
        } catch (Exception ex) {
            this.cancelledForEx = ex;
            this.cancel(false);
            return null;
        }
    }

    @Override
    protected void onCancelled(resultType result) {
        if(this.cancelledForEx != null) {
            this.callbackInterface.onFailure(this.cancelledForEx);
        } else {
            this.callbackInterface.onCancel(result);
        }
    }

    @Override
    protected void onPostExecute(resultType result) {
        this.callbackInterface.onSuccess(result);
    }
}
ErlVolton
sumber
3

Solusi yang lebih komprehensif untuk solusi Cagatay Kalan ditunjukkan di bawah ini:

AsyncTaskResult

public class AsyncTaskResult<T> 
{
    private T result;
    private Exception error;

    public T getResult() 
    {
        return result;
    }

    public Exception getError() 
    {
        return error;
    }

    public AsyncTaskResult(T result) 
    {
        super();
        this.result = result;
    }

    public AsyncTaskResult(Exception error) {
        super();
        this.error = error;
    }
}

ExceptionHandlingAsyncTask

public abstract class ExceptionHandlingAsyncTask<Params, Progress, Result> extends AsyncTask<Params, Progress, AsyncTaskResult<Result>>
{
    private Context context;

    public ExceptionHandlingAsyncTask(Context context)
    {
        this.context = context;
    }

    public Context getContext()
    {
        return context;
    }

    @Override
    protected AsyncTaskResult<Result> doInBackground(Params... params)
    {
        try
        {
            return new AsyncTaskResult<Result>(doInBackground2(params));
        }
        catch (Exception e)
        {
            return new AsyncTaskResult<Result>(e);
        }
    }

    @Override
    protected void onPostExecute(AsyncTaskResult<Result> result)
    {
        if (result.getError() != null)
        {
            onPostException(result.getError());
        }
        else
        {
            onPostExecute2(result.getResult());
        }
        super.onPostExecute(result);
    }

    protected abstract Result doInBackground2(Params... params);

    protected abstract void onPostExecute2(Result result);

    protected void onPostException(Exception exception)
    {
                        new AlertDialog.Builder(context).setTitle(R.string.dialog_title_generic_error).setMessage(exception.getMessage())
                .setIcon(android.R.drawable.ic_dialog_alert).setPositiveButton(R.string.alert_dialog_ok, new DialogInterface.OnClickListener()
                {
                    public void onClick(DialogInterface dialog, int which)
                    {
                        //Nothing to do
                    }
                }).show();
    }
}

Contoh Tugas

public class ExampleTask extends ExceptionHandlingAsyncTask<String, Void, Result>
{
    private ProgressDialog  dialog;

    public ExampleTask(Context ctx)
    {
        super(ctx);
        dialog = new ProgressDialog(ctx);
    }

    @Override
    protected void onPreExecute()
    {
        dialog.setMessage(getResources().getString(R.string.dialog_logging_in));
        dialog.show();
    }

    @Override
    protected Result doInBackground2(String... params)
    {
        return new Result();
    }

    @Override
    protected void onPostExecute2(Result result)
    {
        if (dialog.isShowing())
            dialog.dismiss();
        //handle result
    }

    @Override
    protected void onPostException(Exception exception)
    {
        if (dialog.isShowing())
            dialog.dismiss();
        super.onPostException(exception);
    }
}
vahapt
sumber
Saya mendapatkan metode getResources () seperti pada myActivity.getApplicationContext (). GetResources ()
Stephane
2

Kelas sederhana ini dapat membantu Anda

public abstract class ExceptionAsyncTask<Param, Progress, Result, Except extends Throwable> extends AsyncTask<Param, Progress, Result> {
    private Except thrown;

    @SuppressWarnings("unchecked")
    @Override
    /**
     * Do not override this method, override doInBackgroundWithException instead
     */
    protected Result doInBackground(Param... params) {
        Result res = null;
        try {
            res = doInBackgroundWithException(params);
        } catch (Throwable e) {
            thrown = (Except) e;
        }
        return res;
    }

    protected abstract Result doInBackgroundWithException(Param... params) throws Except;

    @Override
    /**
     * Don not override this method, override void onPostExecute(Result result, Except exception) instead
     */
    protected void onPostExecute(Result result) {
        onPostExecute(result, thrown);
        super.onPostExecute(result);
    }

    protected abstract void onPostExecute(Result result, Except exception);
}
Denis
sumber
2

Cara lain yang tidak bergantung pada berbagi anggota variabel adalah menggunakan pembatalan.

Ini dari dokumen Android:

boolean final publik batal (boolean mayInterruptIfRunning)

Upaya untuk membatalkan pelaksanaan tugas ini. Upaya ini akan gagal jika tugas sudah selesai, sudah dibatalkan, atau tidak bisa dibatalkan karena alasan lain. Jika berhasil, dan tugas ini belum dimulai saat pembatalan dipanggil, tugas ini seharusnya tidak pernah berjalan. Jika tugas sudah dimulai, maka parameter mayInterruptIfRunning menentukan apakah utas yang menjalankan tugas ini harus diinterupsi dalam upaya untuk menghentikan tugas.

Memanggil metode ini akan menghasilkan onCancelled (Object) dipanggil pada utas UI setelah doInBackground (Object []) kembali. Memanggil metode ini menjamin bahwa onPostExecute (Object) tidak pernah dipanggil. Setelah memohon metode ini, Anda harus memeriksa nilai yang dikembalikan oleh isCancelled () secara berkala dari doInBackground (Object []) untuk menyelesaikan tugas sedini mungkin.

Jadi, Anda dapat memanggil batal dalam pernyataan tangkap dan pastikan onPostExcute tidak pernah dipanggil, tetapi sebaliknya onCancelled dipanggil pada utas UI. Jadi, Anda dapat menampilkan pesan kesalahan.

Ali
sumber
Anda tidak dapat menampilkan pesan kesalahan dengan benar, karena Anda tidak tahu masalahnya (Pengecualian), Anda masih perlu menangkap dan mengembalikan AsyncTaskResult. Juga pembatalan pengguna bukan kesalahan, ini interaksi yang diharapkan: Bagaimana Anda membedakannya?
TWiStErRob
cancel(boolean)menghasilkan panggilan untuk onCancelled()ada sejak awal, tetapi onCancelled(Result)ditambahkan di API 11 .
TWiStErRob
0

Sebenarnya, AsyncTask menggunakan FutureTask & Executor, FutureTask mendukung rantai pengecualian Pertama mari kita mendefinisikan kelas pembantu

public static class AsyncFutureTask<T> extends FutureTask<T> {

    public AsyncFutureTask(@NonNull Callable<T> callable) {
        super(callable);
    }

    public AsyncFutureTask<T> execute(@NonNull Executor executor) {
        executor.execute(this);
        return this;
    }

    public AsyncFutureTask<T> execute() {
        return execute(AsyncTask.THREAD_POOL_EXECUTOR);
    }

    @Override
    protected void done() {
        super.done();
        //work done, complete or abort or any exception happen
    }
}

Kedua, mari kita gunakan

    try {
        Log.d(TAG, new AsyncFutureTask<String>(new Callable<String>() {
            @Override
            public String call() throws Exception {
                //throw Exception in worker thread
                throw new Exception("TEST");
            }
        }).execute().get());
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (ExecutionException e) {
        //catch the exception throw by worker thread in main thread
        e.printStackTrace();
    }
Yessy
sumber
-2

Secara pribadi, saya akan menggunakan pendekatan ini. Anda bisa menangkap pengecualian dan mencetak jejak tumpukan jika Anda membutuhkan info.

buat tugas Anda di latar belakang mengembalikan nilai boolean.

seperti ini:

    @Override
                protected Boolean doInBackground(String... params) {
                    return readXmlFromWeb(params[0]);
         }

        @Override
                protected void onPostExecute(Boolean result) {

              if(result){
              // no error
               }
              else{
                // error handling
               }
}
Harry
sumber
-2

Kemungkinan lain akan digunakan Objectsebagai tipe kembali, dan di onPostExecute()periksa untuk jenis objek. Itu pendek.

class MyAsyncTask extends AsyncTask<MyInObject, Void, Object> {

    @Override
    protected AsyncTaskResult<JSONObject> doInBackground(MyInObject... myInObjects) {
        try {
            MyOutObject result;
            // ... do something that produces the result
            return result;
        } catch (Exception e) {
            return e;
        }
    }

    protected void onPostExecute(AsyncTaskResult<JSONObject> outcome) {
        if (outcome instanceof MyOutObject) {
            MyOutObject result = (MyOutObject) outcome;
            // use the result
        } else if (outcome instanceof Exception) {
            Exception e = (Exception) outcome;
            // show error message
        } else throw new IllegalStateException();
    }
}
Matthias Ronge
sumber
1
sama sekali tidak relevan
Dinu
-2

Jika Anda tahu pengecualian yang benar, maka Anda dapat menghubungi

Exception e = null;

publishProgress(int ...);

misalnya:

@Override
protected Object doInBackground(final String... params) {

    // TODO Auto-generated method stub
    try {
        return mClient.call(params[0], params[1]);
    } catch(final XMLRPCException e) {

        // TODO Auto-generated catch block
        this.e = e;
        publishProgress(0);
        return null;
    }
}

dan buka "onProgressUpdate" dan lakukan yang berikut

@Override
protected void onProgressUpdate(final Integer... values) {

    // TODO Auto-generated method stub
    super.onProgressUpdate(values);
    mDialog.dismiss();
    OptionPane.showMessage(mActivity, "Connection error", e.getMessage());
}

Ini hanya akan membantu dalam beberapa kasus saja. Anda juga dapat menyimpan Global Exceptionvariabel dan mengakses pengecualian.

Ajmal Muhammad P
sumber
1
Tolong, jangan lakukan ini. Itu benar-benar, gaya yang buruk!
JimmyB