从 AsyncTask 返回数据到 MainActivity 的最佳方法

Posted

技术标签:

【中文标题】从 AsyncTask 返回数据到 MainActivity 的最佳方法【英文标题】:Best way to return data to MainActivity from AsyncTask 【发布时间】:2020-02-27 00:54:23 【问题描述】:

我在我的应用程序中使用ASyncTask 通过 REST API 从 Web 服务 (Bitly) 获取一些数据(短 URL)。

ASyncTask 完成时,我想将结果传回我的MainActivity

使用AsyncTaskonPostExecute 方法可以将数据返回到MainActivity

我已经阅读并阅读了有关如何执行此操作的信息,似乎有两种通用方法。

最初我使用的是“WeakReference”方法,即在 AsyncTask 类的开头创建对 MainActivity 的弱引用,如下所示:

private class getShortURL extends AsyncTask<String, Void, String> 

    private WeakReference<MainActivity> mainActivityWeakReference;

    myASyncTask(MainActivity activity) 
        mainActivityWeakReference = new WeakReference<>(activity);
    
etc etc

使用这种方法,您的 AsyncTask 类位于您的 MainActivity 类之外,因此需要通过弱引用来引用很多东西。

这工作得很好(除了我怀疑——可能是错误的——这个弱引用可能是偶尔出现 NPE 的原因),但后来我找到了另一种做事方式。

第二种方法涉及将ASyncTask 类移动到MainActivity 类中。

通过这种方式,我能够直接访问 MainActivity 类中可访问的所有内容,包括在 MainActivity 中定义的 UI 元素和方法。这也意味着我可以访问字符串等资源,并且可以生成toasts 来告知用户正在发生的事情。

在这种情况下,可以删除上面的整个 WeakReference 代码,并且可以将 AsyncTask 类设为私有。

然后我也可以直接在onPostExecute 中执行此类操作,或者将其保存在MainActivity 中的一个方法中,我可以直接从onPostExecute 调用: 缩短进度条.setIndeterminate(false); short_progress_bar.setVisibility(View.INVISIBLE);

    if (!shortURL.equals("")) 
        // Set the link URL to the new short URL
        short_link_url.setText(shortURL);
     else 
        CommonFuncs.showMessage(getApplicationContext(), getString(R.string.unable_to_shorten_link));
        short_link_url.setHint(R.string.unable_to_shorten_link);

    

(请注意,CommonFuncs.showMessage() 是我自己对 toast 函数的封装,以便于调用)。

但是,android Studio 随后会发出警告“AsyncTask 类应该是静态的,否则可能会发生泄漏”。

如果我将方法设为静态,则会收到一条警告,指出我想从 onPostExecute 调用的来自 MainActivity 的方法不能被调用,因为它是非静态的。

如果我将 MainActivity 中的那个方法设为静态方法,那么它就无法访问字符串资源和任何其他非静态方法——我就掉进了兔子洞!

如果我只是将代码从MainActivity 中的方法移动到onPostExecute 方法中,正如您所期望的那样,情况也是如此。 所以...

