如何防止 JobService 使用的 AsyncTask 中的上下文泄漏

Posted

技术标签:

【中文标题】如何防止 JobService 使用的 AsyncTask 中的上下文泄漏【英文标题】:How to prevent Context leak in AsyncTask used by JobService 【发布时间】:2018-02-08 09:27:48 【问题描述】:

我需要做一些后台工作,这需要 JobService 中的上下文(我正在使用 Firebase JobDispatcher,因为我们支持 api 16+)我已经阅读了很多关于 JobService 和 AsyncTasks 的文章,但我如果您需要上下文,找不到任何关于如何组合它们的好文章。

我的工作服务

import com.firebase.jobdispatcher.JobParameters;
import com.firebase.jobdispatcher.JobService;

public class AsyncJobService extends JobService 

    @Override
    public boolean onStartJob(JobParameters job) 
        new AsyncWork(this, job).execute();
        return true;
    

    @Override
    public boolean onStopJob(JobParameters job) 
        return false;
    

我的异步任务

import android.os.AsyncTask;
import com.firebase.jobdispatcher.JobParameters;
import com.firebase.jobdispatcher.JobService;

class AsyncWork extends AsyncTask<Void, Void, Void> 

    private JobService jobService;

    private JobParameters job;

    AsyncWork(JobService jobService, JobParameters job) 
        this.jobService = jobService;
        this.job = job;
    

    @Override
    protected Void doInBackground(Void... voids) 
        // some work that needs context
        return null;
    

    @Override
    protected void onPostExecute(Void aVoid) 
        super.onPostExecute(aVoid);
        // some work that needs context
        jobService.jobFinished(job, false);
    

这会发出警告,表明 AsyncWork 类中的 jobService 属性正在泄漏上下文对象。我理解为什么如果你传递一个 Activity 或 Fragment 会出现这种情况,但这是一个 JobService,它应该存在,直到我调用 jobFinished()。我做错了什么还是可以忽略警告?

【问题讨论】:

你为什么首先使用AsyncTaskAsyncTask 背后的要点是在后台工作完成后在主应用线程上做工作。 JobService 应该没有理由想要在主应用程序线程上做任何事情。因此,请使用普通的Thread,或某种形式的Executor JobService 被用于为我们的 appwidget 下载数据和图像并更新 appwidget。我一直认为出于某种原因需要在 UiThread 上使用新的 RemoteViews 更新 AppWidget。看来我的假设是不正确的,我确实不需要 AsyncTask。我明天要试试普通的Thread。谢谢! 不,您可以从后台线程通过AppWidgetManager 更新应用小部件。您将需要一个Context,但这可以是Application 单例,以免引入任何内存泄漏。 AsyncTask 通常是“即将推出”,但它只打算在主要 UI(活动、片段)中使用。 现在一切都变得更有意义了。名称RemoteViews 的“视图”部分总是欺骗我在工作线程上准备RemoteViews 并通过UiThread 上的AppWidgetManager 更新它。我才意识到这是一个错误,因为您质疑我使用AsyncTask。我会使用你给出的建议。再次感谢! 【参考方案1】:

您不能忽略警告。因为AsyncWork 持有对Context 的引用,所以在任务完成之前无法对Context 进行GC:Context 内存泄漏。有两种解决方案:

    使用长期存在的上下文,无论如何,应用程序上下文。 将异步任务的生命周期与它所引用的上下文的生命周期联系起来:在onPause 中取消它

【讨论】:

1.我不能使用应用程序上下文,因为我需要调用 onPostExecuted() 中的 jobFinished() 函数来释放唤醒锁。 2. 据我所知,异步任务与 JobService 的生命周期相关,因为我在 JobService 的 onStartJob() 中返回 true,并在 onPostExecute() 结束时调用 jobFinished()。 JobService 中没有 onPause(),即使警告仍然存在,在 onStopJob() 中调用 cancel() 是否会 100% 防止泄漏? 在这种情况下,“泄漏”的意思是,Android 框架需要一个现在已过时的 Activity 对象使用的内存,但由于 AsyncWork 持有一个引用,所以无法获取它。我不了解您的用例的详细信息,但这是您需要防止的。您还需要注意,一旦调用了onDestroy,您作为上下文传递的Activity 将不再处于一致状态。 我注意到AsyncWork 没有返回任何内容。您是否考虑过将整个混乱放入IntentService 如果我的问题不清楚,我很抱歉。我了解Context 泄漏是如何从ActivityFragment 工作的,我的问题与从具有完全不同生命周期的JobService 调用AsyncTask 时的工作原理有关。 @CommonsWare 在 cmets 中指出,我首先不需要 AsyncTask,这才是真正的问题所在。问题现已解决,非常感谢您的帮助! 什么。我完全遵从@CommonsWare。然而,AsyncTask 正是Executor 的使用。如果您将任务放在自制的Executor 上,并引用Activity,您将遇到完全相同的问题。 ...我确实建议了IntentService【参考方案2】:

要处理泄漏,您需要使用带有 JobService 对象的 WeakReference 类

class AsyncWork extends AsyncTask<Void, Void, Void> 

  private WeakReference<JobService> jobServiceWeakReference;
  private JobParameters job;

  AsyncWork(JobService jobService, JobParameters job) 
      this.jobServiceWeakReference = new WeakReference<>(jobService);
      this.job = job;
  

  @Override
  protected Void doInBackground(Void... voids) 
      // some work that needs context
        return null;
    

  @Override
  protected void onPostExecute(Void aVoid) 
      super.onPostExecute(aVoid);
      // some work that needs context
      jobServiceWeakReference.get().jobFinished(job, false);
  

【讨论】:

嗨,不应该从工作线程调用 jobFinished 吗?

以上是关于如何防止 JobService 使用的 AsyncTask 中的上下文泄漏的主要内容,如果未能解决你的问题,请参考以下文章

如何将房间实例传递给 JobService?

当应用程序从后台删除时,如何停止 JobService Scheduled?

如何从扩展 JobService 的类中调用将视图作为参数的 MainActivity 方法?

Android学习使用JobService

Android JobService的使用及源码分析

安卓权威编程指南 挑战练习(第26章 在 Lollipop 设备上使用 JobService)