Android-在传递按钮实例时避免AsyncTask中的内存泄漏



我有一个扩展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);

我在execute方法中传递下载按钮,但我需要它对DownloadHandler类中的其他方法(onProgressUpdate((,onPostExecute(((可用,所以我将其设为字段。

我在上下文中尝试将它作为弱引用传递到构造函数中,但我遇到了一个错误,说我不能将downloadButton强制转换为WeakReference。

如何使下载按钮可用于下载处理程序中的所有方法,同时避免内存泄漏?

您应该将下载按钮作为构造函数依赖项传递,并像处理上下文一样将其封装在弱引用中。

我认为它可能抛出了一个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中使用上下文的弱ref(:

DownloadHandler downloadTask = new DownloadHandler(getActivity(), downloadButton);
downloadTask.execute(episode);

话虽如此,它仍然不整洁,因为你需要所有的空检查,这会导致很多可读性差的问题,因此为了避免泄露,请确保在活动被破坏时使用AsyncTask#cancel()API取消任何正在进行的任务,然后你可以从实现中删除所有弱引用(假设活动重新创建会再次处理状态(

此外,从长远来看,您可能希望查看更好的异步API,如协同例程或RxJava,因为AsyncTask已被弃用。

相关内容

  • 没有找到相关文章

最新更新