【问题标题】:Android: pass function reference to AsyncTaskAndroid:将函数引用传递给 AsyncTask
【发布时间】:2014-10-05 12:27:46
【问题描述】:

我是 android 新手,非常习惯于 web 开发。在 javascript 中,当您想要执行异步任务时,您将函数作为参数(回调)传递:

http.get('www.example.com' , function(response){
   //some code to handle response
});

我想知道我们是否可以对 android 的 AsyncTask 做同样的事情,将函数引用传递给 onPostExecute() 方法,它就会运行它。

有什么建议吗?

【问题讨论】:

  • 这不是你的做法^_^。您使用异步任务创建一个后台线程,并且您的代码又名 httprequest 进入 doInBackground() 方法,并为它定义返回类型。在后台操作完成后 onPostExecute() 被调用,您可以在其中编写代码来处理响应,因为现在您可以更新视图和所有内容。有一个 progressupdate 方法,当达到定义的进度时触发。一会儿我会得到文档链接

标签: java android android-asynctask


【解决方案1】:

是的,回调的概念在 Java 中也很常见。在 Java 中,您可以这样定义回调:

public interface TaskListener {
    public void onFinished(String result);
}

人们通常会像这样在AsyncTask 中嵌套此类侦听器定义:

public class ExampleTask extends AsyncTask<Void, Void, String> {

    public interface TaskListener {
        public void onFinished(String result);
    }

    ...
}

AsyncTask 中回调的完整实现如下所示:

public class ExampleTask extends AsyncTask<Void, Void, String> {

    public interface TaskListener {
        public void onFinished(String result);
    }

    // This is the reference to the associated listener
    private final TaskListener taskListener;

    public ExampleTask(TaskListener listener) {
        // The listener reference is passed in through the constructor
        this.taskListener = listener;
    }

    @Override
    protected String doInBackground(Void... params) {
        return doSomething();
    }

    @Override
    protected void onPostExecute(String result) {
        super.onPostExecute(result);

        // In onPostExecute we check if the listener is valid
        if(this.taskListener != null) {

            // And if it is we call the callback function on it.
            this.taskListener.onFinished(result);
        }
    }
}

onPostExecute() 在后台任务完成后立即被调用。你可以像这样使用整个东西:

ExampleTask task = new ExampleTask(new ExampleTask.TaskListener() {
    @Override
    public void onFinished(String result) {
        // Do Something after the task has finished
    }
});

task.execute();

或者您可以像这样完全单独定义TaskListener

ExampleTask.TaskListener listener = new ExampleTask.TaskListener() {
    @Override
    public void onFinished(String result) {
        // Do Something after the task has finished
    }
};

ExampleTask task = new ExampleTask(listener);    
task.execute();

或者你可以像这样子类TaskListener

public class ExampleTaskListener implements TaskListener {

    @Override
    public void onFinished(String result) {

    }
}

然后像这样使用它:

ExampleTask task = new ExampleTask(new ExampleTaskListener());    
task.execute();

您当然可以只覆盖AsyncTaskonPostExecute() 方法,但不建议这样做,而且在大多数情况下实际上是非常糟糕的做法。例如,您可以这样做:

ExampleTask task = new ExampleTask() {
    @Override
    public void onPostExecute(String result) {
        super.onPostExecute(result);

        // Your code goes here
    }
};

这与上面使用单独的侦听器接口的实现一样有效,但存在一些问题:

首先,您实际上可以将ExampleTask 全部破解。这一切都归结为上面的super.onPostExecute() 电话。如果您作为开发人员像上面那样覆盖 onPostExecute() 并忘记包含超级调用,或者出于任何原因将其删除,那么 ExampleTask 中的原始 onPostExecute() 方法将不再被调用。例如,使用TaskListener 的整个侦听器实现将突然不再工作,因为对回调的调用是在onPostExecute() 中实现的。您还可以通过许多其他方式破坏TaskListener,在不知不觉中或无意中影响ExampleTask 的状态,使其不再起作用。

如果您在重写这样的方法时查看实际发生的情况,就会更清楚发生了什么。通过覆盖onPostExecute(),您正在创建ExampleTask 的新子类。这与执行此操作完全相同:

public class AnotherExampleTask extends ExampleTask {

