欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android中内存泄漏的相关因素分析(二)

程序员文章站 2022-04-19 15:40:49
...

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