将 AsyncTask 作为非静态方法真的是一件坏事吗? (我的 应用程序似乎在 AS 中可以正常处理此警告,但我显然没有 想在我的应用中造成内存泄漏。 WeakReference 方法实际上是更正确、更安全的方法吗? 如果我使用WeakReference 方法,我如何创建像toasts 这样需要在UI 线程上运行并访问字符串的东西 来自MainActivity的资源等?

我在某处读到了有关创建interface 的信息,但有点迷路了,再也找不到了。这也不会像WeakReference 那样对MainActivity 有同样的依赖,这是一件坏事吗?

我真的在寻找有关如何将一些数据返回到 MainActivity 和从 AsyncTask 的 UI 线程的最佳实践指南,该指南是安全的,不会有内存泄漏的风险。

【问题讨论】:

奇怪的故事。我在活动中有很多 AsyncTasks,但从未收到过这样的警告。请从任务中删除您自己的祝酒词,看看是否有什么不同。 删除 toast 没有任何区别。事实上,在我添加 toasts 之前很久我就遇到了这些问题。 【参考方案1】:

将 AsyncTask 作为非静态方法真的是一件坏事吗? (我的应用在 AS 中似乎可以很好地处理此警告,但我显然不想在我的应用中造成内存泄漏。

是的,你的 Views 和你的 Context 会泄露。

旋转足够多,您的应用将崩溃。

WeakReference 方法实际上是更正确、更安全的方法吗?

这是一头死猪的口红,在这种情况下,WeakReference 更像是一种破解而不是解决方案,绝对不是正确的解决方案。

您正在寻找的是一种来自 something 的事件总线形式,它比 Activity 寿命更长。

您可以为此使用保留的 Fragments* 或 Android 架构组件 ViewModel。

您可能需要引入观察者模式(但不一定是 LiveData)。

如果我使用 Wea​​kReference 方法,如何创建需要在 UI 线程上运行的 toast 以及从 MainActivity 访问字符串资源等内容?

不要在doInBackground() 中运行那种东西。

我真的在寻找关于如何将一些数据从 AsyncTask 中返回到 MainActivity 和 UI 线程的最佳实践指南,该 AsyncTask 是安全的并且不会有内存泄漏的风险。

最简单的方法是使用this library(或者写一些你自己做同样事情的东西,取决于你),将EventEmitter放入ViewModel,然后订阅/取消订阅这个EventEmitter在你的 Activity 中。

public class MyViewModel: ViewModel() 
    private final EventEmitter<String> testFullUrlReachableEmitter = new EventEmitter<>();

    public final EventSource<String> getTestFullUrlReachable() 
        return testFullUrlReachableEmitter;
    

    public void checkReachable() 
        new testFullURLreachable().execute() 
    

    private class testFullURLreachable extends AsyncTask<Void, Void, String>  
        ... 
        @Override
        public void onPostExecute(String result) 
            testFullUrlReachableEmitter.emit(result);
        
    

在你的活动/片段中

private MyViewModel viewModel;

@Override
protected void onCreate(Bundle savedInstanceState) 
    super.onCreate(savedInstanceState);
    viewModel = ViewModelProviders.of(this).get(MyViewModel.class);
    // ...


private EventSource.NotificationToken subscription;

@Override
protected void onStart()  
    super.onStart();
    subscription = viewModel.getTestFullUrlReachable().startListening((result) -> 
        // do `onPostExecute` things here
    );


@Override
protected void onStop()  
    super.onStop();
    if(subscription != null) 
        subscription.stopListening();
        subscription = null;
    

【讨论】:

哇....有很多东西要吸收!我需要一段时间才能理解它。谢谢。 @EpicPandaForce 顺便说一句,我并没有尝试在 doInBackground() 方法中弹出 toast 或访问字符串资源,仅在 onPostExwcute() 方法中。 OK..为此苦苦挣扎。 ViewModelProviders 无法解决。 onStart() 代码包含一个我以前没有使用过的 lambda - 另一个可以深入了解这一点的兔子洞。另外,当我添加它时,ViewModel 的东西也不满意(ViewModel 上的Invalid method declaration; return type required.)。 我还更新了原始问题,因为第一个代码示例显示 WeakReference 实际上来自错误的方法(这也让我在理解这个解决方案时有些困惑,因为我必须翻译方法名称 - 哦!)

以上是关于从 AsyncTask 返回数据到 MainActivity 的最佳方法的主要内容,如果未能解决你的问题,请参考以下文章

如何将变量从 AsyncTask 返回到 Android 中的 Google Map 活动

从 AsyncTask Android 返回数据

在返回结果之前等待 AsyncTask.execute(()) 完成

AsyncTask 不返回值

从Android中的AsyncTask返回一个值[重复]

无法从 AsyncTask 返回值;使用接口和委托;