    @Override
    public void onPostExecute(String result) {
        super.onPostExecute(result);

        // Your code goes here
    }
}

所有这些都隐藏在称为匿名类的语言功能后面。突然覆盖这样的方法似乎不再那么干净和快速了,是吗?

总结:

  • 重写这样的方法实际上会创建一个新的子类。您不仅添加了回调,还修改了此类的工作方式,并且可能会在不知不觉中破坏很多东西。
  • 像这样的调试错误可能不仅仅是一个痛苦的问题。因为突然间ExampleTask 可能会抛出Exceptions 或者只是无缘无故地不再工作,因为你从未真正修改过它的代码。
  • 每个类都必须在适当和预期的地方提供侦听器实现。当然,您可以稍后通过覆盖 onPostExecute() 来添加它们,但这总是非常危险的。即使是享有 13k 声誉的 @flup 也忘记在他的回答中包含 super.onPostExecute() 电话,想象一下其他没有经验的开发人员可能会做什么!
  • 一点抽象永远不会伤害任何人。编写特定的侦听器可能需要更多的代码,但这是一个更好的解决方案。代码将更干净、更易读、更易于维护。使用诸如覆盖onPostExecute() 之类的快捷方式本质上会牺牲代码质量以获得一点便利。这绝不是一个好主意,从长远来看只会导致问题。

【讨论】:

  • 这个答案可以通过使用通用的interface TaskListener&lt;T&gt;onFinished(T result)来改进
  • @cricket_007 我不会称之为改进。每个任务都应该有自己严格定义的监听器。其他一切都会导致糟糕的设计和实现问题,即使忽略类型擦除也是如此。
  • 不过,这比将自己限制为仅回复 String 更灵活。我已经使用 AsyncTask 上的回调方法回答了几个问题。 For example,你说这会导致糟糕的设计?
  • @cricket_007 如果您期望除字符串之外的任何其他响应,您可以在回调中使用它而不是字符串。这个答案中的代码当然只是一个例子。是的,我认为这会导致糟糕的设计。例如,如果您执行两项任务,一项返回 ModelA,另一项返回 ModelB,会发生什么情况。您不能在同一个类上实现 AsyncResponse 和 AsyncResponse。而且你也不能给回调任何有意义的名字,因为无论哪个任务使用回调,方法名都是固定的。
  • @cricket_007 所以总的来说,你可以在这里和那里节省两三行代码,但是你会引入其他问题和限制,否则你就不必处理了。为每个任务编写特定的回调接口总是更好。
【解决方案2】:

在 Java 中,函数不像 JavaScript 那样是一等公民。 AsyncTask 将回调作为类中的方法提供,您应该重写它。

请参阅 Make an HTTP request with android 了解 AsyncTask 的子类,其中实现了发出 Web 请求的 doInBackground。

如果您想使用不同的回调执行多个 HTTP 请求,您可以覆盖 RequestTask 并使用不同的回调实现来实现 onPostExecute。 您可以使用匿名类来模拟 JavaScript 回调常用的闭包:

new RequestTask(){
    @Override
    public void onPostExecute(String result) {
        // Implementation has read only access to 
        // final variables in calling scope. 
    }
}.execute("http://stackoverflow.com");

正如 Xaver 所示,您还可以为侦听器创建一个完整的接口。如果您希望实现几个默认的 onPostExecute 函数并为特定调用选择其中一个默认实现,这似乎对我有用。

【讨论】:

  • 像这样覆盖onPostExecute() 很少是一个好主意。类必须在适当和预期的地方提供监听器功能。你正在做的事情在很多方面都很危险,主要是因为它可能会一起破坏RequestTask。请参阅我的答案的底部。
  • @XaverKapeller 实际上,超类的onPostExecute 没有做任何事情,调用它是毫无意义的。 onPostExecute 回调,而不是你可以偷偷挂上的一些内部实现方法。其目的是将结果通知给任务的原始创建者。
  • 嗯,是的,如果您正在实现一个新的AsyncTask,那是真的,但是如果您已经将AsyncTask 子类化为您的答案中的RequestTask,那么您可能会破坏已经实现的任何功能在RequestTask。这就是我在之前评论中的意思。
  • 但是两个类都没有在onPostExecute 回调方法中做任何事情,这是它应该的方式,因为它将在 UI 线程上调用。 RequestTask 存在的唯一原因是它为您实现了 HTTP 内容。回调方法仍有待实现,具体取决于您要对结果执行的操作。
