最近公司要求优化项目,于是就检查了自己负责的代码,然后总结了一些内存泄漏的几种情况。
java是垃圾回收语言的一种,优点是开发者无需关心管理内存,缺点就是容易内存泄漏,容易浪费掉未释放的内存。
造成内存泄漏的原因:
android内存泄漏的几种情况:
1.使用static
一般使用static,如果不注意那就离内存泄漏不远了。
static Activity
static View
如果一个View初始化过于频繁,也许有必要在内存中保存一个View,而且在一个Activity生命周期内保持不变,那可以把它变成static,加载到视图树上(View Hierachy),像这样,当Activity被销毁时,应当释放资源。释放资源时,把这个static view置null即可。
2.注意context
在Activity中上下文对象有时候被一个耗时操作或者长时间不能释放的对象依赖时,需要使用Application的上下文对象。如请求加载网络时,传入的最好是工程里面封装好的Application的上下文对象,不要使用Activity里面的,否则可能会发生内存泄漏。
3.内部类
内部类的优势之一就是可以访问外部类,不幸的是,导致内存泄漏的原因,就是内部类持有外部类实例的强引用。匿名内部类也是如此。下面就是一个内部类被标记为静态内部类造成的内存泄漏。
private static Object inner;
void createInnerClass() {
class InnerClass { }
inner = new InnerClass();
}
View icButton = findViewById(R.id.ic_button);
icButton.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View v) {
createInnerClass();
nextActivity(); }});
4.Thread
当Activity被销毁了,线程里面的任务还未完成,就造成了内存泄漏,这时有两种方法类处理:一种是能不能在Activity结束是也结束thread里面的任务。另一种是重写Thread进行相应的处理,与Activity进行分离。
解决例子:
//防止线程内存泄漏 重新封装Thread
private static class MyThread extends Thread {
private WeakReference<Activity> mThreadWR;
private Activity activity;
public MyThread(Activity activity) {
mThreadWR = new WeakReference<Activity>(activity);
this.activity = activity;
}
@Override
public void run() {
super.run();
if (mThreadWR == null)
return;
if (mThreadWR.get() != null){
mThreadWR.get().startDo();
}
}
}
Thread、AsyncTask、Handler都可以使用这种方式解决内存泄漏问题
5.AsyncTask
匿名类也维护了外部类的引用。所以内存泄漏很容易发生,当你在Activity中定义了匿名的AsyncTsk。当异步任务在后台执行耗时任务期间,Activity不幸被销毁了,这个被AsyncTask持有的Activity实例就不会被垃圾回收器回收,直到异步任务结束。
6.Handler
跟AsyncTsk原理一样,定义匿名的Runnable,用匿名类Handler执行。Runnable内部类会持有外部类的隐式引用,被传递到Handler的消息队列MessageQueue中,在Message消息没有被处理之前,Activity实例不会被销毁了,于是导致内存泄漏。解决方法:可已在Activity结束之前,清空Handler里面的内容。解决方法:一种是重新封装Handler,处理Handler。另一种是清空Handler消息队列里面的内容,使用方法handler.removeCallbacksAndMessages()
7.TimerTask
跟Thread类似,一定要在Activity结束前停止TimerTask里面的任务,并释放这个对象,否则会引发内存泄漏。
8.Sensor Manager
void registerListener() { SensorManager sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ALL);
sensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_FASTEST);
}
View smButton = findViewById(R.id.sm_button);
smButton.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View v) {
registerListener();
nextActivity();
}});
9.
集合中的对象未清理造成内存泄露
如果一个对象放入到ArrayList
、HashMap
等集合中,这个集合就会持有该对象的引用。当我们不再需要这个对象时,也并没有将它从集合中移除,这样只要集合还在使用(而此对象已经无用了),这个对象就造成了内存泄露。并且如果集合被静态引用的话,集合里面那些没有用的对象更会造成内存泄露了。所以在使用集合时要及时将不用的对象从集合remove
,或者clear
集合,以避免内存泄漏。
10.资源未关闭或释放导致内存泄露
在使用IO
、File
流或者Sqlite
、Cursor
等资源时要及时关闭。这些资源在进行读写操作时通常都使用了缓冲,如果及时不关闭,这些缓冲对象就会一直被占用而得不到释放,以致发生内存泄露。因此我们在不需要使用它们的时候就及时关闭,以便缓冲能及时得到释放,从而避免内存泄露。
11.属性动画造成内存泄露
动画同样是一个耗时任务,比如在Activity
中启动了属性动画(ObjectAnimator
),但是在销毁的时候,没有调用cancle
方法,虽然我们看不到动画了,但是这个动画依然会不断地播放下去,动画引用所在的控件,所在的控件引用Activity
,这就造成Activity
无法正常释放。因此同样要在Activity
销毁的时候cancel
掉属性动画,避免发生内存泄漏。
12.webView造成的内存泄漏
webView加载网页后会长期占用内存,所以需要在Activity销毁之前来释放内存。
webView的CallBack持有Activity对象造成无法释放内存。
在网上找到了一种解决办法,在Activity结束前调用即可:
public void destroy() {
if (mWebView != null) {
// 如果先调用destroy()方法,则会命中if (isDestroyed()) return;这一行代码,需要先onDetachedFromWindow(),再
// destory()
ViewParent parent = mWebView.getParent();
if (parent != null) {
((ViewGroup) parent).removeView(mWebView);
}
mWebView.stopLoading();
// 退出时调用此方法,移除绑定的服务,否则某些特定系统会报错
mWebView.getSettings().setJavaScriptEnabled(false);
mWebView.clearHistory();
mWebView.clearView();
mWebView.removeAllViews();
try {
mWebView.destroy();
} catch (Throwable ex) {
}
}
}
13.软键盘引起的内存泄漏
软键盘源码里面很复杂,看了一圈源码发现软件盘内部持有Activity的引用,导致内存泄漏。没有很直接的解决方法,通过源码知道了可以从内部入手,封装工具类,将软键盘与Activity进行分离。 去获取InputMethodManager的关联View,通过View.getContext()与Activity A进行对比,如果发现两者相同,就表示需要回收;如果两者不一样,则表示有新的界面已经在使用InputMethodManager了,直接不处理就可以了。
总结:一般常发生的内存泄漏大致就是这些,注意以上这些点应该能减少一些内存泄漏的问题。
当然内存泄漏不易被发现,可以使用LeakCanary这个工具进行检查内存泄漏。这个工具的使用就不在介绍了,在网上一找一大把。