AsyncTask và xử lý lỗi trên Android


147

Tôi đang chuyển đổi mã của mình từ sử dụng Handlersang AsyncTask. Cái sau là tuyệt vời ở những gì nó làm - cập nhật không đồng bộ và xử lý kết quả trong luồng UI chính. Điều không rõ ràng với tôi là làm thế nào để xử lý các trường hợp ngoại lệ nếu có gì đó xảy ra AsyncTask#doInBackground.

Cách tôi làm là có một Handler lỗi và gửi tin nhắn đến nó. Nó hoạt động tốt, nhưng nó là phương pháp "đúng" hay có cách nào khác tốt hơn?

Ngoài ra, tôi hiểu rằng nếu tôi xác định Trình xử lý lỗi là trường Hoạt động, thì nó sẽ thực thi trong luồng UI. Tuy nhiên, đôi khi (rất khó đoán) tôi sẽ nhận được một Ngoại lệ nói rằng mã được kích hoạt từ Handler#handleMessageđang thực thi trên luồng sai. Activity#onCreateThay vào đó tôi có nên khởi tạo Handler không? Đặt runOnUiThreadvào Handler#handleMessagecó vẻ dư thừa nhưng nó thực thi rất đáng tin cậy.


Tại sao bạn muốn chuyển đổi mã của bạn? Có một lý do tốt?
HGPB

4
@Haraldo đó là một cách thực hành mã hóa tốt hơn ít nhất là cảm giác của tôi
Bostone 15/03/13

Câu trả lời:


178

Nó hoạt động tốt nhưng nó là phương pháp "đúng" và có cách nào khác tốt hơn không?

Tôi giữ Throwablehoặc Exceptiontrong AsyncTaskchính trường hợp đó và sau đó làm điều gì đó với nó onPostExecute(), vì vậy việc xử lý lỗi của tôi có tùy chọn hiển thị hộp thoại trên màn hình.


8
Xuất sắc! Không cần phải khỉ với Handlers nữa
Bostone

5
Đây có phải là cách tôi nên giữ cho Ném được hay Ngoại lệ? "Thêm một biến đối tượng vào lớp con AsyncTask của riêng bạn sẽ giữ kết quả xử lý nền của bạn." Khi bạn nhận được một ngoại lệ, lưu trữ ngoại lệ (hoặc một số chuỗi lỗi / mã khác) trong biến này. Khi onPostExecute được gọi, hãy xem nếu biến thể hiện này được đặt thành một số lỗi. Nếu vậy, hãy hiển thị thông báo lỗi. "(Từ người dùng" Streets of Boston " Groups.google.com/group/android-developers/browse_thread/thread/iêu )
OneWorld

1
@OneWorld: Vâng, điều đó sẽ ổn thôi.
CommonsWare

2
Xin chào CW, bạn có thể vui lòng giải thích cách thức của bạn để làm điều này chi tiết hơn xin vui lòng - có thể với một ví dụ mã ngắn gọn? Cảm ơn rất nhiều!!
Bruiser

18
@Bruiser: github.com/commonsguy/cw-lunchlist/tree/master/15-Internet/...AsyncTasksau mô hình tôi mô tả.
CommonsWare

140

Tạo một đối tượng AsyncResult (mà bạn cũng có thể sử dụng trong các dự án khác)

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

Trả về đối tượng này từ các phương thức AsyncTask doInBackground của bạn và kiểm tra nó trong postExecute. (Bạn có thể sử dụng lớp này làm lớp cơ sở cho các tác vụ không đồng bộ khác của mình)

Dưới đây là một bản mô phỏng của một tác vụ nhận được phản hồi JSON từ máy chủ 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
            }
        };

    }

1
Tôi thích nó. Đóng gói đẹp. Vì đây là một cách diễn đạt của câu trả lời ban đầu, câu trả lời vẫn còn nhưng điều này chắc chắn xứng đáng với một điểm
Bostone

Đây là một minh chứng khá hay về việc Generics có thể hữu ích như thế nào. Đó là một mùi lạ về sự phức tạp, nhưng không phải là cách tôi có thể nói rõ.
num1

4
Ý tưởng hay, chỉ một câu hỏi: tại sao bạn gọi super()vào AsyncTaskResultkhi lớp không mở rộng bất cứ điều gì?
donturner

7
"Không có hại" - mã dự phòng luôn có hại cho khả năng đọc và bảo trì. Lấy nó ra khỏi đó! :)
donturner

2
Thực sự thích giải pháp ... nghĩ về nó - những người C # đã sử dụng chính xác cùng một phương pháp trong triển khai gốc BackgroundTask tương ứng của C # ...
Vova

11

Khi tôi cảm thấy cần phải xử lý Ngoại lệ AsyncTaskmột cách chính xác, tôi sử dụng điều này như siêu hạng:

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

}

Như bình thường, bạn ghi đè doInBackgroundtrong lớp con của mình để thực hiện công việc nền, vui vẻ ném Ngoại lệ khi cần. Sau đó, bạn buộc phải thực hiện onPostExecute(vì nó trừu tượng) và điều này nhẹ nhàng nhắc nhở bạn xử lý tất cả các loại Exception, được truyền dưới dạng tham số. Trong hầu hết các trường hợp, Ngoại lệ dẫn đến một số loại đầu ra ui, vì vậy onPostExecutelà một nơi hoàn hảo để làm điều đó.


