您的位置:首页 > 其它

如何使用AsyncTask防止内存泄漏(Handler同理)

2017-10-25 19:33 295 查看

Andorid AsyncTask防止内存泄漏

问题根源—-内部类

内部类在Java中是一个很常见的数据结构。它们很受欢迎,因为它们可以以这样的方式来定义:即只有外部类可以实例化它们。很多人可能没有意识到的是这样的类会持有外部类的隐式引用。隐式引用很容易出错,尤其是当两个类具有不同的生命周期。以下是常见的Android Activity写法。

public class AsyncActivity extends Activity {

TextView textView;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_async);
textView = (TextView) findViewById(R.id.textView);

new BackgroundTask().execute();
}

private class BackgroundTask extends AsyncTask<Void, Void, String>{

@Override
protected String doInBackground(Void... params) {
// Do background work. Code omitted.
return "some string";
}

@Override
protected void onPostExecute(String result) {
textView.setText(result);
}
}
}


这种特殊的实现在执行上没有问题。问题是,它保留内存的时间肯定会超过必要的时间。由于BackgroundTask持有一个AsyncActivity隐式引用并运行在另一个没有取消策略的线程上,它将保留AsyncActivity在内存中的所有资源连接,直到后台线程终止运行。在HTTP请求的情况下,这可能需要很长的时间,尤其是在速度较慢的连接。

解决办法

我们第一要务是使用静态类的实现方式来消除指向Activity的引用,但这样我们也不能直接访问 textView了。因此我们还需要添加一个构造函数,把textView作为参数传递进来。最后,我们需要引入AsyncTask文档中所述的取消策略。考虑到所有这一切,让我们看看我们的代码最终呈现。

public class AsyncActivity extends Activity {

TextView textView;
AsyncTask task;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_async);
textView = (TextView) findViewById(R.id.textView);

task = new BackgroundTask(textView).execute();
}

@Override
protected void onDestroy() {
task.cancel(true);
super.onDestroy();
}

private static class BackgroundTask extends AsyncTask<Void, Void, String> {

private final TextView resultTextView;

public BackgroundTask(TextView resultTextView) {
this.resultTextView = resultTextView;
}

@Override
protected void onCancelled() {
// Cancel task. Code omitted.
}

@Override
protected String doInBackground(Void... params) {
// Do background work. Code omitted.
return "some string";
}

@Override
protected void onPostExecute(String result) {
resultTextView.setText(result);
}
}
}


但是。。。。。以上这种方法还存在问题。。。

resultTextView持有一个mContext引用,毫无疑问,它就是泄露的Activity的引用。那么如何解决这个问题?我们无法消除resultTextView绑定的context引用,因为我们需要在BackgroundTask中使用resultTextView的引用,以便更新用户界面。为了解决这个问题,一种简单的方法是使用WeakReference。我们持有的resultTextView引用是强引用,具有防止GC回收的能力。相反,WeakReference不保证其引用的实例存活。当一个实例最后一个强引用被删除,GC会把其资源回收,而不管这个实例是否有弱引用。下面是使用WeakReference的最终版本:

public class AsyncActivity extends Activity {

TextView textView;
AsyncTask task;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_async);
textView = (TextView) findViewById(R.id.textView);

task = new BackgroundTask(textView).execute();
}

@Override
protected void onDestroy() {
task.cancel(true);
super.onDestroy();
}

private static class BackgroundTask extends AsyncTask<Void, Void, String> {

private final WeakReference<TextView> textViewReference;

public BackgroundTask(TextView resultTextView) {
this.textViewReference = new WeakReference<>(resultTextView);
}

@Override
protected void onCancelled() {
// Cancel task. Code omitted.
}

@Override
protected String doInBackground(Void... params) {
// Do background work. Code omitted.
return "some string";
}

@Override
protected void onPostExecute(String result) {
TextView view = textViewReference.get();
if (view != null) {
view.setText(result);
}
}
}
}


请注意,在onPostExecute我们要检查空值,判断实例是否被回收。

以上才算正在实现内存不出现泄漏。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: