Android - 传递按钮实例时避免 AsyncTask 中的内存泄漏
Posted
技术标签:
【中文标题】Android - 传递按钮实例时避免 AsyncTask 中的内存泄漏【英文标题】:Android - avoiding memory leak in AsyncTask when passing a button instance 【发布时间】:2021-08-18 02:45:59 【问题描述】:我有一个扩展 AsyncTask 的类。调用时,此任务会将视频下载到内部存储,并依次更新进度指示器。任务完成后,它会将下载按钮更改为已下载按钮(我使用的是abdularis androidButtonProgress)。
该过程运行良好,但是我有一个下载按钮字段,它被突出显示为内存泄漏:
public class DownloadHandler extends AsyncTask<Object, Integer, String>
private DownloadButtonProgress downloadButton; // This field leaks a context object
private WeakReference<Context> context;
Episode episode;
int totalSize;
public DownloadHandler(Context context)
this.context = new WeakReference<> (context);
@Override
protected String doInBackground(Object... params)
episode = (Episode) params[0];
Context context = (Context) params[1];
downloadButton = (DownloadButtonProgress) params[2];
String urlString = "https://path.to.video.mp4";
try
URL url = new URL(urlString);
URLConnection ucon = url.openConnection();
ucon.setReadTimeout(5000);
ucon.setConnectTimeout(10000);
totalSize = ucon.getContentLength();
InputStream is = ucon.getInputStream();
BufferedInputStream inStream = new BufferedInputStream(is, 1024 * 5);
String fileName = episode.getFilename() + ".mp4";
File file = new File(String.valueOf(context.getFilesDir()) + fileName);
if (file.exists())
file.delete();
file.createNewFile();
FileOutputStream outStream = new FileOutputStream(file);
byte[] buff = new byte[5 * 1024];
int len;
long total = 0;
while ((len = inStream.read(buff)) != -1)
total += len;
if (totalSize > 0)
publishProgress((int) (total * 100 / totalSize));
outStream.write(buff, 0, len);
outStream.flush();
outStream.close();
inStream.close();
return "Downloaded";
catch (Exception e)
e.printStackTrace();
return "Not downloaded";
@Override
protected void onProgressUpdate(Integer... progress)
int downloadedPercentage = progress[0];
downloadButton.setCurrentProgress(downloadedPercentage);
@Override
protected void onPostExecute(String result)
if (!result.equals("Downloaded"))
Log.d(TAG, "onPostExecute: ERROR");
else
downloadButton.setFinish();
// Save to Room (this is why I pass context as a weak reference)
AppDatabase db = AppDatabase.getDbInstance(context.get().getApplicationContext());
// ....
当我从片段中调用 DownloadHandler 时,我会这样做:
DownloadHandler downloadTask = new DownloadHandler(getActivity());
downloadTask.execute(episode, getActivity(), downloadButton);
我在执行方法中传递了下载按钮,但我需要它可用于 DownloadHandler 类中的其他方法(onProgressUpdate()、onPostExecute()),所以我将它设为一个字段。
我尝试在构造函数中将它作为弱引用传递给上下文,但我收到一个错误,提示我无法将 downloadButton 强制转换为 WeakReference。
我怎样才能使下载处理程序中的所有方法都可以使用下载按钮,但避免内存泄漏?
【问题讨论】:
【参考方案1】:您应该将下载按钮作为构造函数依赖项传递,并像使用上下文一样将其包装在弱引用中。
我认为它可能抛出了 ClassCastException
,因为您试图从 doInBackground()
强制投射它,而来自 AsyncTask
主机的下载按钮是视图的弱引用。
对现有代码的小修改应该可以正常工作:
public class DownloadHandler extends AsyncTask<Object, Integer, String>
private WeakReference<DownloadButtonProgress> downloadButton;
private WeakReference<Context> context;
Episode episode;
int totalSize;
public DownloadHandler(Context context, DownloadButtonProgress button)
this.context = new WeakReference<> (context);
this.downloadButton = new WeakReference<>(button)
@Override
protected String doInBackground(Object... params)
episode = (Episode) params[0];
String urlString = "https://path.to.video.mp4";
try
URL url = new URL(urlString);
URLConnection ucon = url.openConnection();
ucon.setReadTimeout(5000);
ucon.setConnectTimeout(10000);
totalSize = ucon.getContentLength();
InputStream is = ucon.getInputStream();
BufferedInputStream inStream = new BufferedInputStream(is, 1024 * 5);
String fileName = episode.getFilename() + ".mp4";
File file = new File(String.valueOf(context.get().getFilesDir()) + fileName);
if (file.exists())
file.delete();
file.createNewFile();
FileOutputStream outStream = new FileOutputStream(file);
byte[] buff = new byte[5 * 1024];
int len;
long total = 0;
while ((len = inStream.read(buff)) != -1)
total += len;
if (totalSize > 0)
publishProgress((int) (total * 100 / totalSize));
outStream.write(buff, 0, len);
outStream.flush();
outStream.close();
inStream.close();
return "Downloaded";
catch (Exception e)
e.printStackTrace();
return "Not downloaded";
@Override
protected void onProgressUpdate(Integer... progress)
int downloadedPercentage = progress[0];
if (downloadButton.get() != null)
downloadButton.get().setCurrentProgress(downloadedPercentage);
@Override
protected void onPostExecute(String result)
if (!result.equals("Downloaded"))
Log.d(TAG, "onPostExecute: ERROR");
else
if (downloadButton.get() != null)
downloadButton.get().setFinish();
// Save to Room (this is why I pass context as a weak reference)
AppDatabase db = AppDatabase.getDbInstance(context.get().getApplicationContext());
// ....
现在您可以像这样调用它(注意在doInBackground
中使用上下文的弱引用):
DownloadHandler downloadTask = new DownloadHandler(getActivity(), downloadButton);
downloadTask.execute(episode);
话虽如此,它仍然不整洁,因为您需要进行所有空值检查,这会造成很多可读性差的问题,因此为避免泄漏,请确保您使用 AsyncTask#cancel()
API 取消任何正在进行的任务活动被销毁,然后你可以从你的实现中删除所有弱引用(假设活动重新创建再次处理状态)
此外,从长远来看,您可能想查看更好的异步 API,例如协同例程或 RxJava,因为 AsyncTask
已被弃用。
【讨论】:
感谢您的成功。肯定会考虑其他选项,但目前已解决。以上是关于Android - 传递按钮实例时避免 AsyncTask 中的内存泄漏的主要内容,如果未能解决你的问题,请参考以下文章
在 Android 移动网络应用程序中嵌入 pdf 时如何避免打开按钮?