AsyncTask และการจัดการข้อผิดพลาดบน Android


147

ฉันแปลงรหัสของฉันจากการใช้เพื่อHandler AsyncTaskสิ่งหลังสุดยอดมากในสิ่งที่ทำ - การอัพเดตแบบอะซิงโครนัสและการจัดการผลลัพธ์ในเธรด UI หลัก AsyncTask#doInBackgroundสิ่งที่ไม่ชัดเจนให้ฉันเป็นวิธีการจัดการกับข้อยกเว้นหากสิ่งที่ไปยุ่งเหยิงใน

วิธีที่ฉันทำคือมีตัวจัดการข้อผิดพลาดและส่งข้อความถึงมัน มันใช้งานได้ดี แต่มันเป็นวิธีที่ "ถูก" หรือมีทางเลือกที่ดีกว่า?

นอกจากนี้ฉันเข้าใจว่าถ้าฉันกำหนดตัวจัดการข้อผิดพลาดเป็นฟิลด์กิจกรรมก็ควรดำเนินการในเธรด UI อย่างไรก็ตามบางครั้ง (คาดเดาไม่ได้มาก) ฉันจะได้รับข้อยกเว้นโดยบอกว่ารหัสที่ถูกเรียกใช้Handler#handleMessageนั้นกำลังรันอยู่บนเธรดที่ไม่ถูกต้อง ฉันควรเริ่มต้นตัวจัดการข้อผิดพลาดActivity#onCreateแทนหรือไม่ วางrunOnUiThreadลงในHandler#handleMessageดูเหมือนซ้ำซ้อน แต่ก็ดำเนินการอย่างน่าเชื่อถือ


ทำไมคุณถึงต้องการแปลงรหัสของคุณ? มีเหตุผลที่ดีหรือไม่?
HGPB

4
@Haraldo มันเป็นวิธีการเข้ารหัสที่ดีกว่าอย่างน้อยนั่นก็เป็นความรู้สึกของฉัน
Bostone

คำตอบ:


178

มันใช้งานได้ดี แต่เป็นวิธีที่ "ถูกต้อง" และมีทางเลือกที่ดีกว่านี้หรือไม่?

ฉันค้างไว้ThrowableหรือExceptionในAsyncTaskอินสแตนซ์ของตัวเองแล้วทำอะไรกับมันในonPostExecute()ดังนั้นการจัดการข้อผิดพลาดของฉันจึงมีตัวเลือกในการแสดงข้อความโต้ตอบบนหน้าจอ


8
ยอดเยี่ยม! ไม่จำเป็นต้องลิงที่มี Handlers อีกต่อไป
Bostone

5
นี่เป็นวิธีที่ฉันควรจะยึดไว้กับ Throwable หรือ Exception หรือไม่? "เพิ่มตัวแปรอินสแตนซ์ให้กับคลาสย่อย AsyncTask ของคุณซึ่งจะเก็บผลลัพธ์ของการประมวลผลเบื้องหลังของคุณ" เมื่อคุณได้รับข้อยกเว้นให้เก็บข้อยกเว้น (หรือ error-string / code อื่น ๆ ) ในตัวแปรนี้ เมื่อ onPostExecute ถูกเรียกดูว่าอินสแตนซ์ตัวแปรนี้ถูกตั้งค่าเป็นข้อผิดพลาดบางอย่าง ถ้าเป็นเช่นนั้นแสดงข้อความข้อผิดพลาด "(จากผู้ใช้" Streets of Boston " groups.google.com/group/android-developers/browse_thread/thread/ ...... )
OneWorld

1
@ OneWorld: ใช่ว่าควรจะดี
CommonsWare

2
สวัสดี CW คุณช่วยอธิบายวิธีการของคุณในรายละเอียดเพิ่มเติมหน่อยได้ไหม - อาจมีตัวอย่างรหัสย่อ ขอบคุณมาก!!
Bruiser

18
@Bruiser: github.com/commonsguy/cw-lunchlist/tree/master/15-Internet/…มีAsyncTaskรูปแบบที่ฉันอธิบาย
คอมมอนส์

140

สร้างวัตถุ AsyncResult (ซึ่งคุณสามารถใช้ในโครงการอื่น ๆ )

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

ส่งคืนออบเจ็กต์นี้จากวิธี AsyncTask doInBackground ของคุณและตรวจสอบใน postExecute (คุณสามารถใช้คลาสนี้เป็นคลาสพื้นฐานสำหรับงาน async อื่น ๆ ของคุณ)

