可读的有序AsyncTask代码的最佳方法

时间:2018-09-24 21:09:07

标签: java android

我会转圈尝试不同的技巧,但每次遇到不同的障碍。

理想情况下,我希望我的代码看起来像这样;

public class MyActivity extends AppCompatActivity {
  SomeMethod();
  new SomeAsyncTask().execute();
  SomeOtherMethod();
  new SomeOtherAsyncTask().execute();
}

但是我需要按顺序执行每个方法(并等待前一个方法完成)。假设第一个AsyncTask对身份验证令牌进行身份验证并将身份验证令牌存储在静态位置-然后随后的调用将需要此令牌。

我的AsyncTask与API通信时必须是异步的。

我知道我可以使用AsyncTasks的onPostExecute()方法,但是我不喜欢它造成的混乱(必须跳过代码)。

我知道我也可以创建一个接口并将一个类传递给我的AsyncTask,但这也无济于事(代码仍然会跳来跳去)。

我以为我想出了一个完美的解决方案,调用SomeAsyncTask.execute().get()等待任务完成,然后继续执行下一行代码,但今天我也遇到了一些问题。

我还可以使用其他任何技术来结合前台线程和后台线程活动来实现干净的代码吗?

编辑目前,我正在考虑是否可以使我的所有方法都异步,然后再从异步方式调用它们,就像这样;

public class MyActivity extends AppCompatActivity {
    new AsyncTask<Void, Void, Void>() {
        @Override
        protected Void doInBackground( final Void ... params ) {
              SomeMethod();
              SomeMethod2();
              SomeOtherMethod();
              SomeOtherMethod2();
              return null;
        }

        @Override
        protected void onPostExecute( final Void result ) {
            // any ui stuff
        }
    }.execute();
}

EDIT2 当前首选的解决方案是;

在我的AsyncTask中定义一个“ PostExecute”接口;

public class GetMerchantDataAsync extends AsyncTask<Void, Void, Void> {
    private Context mContext;
    private PostExecute mDelegate;
    public interface PostExecute {
        void Callback();
    }

    public GetMerchantDataAsync(Context context, PostExecute delegate) {
        mContext = context;
        mDelegate = delegate;
    }

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

    @Override
    protected void onPostExecute(Void v){
        mDelegate.Callback();
    }
}

然后定义接口的实例并将其传递给构造函数;

public class StartActivity extends Activity {

    private final Context context = this;

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

        new GetMerchantDataAsync(context, getMerchantDataPostExecute).execute();
    }

    private GetMerchantDataAsync.PostExecute getMerchantDataPostExecute = new GetMerchantDataAsync.PostExecute() {
        @Override
        public void Callback() {
            DoSomethingElse();
        }
    };
}

这有点混乱(不是一个代码块),但是它几乎读为一个块,希望具有合理/一致的命名将易于阅读。

2 个答案:

答案 0 :(得分:2)

有很多方法可以给这只猫换皮,还可以使用一些不错的异步等待库,类似于Promise。

或者,如果满足您的需求,也可以简单地进行 onPostExecute 内联回调,并在生命周期仍然有效的情况下调用onPostExecute的下一步。

但是,如果您想要一些简洁的代码,则应考虑Kotlin和协程。

有关超时,取消和错误处理的示例:

注意withTimeout和withContext调用。这些允许您在继续之前等待其中的内容。另外,这里的方法是一个可挂起的协程,这是一个额外的好处,这意味着调用方也可以等待它。您可以使用c.resume(returnValueType)恢复呼叫者。

如果您觉得这太复杂了,那么我会坚持使用onPostExecute,但是大多数开发人员在AsyncTasks上忘记了。

    如果您通常退出活动,请取消
  • AsyncTasks

  • 如果允许线程池,则AsyncTasks可能会无序完成 管理

  • 对象锁必须包装以确保并发 变量的修改不会成为问题。
  • 回调应处理错误,而不仅仅是正向路由
  • 必须在Async任务之外管理超时,这会增加更多的膨胀。

