Android中内存泄漏的相关因素分析(二)
Context因素
由于Context的原因,使得本来应该被释放的资源因为被持有所以无法回收,其中最典型的就是应用在单例中,我们知道单例的静态特性使得其生命周期和应用的生命周期一样长,因此不合理的使用,很容易造成内存泄漏。分析单例的特性,我们可以看到,单例的内部有公共的静态方法,用于返回该类内部创建的静态实例。所以在这里所讲的单例的实质其实就是静态变量所造成的内存泄漏。
“单例的静态特性使得其生命周期和应用的生命周期一样长”怎么理解呢?我们知道单例中的静态变量是存在于JVM的方法区内(静态域)中,JVM在回收的时候,是从GC Roots开始进行可达性分析,那么GC Roots本身是不会回收的,那么可做GC Roots的对象有:
- Java栈中的引用的对象
- 本地方法栈中JNI引用的对象
- 方法区中运行时常量池引用的对象
- 方法区中静态属性引用的对象
- 运行中的线程
- 由引导类加载器加载的对象
- GC控制的对象
泄露版
public class AppInfo {
//静态的引用
private static AppInfo instance;
private Context context;
private AppInfo(Context context){
this.context = context;
}
//这里传入的Context,所以其生命周期至关重要
public static AppInfo getInstance(Context context) {
if (instance == null) {
instance = new AppInfo(context);
}
return instance;
}
}
优化版
public class AppInfo {
private static AppInfo instance;
private Context context;
private AppInfo(Context context) {
//此处传入的是Application
this.context = context.getApplicationContext();
}
public static AppInfo getInstance(Context context) {
if (instance == null) {
instance = new AppInfo(context);
}
return instance;
}
}
我们知道JAVA的JVM的内存可分为3个区:堆(heap)、栈(stack)和方法区(method),其中方法区又叫静态区,跟堆一样是被所有的线程共享。方法区包含所有的class和static变量,方法区中包含的都是在整个程序中永远唯一的元素。显然,上述中的类被自身的静态属性所引用,符合第四条,因此单例对象不会被jvm垃圾收集
静态View因素
如果一个View初始化耗费大量资源,而且在一个Activity生命周期内保持不变,那可以把它变成static,加载到视图树上(View Hierachy),可以避免每次启动Activity都去读取并渲染View,但是静态View会持有Activity的引用,导致Activity无法被回收,像这样,当Activity被销毁时,应当释放资源。将static view置null
集合因素
程序中我们经常在集合中添加对象使用,例如ArrayList、HashMap等,这个集合会持有该对象的引用,当不使用此对象时,若是没有从集合中移除,这样只要集合还在使用,虽然该对象已经无用,这个对象就造成了内存泄漏,若是该集合是被静态引用的话,那就更严重了,所以在使用集合时要及时从集合中将不用的对象remove或者clear集合,以避免内存泄漏。同时Android API当中提供了一些优化过后的数据集合工具类,如SparseArray,SparseBooleanArray,以及LongSparseArray等,使用这些API可以让我们的程序更加高效。传统Java API中提供的HashMap工具类会相对比较低效,因为它需要为每一个键值对都提供一个对象入口,而SparseArray就避免掉了基本数据类型转换成对象数据类型的时间,在使用HashMap时,即使你只设置了一个基本数据类型的键,比如说int,但是也会按照对象的大小来分配内存,大概是32字节,而不是4字节。因此最好的办法就是像上面所说的一样,使用优化过的数据集合
public class ListDemoActivity extends AppCompatActivity {
private static ArrayList<Object> list = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test_demo);
for (int i = 0;i < 10; i++) {
Object object = new Object();
list.add(object);
//将临时的对象置null,方便GC回收
object = null;
}
}
@Override
protected void onDestroy() {
super.onDestroy();
//清空其中的对象,避免其中静态变量引用其中的对象
list.clear();
}
}
资源未关闭因素
在使用IO、File流或者Sqlite、Cursor等资源时要及时关闭。这些资源在进行读写操作时通常都使用了缓冲,如果及时不关闭,这些缓冲对象就会一直被占用而得不到释放,以致发生内存泄露。因此我们在不需要使用它们的时候就及时关闭,以便缓冲能及时得到释放,从而避免内存泄露
属性动画因素
动画同样是一个耗时任务,比如在Activity中启动了属性动画(ObjectAnimator),但是在销毁的时候,没有调用cancle方法,虽然我们看不到动画了,但是这个动画依然会不断地播放下去,动画引用所在的控件,所在的控件引用Activity,这就造成Activity无法正常释放。因此同样要在Activity销毁的时候cancel掉属性动画,避免发生内存泄漏
@Override
protected void onDestroy() {
super.onDestroy();
mAnimator.cancel();
}
WebView因素
WebView在加载网页后会长期占用内存而不能被释放,因此我们在Activity销毁后要调用它的destory()方法来销毁它以释放内存,但是Webview下面的Callback持有Activity引用,造成Webview内存无法释放,即使是调用了Webview.destory()等方法都无法解决问题(Android5.1之后),所以在销毁WebView之前需要先将WebView从父容器中移除,然后在销毁WebView
@Override
protected void onDestroy() {
super.onDestroy();
// 先从父控件中移除WebView
mWebViewContainer.removeView(mWebView);
mWebView.stopLoading();
mWebView.getSettings().setJavaScriptEnabled(false);
mWebView.clearHistory();
mWebView.removeAllViews();
mWebView.destroy();
}
监听器因素
通过Context.getSystemService(int name)可以获取系统服务。这些服务工作在各自的进程中,帮助应用处理后台任务,处理硬件交互。如果需要使用这些服务,可以注册监听器,这会导致服务持有了Context的引用,如果在Activity销毁的时候没有注销这些监听器,会导致内存泄漏,所以在退出的时候一定要注销,其中包括自己手动add的Listener,要在合适的时候及时remove这个Listener
https://www.jianshu.com/p/ab4a7e353076
https://www.jianshu.com/p/003913cc2444
上一篇: PS如何制作漂亮自然的天空
下一篇: 04 | 内存泄露了,该怎么定位和处理?