1
Whoa, tại sao không vượt qua paramsphía trước, vì vậy nó giống với bản gốc hơn và dễ dàng di chuyển hơn?
TWiStErRob

@TWiStErRob không có gì sai với ý tưởng đó. Tôi đoán đó là vấn đề sở thích cá nhân, vì tôi có xu hướng không sử dụng params. Tôi thích new Task("Param").execute()hơn new Task().execute("Param").
sulai

5

Nếu bạn muốn sử dụng khung RoboGuice mang lại cho bạn những lợi ích khác, bạn có thể dùng thử RoboAsyncTask có thêm Callback onException (). Hoạt động thực sự tốt và tôi sử dụng nó. http://code.google.com.vn/p/roboguice/wiki/RoboAsyncTask


kinh nghiệm của bạn với điều này là gì? khá ổn định?
nickaknudson

RoboGuicevẫn còn sống? Có vẻ như chưa được cập nhật từ năm 2012?
Dimitry K

Không, RoboGuice đã chết và không được dùng nữa. Dagger2 là sự thay thế được đề xuất, nhưng nó chỉ là một thư viện DI đơn giản.
Avi Cherry

3

Tôi đã tạo lớp con AsyncTask của riêng mình với giao diện xác định cuộc gọi lại thành công và thất bại. Vì vậy, nếu một ngoại lệ được ném vào AsyncTask, chức năng onFailure sẽ được thông qua ngoại lệ, nếu không, cuộc gọi lại onSuccess sẽ vượt qua kết quả của bạn. Tại sao Android không có thứ gì đó tốt hơn có sẵn ngoài tôi.

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

3

Một giải pháp toàn diện hơn cho giải pháp của Cagatay Kalan được trình bày dưới đây:

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

Nhiệm vụ mẫu

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

Tôi đã nhận được phương thức getResource () như trong myActivity.getApplicationContext (). GetResource ()
Stephane

2

Lớp học đơn giản này có thể giúp bạn

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

2

Một cách khác không phụ thuộc vào việc chia sẻ thành viên biến là sử dụng hủy.

Đây là từ tài liệu Android:

hủy bỏ boolean cuối cùng (boolean mayInterrupt IfRasty)

Nỗ lực hủy bỏ thực hiện nhiệm vụ này. Nỗ lực này sẽ thất bại nếu nhiệm vụ đã hoàn thành, đã bị hủy hoặc không thể bị hủy vì một số lý do khác. Nếu thành công và tác vụ này chưa bắt đầu khi hủy được gọi, tác vụ này sẽ không bao giờ chạy. Nếu tác vụ đã bắt đầu, thì tham số mayInterrupt IfRasty xác định xem luồng thực thi tác vụ này có bị gián đoạn trong nỗ lực dừng tác vụ hay không.

Gọi phương thức này sẽ dẫn đến việc onCancelling (Object) được gọi trên luồng UI sau khi trả về doInBackground (Object []). Gọi phương thức này đảm bảo rằng onPostExecute (Object) không bao giờ được gọi. Sau khi gọi phương thức này, bạn nên kiểm tra giá trị được trả về bởi isCancelling () từ doInBackground (Object []) để hoàn thành nhiệm vụ sớm nhất có thể.

Vì vậy, bạn có thể gọi hủy trong câu lệnh bắt và chắc chắn rằng onPostExcute không bao giờ được gọi, nhưng thay vào đó, onCancelling được gọi trên luồng UI. Vì vậy, bạn có thể hiển thị thông báo lỗi.


Bạn không thể hiển thị thông báo lỗi chính xác, vì bạn không biết vấn đề (Ngoại lệ), bạn vẫn cần phải bắt và trả lại AsyncTaskResult. Ngoài ra, việc hủy bỏ của người dùng không phải là một lỗi, đó là một tương tác dự kiến: Làm thế nào để bạn phân biệt giữa những điều này?
TWiStErRob

cancel(boolean)dẫn đến một cuộc gọi onCancelled()tồn tại từ đầu, nhưng onCancelled(Result)đã được thêm vào API 11 .
TWiStErRob

0

Trên thực tế, AsyncTask sử dụng FutureTask & Executor, chuỗi ngoại lệ hỗ trợ FutureTask Trước tiên, hãy xác định một lớp trình trợ giúp

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

Thứ hai, hãy sử dụng

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

-2

Cá nhân, tôi sẽ sử dụng phương pháp này. Bạn chỉ có thể bắt ngoại lệ và in ra dấu vết ngăn xếp nếu bạn cần thông tin.

làm cho nhiệm vụ của bạn trong nền trả về một giá trị boolean.

nó như thế này:

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

        @Override
                protected void onPostExecute(Boolean result) {

              if(result){
              // no error
               }
              else{
                // error handling
               }
}

-2

Một khả năng khác là sử dụng Objectlàm kiểu trả về và onPostExecute()kiểm tra loại đối tượng. Nó ngắn.

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

1
hoàn toàn không liên quan
Dinu

-2

Nếu bạn biết ngoại lệ chính xác thì bạn có thể gọi

Exception e = null;

publishProgress(int ...);

ví dụ:

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

và đi đến "onProTHERUpdate" và thực hiện theo

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

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

Điều này sẽ chỉ hữu ích trong một số trường hợp. Ngoài ra, bạn có thể giữ một Global Exceptionbiến và truy cập ngoại lệ.


1
Xin đừng làm điều này. Đó thực sự, thực sự, phong cách xấu!
JimmyB
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.