因此,您看到的只是执行myAsyncTask.execute {onPostExecute(value){// dostuff}} 可能看起来像简单的快速代码,如果不处理所有可能出现的小问题,肯定很容易出错。

协程在易于阅读的括号内为所有这些内容提供了不错的包装。

private suspend fun updateConfigurationWithChanges() : Boolean = suspendCoroutine { c ->
        A35Log.v(mClassTag, "updateConfigurationWithChanges")
        setIsActionInProgress(true)
        mSaveOrUpdateJob = launch(UI) {
            try{
                withTimeout(TIMEOUT_FOR_DB_INTERACTION_MS){
                    showProgressDialog(mClassTag, "Saving", false)
                    mSelectedConfiguration!!.setLastSavedDateStr(DateTimeHelper.getNowTimeStamp())
                    val updatedRecordCount = withContext(DefaultDispatcher){ SSDBHelper.updateConfiguration(mSelectedConfiguration!!) }
                    if(updatedRecordCount > 0){
                        showFancyToast("Successfully updated", true, FancyToast.SUCCESS)
                        c.resume(true)
                    }else{
                        showFancyToast("Error while updating, please try again or press back", true, FancyToast.SUCCESS)
                        c.resume(false)
                    }
                }
            }catch(ex: JobCancellationException){
                showFancyToast("Save canceled", true, FancyToast.ERROR, "Save canceled: ${ex.message}")
                c.resume(false)
            }catch (ex: TimeoutCancellationException) {
                showFancyToast("Timed out updating, please try again or press back", true, FancyToast.ERROR, "Timed out updating database: ${ex.message}")
                c.resume(false)
            }catch(ex: Exception){
                showFancyToast("Error updating database, please try again or press back", true, FancyToast.ERROR, "Error updating database: ${ex.message}")
                c.resume(false)
            }
        }
    }

当然,如果用户没有伤到屏幕就退出屏幕,还是要取消该操作。

 override fun onPause() {
    super.onPause()
    if(mSaveOrUpdateJob != null && mSaveOrUpdateJob!!.isActive) {
        A35Log.v(mClassTag, "canceling saveOrUpdate job")
        mSaveOrUpdateJob?.cancel()
    }
}

但是,归根结底,如果管理asyncTask和onPostExecute可以满足您的需求,并且您的所有i都被点缀了,那么您应该做的最适合您的情况。< / p>

如果出于完整性考虑,如果您想知道如何调用并等待上面的方法,它看起来像这样。

 fun myButtonClick(){
         launch(UI) {
                if(mIsNewConfiguration){
                    //save first if new
                    if(withContext(DefaultDispatcher){ isNewOrUnchangedName() }) {
                        if (withContext(DefaultDispatcher) { saveNewConfigurationToDatabase() }) {
                            refreshCurrentNamesList()
                            launchConnectAndSendToDeviceFlow()
                        }
                    }
                }else {
                    //update first if changes made
                    if(withContext(DefaultDispatcher){ isNewOrUnchangedName() }) {
                        if(DeviceAndConfigurationHelper.hasConfigurationModelChanged(mOriginalCopyConfiguration!!, mSelectedConfiguration!!)){
                            if(withContext(DefaultDispatcher) { updateConfigurationWithChanges() }){
                                refreshCurrentNamesList()
                                launchConnectAndSendToDeviceFlow()
                            }
                        }else{
                            launchConnectAndSendToDeviceFlow()
                        }
                    }
                }
            }
       }

快乐编码。

答案 1 :(得分:0)

我可以想到两种方法,根据您的约束,您可以选择其中一种,其中一种使用自定义异步任务:

创建一个自定义AsyncTask,它将被所有异步任务继承(或者也可以内联完成):

public abstract class BaseAsyncTask<T, U, V> extends AsyncTask<T, U, V> {

    public interface Callback<X> {

        void onComplete(X param);
    }

    private Callback mainCallback;

    public void execute(Callback<V> callback, T... params) {
        mainCallback = callback;
        executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, params);
    }

    @Override
    protected void onPostExecute(V v) {
        super.onPostExecute(v);
        if (mainCallback != null) {
            mainCallback.onComplete(v);
        }
    }
}

对于您的情况,可以这样使用:

public static class TestThinghy {

    BaseAsyncTask<String, String, String> task = new BaseAsyncTask<String, String, String>() {
        @Override
        protected String doInBackground(String... strings) {
            return null;
        }
    };

    void firstFunOnMainThread() {
        //....do whatever...///
    }

    void runEverything() {
        firstFunOnMainThread();
        new BaseAsyncTask<String, String, String>() {
            @Override
            protected String doInBackground(String... strings) {
                return "testing thinghy";
            }
        }.execute(new Callback<String>() {
            @Override
            public void onComplete(String param) {
                secondRunOnMainThread();
                //you can start new async here, or better start it from a different methond
            }
        }, "the other string param");
    }

    void secondRunOnMainThread() {
        ///...whatever here
    }
}

另一种方法是使用RxJava,它是一种功能强大的方法,可为您提供大量方法来链接此类任务并决定如何运行它们,为此,我将让您进行一些研究。