【解决方案3】:

在 Kotlin 中

首先,创建类 AsyncTaskHelper,如下所示。

class AsyncTaskHelper() : AsyncTask<Callable<Void>, Void, Boolean>() {

    var taskListener: AsyncListener? = null

    override fun doInBackground(vararg params: Callable<Void>?): Boolean {
        params.forEach {
            it?.call()
        }
        return true
    }

    override fun onPreExecute() {
        super.onPreExecute()
    }

    override fun onPostExecute(result: Boolean?) {
        super.onPostExecute(result)
        taskListener?.onFinished(result as Any)
    }

}

interface AsyncListener {
    fun onFinished(obj: Any)
}

当你想使用异步任务时,可以使用下面的代码。

    AsyncTaskHelper().let {
        it.execute(Callable<Void> {
                //this area is the process do you want to do it in background
                // dosomething()
                }
            }
            null
        })
        it.taskListener = object : AsyncListener{
            override fun onFinished(obj: Any) {
                // this area is for the process will want do after finish dosomething() from Callable<Void> callback


            }

        }

从上面的代码。如果您想将您的流程分成几个任务。您可以按照下面的代码进行操作。

AsyncTaskHelper().let {
            it.execute(Callable<Void> {
                // task 1 do in background
                null
            },Callable<Void>{
                // task 2 do in background
                null
            },Callable<Void>{
                // task 3 do in background
                null
            })

            it.taskListener = object : AsyncListener {
                override fun onFinished(obj: Any) {
                    // when task1,task2,task3 has been finished . it will do in this area
                }

            }
        }

【讨论】:

    【解决方案4】:

    我也想为 Sup.la 的 Kotlin 解决方案做出贡献来自AsyncTask。 为了便于理解,我把解释分开了。

    如果你只想复制粘贴,直接跳到Step2 :)

    注意:请记住,AsyncTask 现在已弃用。但是,由于兼容性和其他原因,我认为它会保留一段时间。


    第 1 步

    这是泛型类的样子:

    import android.os.AsyncTask
    
    @Suppress("DEPRECATION") // As mentioned, AsyncTask is deprecated now
    class GenericAsyncTask() : AsyncTask<Callable<Any>, Void, Any?>() {
        override fun doInBackground(vararg params: Callable<Any>): Any? {
            // params receives vararg, meaning its an Array.
            // In this example we only want pass one function to easen up the example (-> [0]).
            return params[0]?.call()
        }
    }
    

    可以看出,AsyncTask 现在接受任何传递给它的函数 (Callable&lt;Any&gt;),其中 Anynull 可以是函数的结果。因此,这里没有人关心 AsyncTask 必须处理或返回的类型或传递的函数的样子。

    您现在可以像这样使用它:

    // In this example lets assume we have a method which returns a list of Strings
    var res: List<String>? = GenericAsyncTask().let {
            it.execute(
                Callable<Any> { your_random_function() }
            ).get() as? List<String>?
        }
    

    如您所见,我们仅通过强制转换来处理Any 的困境。这是可行的,因为除了null 之外,所有类型都源自Any。为了处理后者,我们使用as? 进行安全转换。因此,null 作为返回类型也被处理。


    第二步

    但是,最终我们甚至可以使这个更通用:

    // The same example result as shown above
    // The casting will be inferred directly from your_random_function() return-type
    val res: List<String>? = GenericAsyncTask().async(::your_random_function)
    
    // For the sake of completeness, this is valid as well
    val res: List<String>? = GenericAsyncTask().async<List<String>?> { your_random_function() }
    
    
    class GenericAsyncTask() : AsyncTask<Callable<Any>, Void, Any?>() {
    
        // This function takes any method you provide and even infers the result-type, thus, spares you the casting
        public fun<T> async(method: () -> T): T? {
            return this.let {
                it.execute(Callable<Any> { method() }).get() as? T?
            }
        }
    
        override fun doInBackground(vararg params: Callable<Any>): Any? { ... }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-02-15
      • 1970-01-01
      • 2011-03-05
      • 2014-08-21
      • 2013-07-13
      • 1970-01-01
      相关资源
      最近更新 更多