Android: 将函数引用传递给AsyncTask

18

我是Android开发新手,之前主要从事Web开发工作。在JavaScript中,当你想要执行一个异步任务时,你需要将一个函数作为参数传递(即回调函数):

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

我在想我们是否可以在Android的AsyncTask中做同样的事情,将函数引用传递给onPostExecute()方法,并使其运行。

有什么建议吗?


这不是正确的做法 ^_^。你需要使用async task创建一个后台线程,将你的代码(例如httprequest)放在doInBackground()方法中,并为其定义返回类型。当后台操作完成时,会调用onPostExecute()方法,在其中编写处理响应的代码,因为现在你可以更新视图等。还有一个progressupdate方法,当达到定义的进度时会触发它。我一会儿会给你文档链接。 - Rico
http://developer.android.com/reference/android/os/AsyncTask.html - Rico
4个回答

48

是的,在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()会在后台任务完成后立即调用。您可以像这样使用整个内容:

onPostExecute()方法会在后台任务完成后立即被调用。

您可以按照以下方式使用整个内容:

onPostExecute()后台任务完成后立即被调用,您可以在此处执行任何必要的UI更新。

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(),但忘记包含super调用或者出于某种原因将其删除,则ExampleTask中的原始onPostExecute()方法将不再被调用。例如,整个带有TaskListener的监听器实现将突然不起作用,因为回调调用是在onPostExecute()中实现的。您还可以通过无意中影响ExampleTask的状态以使TaskListener失效。

如果您看一下重写此类方法时实际发生的事情,那么就会更清楚发生了什么。通过重写onPostExecute(),您正在创建ExampleTask的新子类。这将完全相同,就像这样做一样:

public class AnotherExampleTask extends ExampleTask {

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

        // Your code goes here
    }
}

所有这些都只是隐藏在语言特性“匿名类”背后。突然间像这样覆盖一个方法就不再那么干净和快速了,是吧?

总结:

  • 像这样覆盖一个方法实际上会创建一个新的子类。你不仅仅是添加一个回调函数,而是修改了这个类的工作方式,并且可能会不知不觉地破坏很多东西。
  • 调试此类错误可能远不止让人头疼。因为突然间ExampleTask可以抛出异常或者根本停止工作,而没有任何明显的原因,因为你从未实际修改过它的代码。
  • 每个类都必须在适当和有意义的位置提供侦听器实现。当然,你可以稍后通过覆盖onPostExecute()来添加它们,但这总是非常危险的。即使@flup拥有13k的声望,他也曾经在答案中忘记包含super.onPostExecute()调用,想象一下其他不那么有经验的开发人员会做什么!
  • 一点抽象从未伤害过任何人。编写特定的侦听器可能需要更多的代码,但这是一个更好的解决方案。代码将更加清晰、易读和可维护。使用像覆盖onPostExecute()这样的快捷方式实质上牺牲了代码质量以换取一点便利。这永远不是一个好主意,只会在长期运行中引起问题。

这个答案可以通过使用通用的 interface TaskListener<T>onFinished(T result) 来改进。 - OneCricketeer
@cricket_007 如果你期望回调函数返回的不是字符串,那么你需要在回调函数中使用相应的类型。当然,这个答案中的代码只是一个例子。我认为这会导致糟糕的设计。例如,如果你执行两个任务,一个返回ModelA,另一个返回ModelB。你不能在同一个类上实现AsyncResponse<ModelA>和AsyncResponse<ModelB>。而且你也不能给回调函数起有意义的名称,因为方法名是固定的,无论哪个任务使用回调函数。 - Xaver Kapeller
@cricket_007 所以总的来说,你可能会在这里和那里节省两三行代码,但是你引入了其他问题和限制,否则你就不必处理。为每个任务编写特定的回调接口总是更好的选择。 - Xaver Kapeller
那么,VolleyRequest<T>Response<T> 不好用吗?而且我不明白为什么不能同时实现 AsyncResponse<ModelA>AsyncResponse<ModelB>,这难道不就是重载方法吗? - OneCricketeer
@cricket_007 不,你不能在同一个类上实现 AsyncResponse<ModelA>AsyncResponse<ModelB>。试试定义一个通用接口并尝试像这样在同一个类上实现它:public class Example implements AsyncResponse<ModelA>, AsyncResponse<ModelB>。这是行不通的。Java != C#。类型擦除阻止了任何类似这样的操作。当然,你可以为每个回调创建字段或者内联使用它们,但我认为这很难证明这会使代码更易读。 - Xaver Kapeller
显示剩余3条评论

2
在Java中,与JavaScript相比,函数不是一等公民。AsyncTask将回调作为类中的一个方法,并要求您覆盖它。
请参阅使用Android进行HTTP请求,其中包含一个实现doInBackground并进行Web请求的AsyncTask子类。
如果您想使用不同的回调执行多个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。请参见我的答案底部。 - Xaver Kapeller
@XaverKapeller 实际上,超类的 onPostExecute 方法并没有做任何事情,调用它是相当无意义的。onPostExecute 就是回调函数,而不是你可以偷偷钩入的内部实现方法。它的目的是通知任务的原始创建者结果。 - flup
是的,如果您正在实现新的“AsyncTask”,那么这是正确的。但是,如果您已经像您回答中建议的那样对“AsyncTask”进行了子类化,比如“RequestTask”,那么您可能会破坏已经在“RequestTask”中实现的任何功能。这就是我在之前评论中所说的意思。 - Xaver Kapeller
但是在“onPostExecute”回调方法中,两个类都不会做任何事情,这样才对,因为它将在UI线程上调用。RequestTask存在的唯一原因是为您实现HTTP方面的功能。回调方法仍然需要根据您希望对结果执行什么操作来实现。 - flup

1

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

            }
        }

0

我也想为Sup.la的Kotlin解决方案做出贡献,使其更加通用,以便您只对异步执行函数.execute()和从AsyncTask获取结果.get()感兴趣。

我将解释分成几个部分,以使其更易于理解。

如果您只想复制粘贴,请直接跳到第二步 :)

注意:请记住,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<Any>),其中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也被处理。


步骤2

然而,最终我们甚至可以使其更加通用:

// 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? { ... }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接