ด้านล่างคือการจำลองของงานที่ได้รับการตอบสนอง JSON จากเว็บเซิร์ฟเวอร์

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
ฉันชอบมัน. การห่อหุ้มที่ดี เนื่องจากนี่คือการถอดความจากคำตอบเดิมคำตอบอยู่ แต่แน่นอนสมควรได้รับจุด
Bostone

นี่เป็นการสาธิตที่ดีว่าเจเนอเรชั่นมีประโยชน์อย่างไร มันมีกลิ่นแปลก ๆ ในแง่ของความซับซ้อน แต่ไม่ใช่ในแบบที่ฉันสามารถพูดได้
num1

4
ความคิดที่ดีเพียงหนึ่งคำถาม: ทำไมคุณเรียกsuper()ในAsyncTaskResultเมื่อชั้นเรียนไม่ได้ขยายอะไร?
donturner

7
"ไม่มีอันตราย" - รหัสซ้ำซ้อนมักเป็นอันตรายต่อการอ่านและการบำรุงรักษา ออกไปจากที่นั่น! :)
donturner

2
จริงๆชอบวิธีการแก้ ... มาที่จะคิดเกี่ยวกับมัน - C # คนที่ใช้ว่าวิธีการเดียวกันใน C # สอดคล้อง BackgroundTask การดำเนินพื้นเมือง ...
Vova

11

เมื่อฉันรู้สึกว่าจำเป็นต้องจัดการข้อยกเว้นAsyncTaskอย่างถูกต้องฉันใช้สิ่งนี้เป็นคลาสพิเศษ:

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

}

ตามปกติคุณจะแทนที่doInBackgroundในคลาสย่อยเพื่อทำงานเบื้องหลังโดยมีความสุขในการขว้างข้อยกเว้นเมื่อจำเป็น จากนั้นคุณจะถูกบังคับให้ใช้งานonPostExecute(เพราะมันเป็นนามธรรม) และสิ่งนี้เตือนให้คุณจัดการทุกประเภทExceptionอย่างละเอียดซึ่งจะถูกส่งเป็นพารามิเตอร์ ในกรณีส่วนใหญ่ข้อยกเว้นนำไปสู่เอาต์พุต UI บางประเภทดังนั้นจึงonPostExecuteเป็นสถานที่ที่สมบูรณ์แบบในการทำเช่นนั้น


1
อืมทำไมไม่เพียงแค่ส่งparamsต่อดังนั้นจึงคล้ายกับต้นฉบับและง่ายกว่าในการโยกย้าย
TWiStErRob

@TWiStErRob ไม่มีอะไรผิดปกติกับความคิดนั้น มันเป็นเรื่องของความชอบส่วนตัวที่ฉันเดาเพราะฉันมักจะไม่ใช้ params ฉันชอบมากกว่าnew Task("Param").execute() new Task().execute("Param")
sulai

5

หากคุณต้องการใช้กรอบการทำงานของ RoboGuice ซึ่งให้ผลประโยชน์อื่น ๆ กับคุณคุณสามารถลองใช้ RoboAsyncTask ซึ่งมี Callback onException () เพิ่มเติม ใช้งานได้ดีจริงและฉันใช้มัน http://code.google.com/p/roboguice/wiki/RoboAsyncTask


ประสบการณ์ของคุณกับสิ่งนี้คืออะไร? ค่อนข้างเสถียรใช่ไหม
nickaknudson

คือRoboGuiceยังคงมีชีวิตอยู่? Seems ยังไม่ได้รับการปรับปรุงตั้งแต่ปี 2012?
Dimitry K

ไม่ RoboGuice ตายแล้วและเลิกใช้แล้ว Dagger2 เป็นอุปกรณ์ทดแทนที่แนะนำ แต่เป็นห้องสมุด DI กระดูกเปลือยเท่านั้น
Avi Cherry

3

ฉันสร้างคลาสย่อย AsyncTask ของตัวเองด้วยอินเทอร์เฟซที่กำหนดการโทรกลับเพื่อความสำเร็จและความล้มเหลว ดังนั้นหากมีข้อผิดพลาดเกิดขึ้นใน AsyncTask ของคุณฟังก์ชั่น onFailure จะได้รับการส่งผ่านข้อยกเว้นมิฉะนั้นการเรียกกลับ onSuccess จะได้รับผลลัพธ์ของคุณ ทำไม android ถึงไม่มีสิ่งที่ดีกว่ามีอยู่เหนือฉัน

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

โซลูชันที่ครอบคลุมมากขึ้นสำหรับCagatay Kalanดังแสดงด้านล่าง:

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

