内存泄漏以优化大全,2021 非科班生的 Android 面试之路
}
}
}
}
}
mHandler 通过弱引用的方式持有 Activity,当 GC 执行垃圾回收时,遇到 Activity 就会被回收并释放所有占据的内存单元。这样就不会发生内存泄漏了。 上面的做法确实避免了 Activity 导致的内存泄漏,发送的 msg 不再已经没有持有 Activity 的引用了,但是 msg 还是有可能存在消息队列 MessageQueue 中,所有更好的是在 Activity 销毁时就将 mHandler 的回调和发送的消息给移除掉。
@Override
protected void onDestroy() {
super.onDestroy();
mHandler.removeCallbacksAndMessages(null);
}
[](
)Thread、AsyncTask
非静态内部类造成内存泄漏还有一种情况就是使用 Thread 或者 AsyncTask。 比如在 Activity 中直接 new 一个子线程 Thread:
public class ThreadActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_thread);
new Thread(new Runnable() {
@Override
public void run() {
//Something
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
或者直接新建 AsyncTask 异步任务:
public class AsyncTaskActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle s
avedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_async);
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... voids) {
//something
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
}.execute();
}
}
很多初学者都会像上面这样新建线程和异步任务,殊不知这样的写法非常不友好,这样方式新建的子线程 Thread 和 AsyncTask 都是匿名内部类对象,默认就隐式的持有外部 Activity 的引用,导致 Activity 内存泄漏。要避免内存泄漏的话还是需要像上面 Handler 一样使用静态内部类 + 弱引用的方式。
[](
)未取消注册或回调导致内存泄漏
比如我们在 Activity 中注册广播,如果在 Activity 销毁后不取消注册,那么这个广播会一直存在系统中,同上面所说的非静态内部类一样持有 Activity 引用,导致内存泄漏。因此注册广播后在 Activity 销毁后一定要取消注册。
public class BroadcastActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_broadcast);
registerReceiver(mReceiver, new IntentFilter());
}
private BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
//接收到广播需要做的逻辑
}
};
@Override
protected void onDestroy() {
super.onDestroy();
unregisterReceiver(mReceiver);
}
}
在注册观察者模式的时候,如果不及时取消也会造成内存泄漏。比如使用 Retrofit + RxJava 注册网络请求的观察者回调,同样作为匿名内部类持有外部引用,所以需要记得在不用或者销毁的时候取消注册。
[](
)Timer 和 TimerTask 导致内存泄漏
Timer 和 TimerTask 在 Android 中通常会被用来做一些计时或循环任务,比如实现无限轮播的 ViewPager:
public class TimerActivity extends AppCompatActivity {
private ViewPager mViewPager;
private PagerAdapter mAdapter;
private Timer mTimer;
private TimerTask mTimerTask;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_timer);
init();
mTimer.schedule(mTimerTask, 3000, 3000);
}
private void init() {
mViewPager = findViewById(R.id.view_pager);
mAdapter = new ViewPagerAdapter();
mViewPager.setAdapter(mAdapter);
mTimer = new Timer();
mTimerTask = new TimerTask() {
@Override
public void run() {
runOnUiThread(new Runnable() {
@Override
public void run() {
loopViewpager();
}
});
}
};
}
private void loopViewpager() {
if (mAdapter.getCount() > 0) {
int curPos = mViewPager.getCurrentItem();
curPos = (++curPos) % mAdapter.getCount();
mViewPager.setCurrentItem(curPos);
}
}
private void stopLoopViewPager() {
if (mTimer != null) {
mTimer.cancel();
mTimer.purge();
mTimer = null;
}
if (mTimerTask != null) {
mTimerTask.cancel();
mTimerTask = null;
}
}
@Override
protected void onDestroy() {
super.onDestroy();
stopLoopViewPager();
}
}
当我们 Activity 销毁时,有可能 Timer 还在继续等待执行 TimerTask,它持有 Activity 的引用不能被回收,因此当我们 Activity 销毁的时候要立即 cancel 掉 Timer 和 TimerTask,以避免发生内存泄漏。
[](
)集合中的对象未清理造成内存泄漏
这个比较好理解,如果一个对象放入到 ArrayList、HashMap 等集合中,这个集合就会持有该对象的引用,当我们不再需要这个对象时,也并没有将它从集合中移除,这样只要集合还在使用(而此对象已经无用了),这个对象就造成了内存泄漏。并且如果集合被静态引用的话,集合里面那些没有用的对象更会造成内存泄漏了,所以在使用集合时要及时将不用的对象从集合 remove,或者 clear 集合,以避免内存泄漏。
[](
)资源未关闭或释放导致内存泄漏
在使用 IO、File 流或者 Sqlite、Cursor 等资源时要及时关闭。这些资源在进行写操作时通过都使用了缓冲,如果及时不关闭,这些缓冲对象就会一直被占用而得不到释放,以至于发生内存泄漏。因此我们在不需要使用它们的时候就及时关闭,以便缓冲能及时得到释放,从而避免内存泄漏。
[](
)属性动画造成内存泄漏
动画同样是一个耗时任务,比如在 Activity 中启动了属性动画(ObjectAnimator),但是在销毁的时候没有调用 cancel 方法,虽然我们看不到动画了,但是这个动画依然会不断地播放下去,动画引用所在的控件,所在的控件引用 Activity ,这就造成 Activity 无法正常释放。因此同样要在 Activity 销毁的时候 cancel 掉属性动画,避免发生内存泄漏。
@Override
protected void onDestroy() {
super.onDestroy();
mAnimator.cancel();
}
[](
)WebView 造成内存泄漏
关于 WebView
的内存泄漏,因为 WebView
在加载网页后会长期占用内存而不能被释放,因此我们在 Activity
销毁后要调用它的 destroy()
方法来销毁它以释放内存。 另外在查阅 WebView 内存泄漏相关资料时看到这种情况: WebView
下面的 Callback
持有 Activity
引用,造成 WebView
内存无法释放,即使是调用了 WebView.destroy()
等方法都无法解决问题(Android 5.1 之后)。 最终的解决方案是:在销毁 WebView 之前需要先将 WebView 从父容器中移除,然后在销毁 WebView。 详细分析过程可以参考这篇文章:[WebView 内存泄漏解决方法](
)。
@Override
protected void onDestroy() {
super.onDestroy();
if (mWebView != null) {
((ViewGroup) mWebView.getParent()).removeView(mWebView); //先从父控件中移除 WebView.
mWebView.stopLoading();
mWebView.getSettings().setJavaScriptEnabled(false);
mWebView.clearHistory();
mWebView.removeAllViews();
mWebView.destroy();
mWebView = null;
}
}
[](
)总结
评论