ตัวอย่างงาน

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

ฉันได้รับ getResources () วิธีการใน myActivity.getApplicationContext (). getResources ()
Stephane

2

คลาสง่าย ๆ นี้สามารถช่วยคุณได้

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

อีกวิธีหนึ่งที่ไม่ได้ขึ้นอยู่กับการแบ่งปันสมาชิกตัวแปรคือใช้ยกเลิก

นี่คือจากเอกสาร Android:

บูลีนสุดท้ายสาธารณะยกเลิก (บูลีนอาจInInterrupt ifRunning)

พยายามยกเลิกการดำเนินการของงานนี้ ความพยายามนี้จะล้มเหลวหากงานเสร็จสมบูรณ์แล้วถูกยกเลิกไปแล้วหรือไม่สามารถยกเลิกได้ด้วยเหตุผลอื่น หากสำเร็จและงานนี้ยังไม่เริ่มต้นเมื่อมีการยกเลิกการเรียกใช้งานนี้ไม่ควรรัน หากงานได้เริ่มขึ้นแล้วพารามิเตอร์ mayInterruptIfRunning จะพิจารณาว่าเธรดที่เรียกใช้งานนี้ควรถูกขัดจังหวะในความพยายามที่จะหยุดงานหรือไม่

การเรียกเมธอดนี้จะส่งผลให้ onCancelled (Object) ถูกเรียกใช้บนเธรด UI หลังจากส่งคืน doInBackground (Object []) การเรียกใช้เมธอดนี้รับประกันว่าจะไม่เรียกใช้ onPostExecute (Object) หลังจากเรียกใช้เมธอดนี้คุณควรตรวจสอบค่าที่ส่งคืนโดย isCancelled () เป็นระยะ ๆ จาก doInBackground (Object []) เพื่อให้งานเสร็จเร็วที่สุด

ดังนั้นคุณสามารถโทรยกเลิกในคำสั่ง catch และตรวจสอบให้แน่ใจว่า onPostExcute ไม่เคยถูกเรียก แต่จะเรียก onCancelled แทนในเธรด UI ดังนั้นคุณสามารถแสดงข้อความข้อผิดพลาด


คุณไม่สามารถแสดงข้อความแสดงข้อผิดพลาดได้อย่างถูกต้องเนื่องจากคุณไม่ทราบปัญหา (ข้อยกเว้น) คุณยังต้องจับและส่งคืน AsyncTaskResult ผู้ใช้ที่ยกเลิกไม่ใช่ข้อผิดพลาด แต่เป็นการโต้ตอบที่คาดหวัง: คุณแยกแยะระหว่างสิ่งเหล่านี้ได้อย่างไร
TWiStErRob

cancel(boolean)ส่งผลให้ในการเรียกร้องให้onCancelled()มาตั้งแต่เริ่มต้น แต่onCancelled(Result)ถูกเพิ่มเข้ามาใน API 11
TWiStErRob

0

ที่จริงแล้ว AsyncTask ใช้ FutureTask & Executor, FutureTask รองรับข้อยกเว้นโซ่ก่อนอื่นมากำหนดคลาสตัวช่วย

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

ประการที่สองมาใช้กัน

    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

ส่วนตัวผมจะใช้วิธีนี้ คุณสามารถตรวจจับข้อยกเว้นและพิมพ์การติดตามสแต็กหากคุณต้องการข้อมูล

ทำให้งานของคุณเป็นพื้นหลังคืนค่าบูลีน

มันเป็นเช่นนี้:

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

        @Override
                protected void onPostExecute(Boolean result) {

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

-2

ความเป็นไปได้อีกอย่างหนึ่งคือการใช้Objectเป็นชนิดส่งคืนและonPostExecute()ตรวจสอบประเภทวัตถุ มันสั้น

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
ไม่เกี่ยวข้องอย่างสมบูรณ์
Dinu

-2

หากคุณทราบข้อยกเว้นที่ถูกต้องคุณสามารถโทร

Exception e = null;

publishProgress(int ...);

เช่น:

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

และไปที่ "onProgressUpdate" และดำเนินการต่อ

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

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

สิ่งนี้จะเป็นประโยชน์ในบางกรณีเท่านั้น นอกจากนี้คุณสามารถเก็บGlobal Exceptionตัวแปรและเข้าถึงข้อยกเว้น


1
กรุณาอย่าทำเช่นนี้ นั่นเป็นสไตล์ที่แย่จริงๆ!
JimmyB
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.