内存泄漏和内存优化

  1. 内存泄漏产生的原因
    Android系统会为应用运行分配内存,当分配的内存不足时就会触发GC,GC采用的垃圾标记算法是根搜索算法。当代码中没有用的对象到GC Roots是可达的(也就是对象被引用),那么就会产生内存泄漏。内存泄漏一般是开发人员编码造成的泄漏、第三方框架造成的和Android系统造成的,后两种情况是不可控的,所以我们需要做的就是减少编码造成的内存泄漏。
  2. 内存泄漏的场景
  • 静态变量导致的内存泄漏
    静态变量sContext引用了Activity,导致Activity无法正常销毁。静态变量sView内部也持有当前Activity,Activity也无法释放。这就是静态变量导致的内存泄漏,这种一般是很明显发现的。
package tool.com.angeliur.tooldemo;

import android.content.Context;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;

public class MainActivity extends AppCompatActivity {

    private static Context sContext;
    private static View sView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        sContext = this;
        sView = new View(this);
    }
}
  • 单例模式导致的内存泄漏
    单例模式的SingleManager 可以接受外部的注册并将外部的监听器存储起来。MainActivity实现OnDataArrivedListener接口并向SingleManager注册监听。由于缺少解注册的操作,MainActivity的对象被SingleManager所持有。单例模式的特点是生命周期和Application保持一致,因此Activity无法及时释放导致内存泄漏。
public class SingleManager {

    private List mOnDataArrivedListeners = new ArrayList();

    public SingleManager() {

    }

    public static SingleManager getInstance(){
        return SingletonHolder.INSTANCE;
    }

    private static class SingletonHolder{
        public static final SingleManager INSTANCE = new SingleManager();
    }

    public synchronized void registerListener(OnDataArrivedListener listener){
        if (!mOnDataArrivedListeners.contains(listener)){
            mOnDataArrivedListeners.add(listener);
        }
    }

    public synchronized void unregisterListener(OnDataArrivedListener listener){
        mOnDataArrivedListeners.remove(listener);
    }

    public interface OnDataArrivedListener{
        public void onDataArrived(Object data);
    }
}
public class MainActivity extends AppCompatActivity implements SingleManager.OnDataArrivedListener {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        SingleManager.getInstance().registerListener(this);
    }


    @Override
    public void onDataArrived(Object data) {

    }
}
  • 属性动画导致的内存泄漏
    属性动画中可以设置动画无限循环,如果在Activity中播放无限动画并且没有在onDestory中停止动画,动画就会一直播放下去,尽管已经无法在界面上看到动画效果,但是Activity的View会被动画持有,而View又持有Activity,导致Activity无法释放。所以需要在onDestory()中调用animator.cancle()来停止动画避免内存泄漏。
public class MainActivity extends AppCompatActivity{

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button button = findViewById(R.id.button);
        ObjectAnimator animator = ObjectAnimator.ofFloat(button, "rotation", 0, 360);
        animator.setDuration(1000);
        animator.setRepeatCount(ValueAnimator.INFINITE);
        animator.start();
    }

}
  • 非静态内部类的静态实例
    非静态内部类会持有外部类实例的引用,如果非静态内部类的实例是静态的,就会间接地长期维持着外部类的引用,导致外部类无法被回收。点击Button时,创建非静态内部类InnerClass的静态实例,该实例的生命周期和应用程序一样长,并且一直持有MainActivity的引用,导致MainActivity无法回收
public class MainActivity extends AppCompatActivity{

    private static Object innerClass;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button button = findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                createInnerClass();
                finish();
            }
        });
    }

    void createInnerClass() {
        class InnerClass{

        }

        innerClass = new InnerClass();
    }

}
  • 多线程相关的匿名内部类/非静态内部类
    和非静态内部类一样,匿名内部类也会持有外部类实例的引用。多线程相关的类AsyncTask类、Thread类和实现Runnable接口的类,他们的匿名内部类/非静态内部类如果做耗时操作就可能内存泄漏。
    通过startAsyncTask()实例化一个AsyncTask,AsyncTask的异步任务在后台执行耗时任务期间,MainActivity被销毁了,被AsyncTask持有的MainActivity实例不会被垃圾回收期回收,直到异步任务结束。
public class MainActivity extends AppCompatActivity{

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button button = findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                startAsyncTask();
                finish();
            }
        });
    }

    void startAsyncTask() {
        new AsyncTask(){

            @Override
            protected Void doInBackground(Void... voids) {
                while (true);
            }
        }.execute();
    }
}

自定义的AsyncTask如果是非静态内部类也会发生内存泄漏,解决办法就是自定义一个静态的AsyncTask.

public class MainActivity extends AppCompatActivity{

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button button = findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                startAsyncTask();
                finish();
            }
        });
    }

    void startAsyncTask() {
        new MyAsyncTask().execute();
    }

    private static class MyAsyncTask extends AsyncTask{
        @Override
        protected Void doInBackground(Void... voids) {
            while (true);
        }
    }
}
  • Handler内存泄漏
    Handler的Message被存储在MessageQueue中,有些Message并不能马上被处理,它们在MessageQueue中存在的时间会很长,导致Handler无法被回收。如果Handler是非静态的,也会导致引用它的Activity或Service不能被回收。
    如下,Handler是非静态的匿名内部类的实例,它会隐形引用外部类MainActivity,当我们点击button时,MainActivity会结束,但是Handler中的消息还没有被处理,因此MainActivity无法被回收。
public class MainActivity extends AppCompatActivity{

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button button = findViewById(R.id.button);
        
        final Handler handler = new Handler(){
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
            }
        };
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                handler.sendEmptyMessageDelayed(Message.obtain(),60000);
                finish();
            }
        });
    }
}

解决方案有两种:一种是使用一个静态的Handler,Handler持有的对象要使用弱引用。如下,MyHandler是一个静态内部类,它持有MainActivity对象使用了弱引用,避免了内存泄漏。第二种是在Activity的onDestory()方法中移除MessageQueue中的消息,在onDestory方法中将Callbacks和Messages全部清除掉。采用这种方案,Handler中的消息可能无法全部处理完,所以建议使用第一种方案。

public class MainActivity extends AppCompatActivity{

    private MyHandler myHandler = new MyHandler(this, activity1);
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button button = findViewById(R.id.button);

        
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                myHandler.sendMessageDelayed(Message.obtain(),60000);
                finish();
            }
        });
    }
    
    private static class MyHandler extends Handler{
        private final WeakReference mActivity;
        
        public MyHandler(MainActivity activity) {
            mActivity = new WeakReference(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
        }
    }

    @Override
    protected void onDestroy() {
        if (myHandler != null){
            myHandler.removeCallbacksAndMessages(null);
        }       
        super.onDestroy();
    }
}
  • 未正确使用Context
    对于不是必须使用Activity的Context的情况,可以考虑使用Application Context来代替,Dialog的Context必须使用Activity的Context。
    在单例模式中,AppSetting作为静态对象,生命周期会长于Activity。屏幕旋转时默认情况下系统会销毁Activity。因为当前Activity调用了setup方法,并传入了Activity Context,使得Activity被单例持有,导致垃圾回收期无法回收,产生内存泄漏。可以通过使用Application Context来解决。
public class AppSetting{
        private Context mAppContext;
        private static AppSetting mAppSetting = new AppSetting();
        private static AppSetting getInstance(){
            return mAppSetting;
        }
        
        public final void setup(Context context){
            mAppContext = context.getApplicationContext();
        }
    }
  • 静态View
    使用静态View可以避免每次启动Activity都去读取并渲染View,但是静态View会持有Activity的引用,导致Activity无法被回收。解决办法就是在onDestory中将静态View置为null
public class MainActivity extends AppCompatActivity{

    private static Button button;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        button = findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                finish();
            }
        });
    }

    @Override
    protected void onDestroy() {
        button = null;
        super.onDestroy();
    }
}
  • WebView
    WebView都会存在内存泄漏的问题,在应用中只要使用一次WebView,内存就不会被释放掉。解决办法是为WebView单开一个线程,使用AIDL与应用程序的主进程进行通信。WebView进程可以根据业务需求,在合适的时机进行销毁。
  • 资源对象未关闭
    Cursor、File等,往往都使用了缓冲,会造成内存泄漏。在资源对象不使用时,一定要确保他们已经关闭并将他们的引用置为null,通常在finally语句中进行关闭,防止出现异常时,资源未被释放的问题。
  • Bitmap对象
    临时创建的某个相对比较大的Bitmap对象,经过变换得到新的Bitmap对象后,尽快回收原始的Bitmap,这样能更快释放原始bitmap占用的空间。避免静态变量持有比较大的Bitmap对象或者其他大的数据对象,如果已经持有,要尽快置空该静态变量。
  • 监听器未关闭
    很多系统服务(比如TelephonyManager、SensorManager)需要register和unregister监听器,确保在合适的时候及时unregister那些监听器。自己手动添加的listener,记得在合适的时候及时移除这个listener。
  • 集合中对象没清理
    通常把一些对象的引用加入到集合中,当不需要该对象时,如果没有把该对象的引用从集合中清理掉,集合就会越来越大。如果这个集合是static的话,情况会更严重。
  1. 内存优化的方法
    要做好内存优化,首先要理解内存优化相关的原理,然后就是善于运用内存分析的工具。
  • Memory Monitor
    AS中的Memory Monitor包含了Logcat、Memory Monitor、CPU Monitor、GPU Monitor、Network Monitor。Memory Monitor可以监视应用程序的性能和内存使用情况,便于找到被分配的对象,定位内存泄漏,并跟踪连接设备中正在使用的内存数量。Memory Monitor的作用有:实时显示可用的和分配的Java内存图表、实时显示垃圾收集事件、启动垃圾收集事件、快速测试应用程序缓慢是否与过度垃圾收集有关、快速测试应用程序崩溃是否和内存耗尽有关。
    应用程序中出现大内存分配时,内存图中分配的内存会急剧上升。我们要判断这些是否是合理分配的内存,是bitmap还是其他大数据,并对这种数据优化,减少内存开销。
    内存抖动一般指在很短的时间内发生了多次内存分配和释放,严重的内存抖动会导致应用卡顿。内存抖动的原因主要是短时间内频繁创建对象(可能在循环中创建对象),内存为了应对这种情况,会频繁进行GC。非并行GC进行时,其他线程都会被挂起,等待GC操作完成后恢复工作。如果是频繁的GC会产生大量的暂停时间,导致界面绘制时间减少,使得多次绘制一帧的时间超过16ms,产生的现象就是界面卡顿。内存抖动在内存图中就会产生锯齿状的抖动图示。
  • Allocation Tracker
    Allocation Tracker用来跟踪内存分配,允许你在执行某些操作的同时监视在何处分配对象,了解这些分配使你能够调整与这些操作相关的方法调用,优化应用程序性能和内存使用。Allocation Tracker的作用:显示代码分配对象类型、大小、分配线程、堆栈跟踪的时间和位置。通过重复的分配/释放模式帮助识别内存变化。当与HPROF Viewer结合使用时,可以帮助你跟踪内存泄漏。如果在堆上看到一个bitmap对象,可以使用Allocation Tracker来找到其分配的位置。
  • Heap Dump
    Heap Dump的主要功能就是产看不同的数据类型在内存中的使用情况,可以帮助你找到大对象,也可以通过数据的变化发现内存泄漏。
  • 内存分析工具MAT
    如果想要深入分析并确定内存泄漏就需要分析疑似内存泄漏时所生成的堆存储文件。堆存储文件可以使用DDMS或者Memory Monitor来生成,输出的文件格式为hprof,而MAT就是分析堆存储文件的。MAT是eclipse的插件,使用AS开发需要单独下载。
  • LeakCanary
    使用MAT来分析内存问题,会有一些难度,并且效率也不是很高,对于一个内存泄漏问题可能要进行多次排查和对比。为了能迅速发现内存泄漏,Square公司基于MAT开源了LeakCanary。
    LeakCanary的使用:
dependencies {
    debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.5.2'
    releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.2'
}

public class LeakApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        if (LeakCanary.isInAnalyzerProcess(this)){
            return;
        }
        LeakCanary.install(this);
    }
}

上述配置只能检测Activity的内存泄漏,如果还需要检测其他类的内存泄漏,需要使用RefWatcher来进行监控,改写LeakApplication。
非静态内部类LeakThread持有外部类MainActivity的引用,在LeakThread中做耗时操作,导致MainActivity无法被释放,所以存在内存泄漏。运行程序后,桌面会生成一个Leaks的应用图标。不断横竖屏,会闪出一个提示框" Dumping memory app will freeze",然后内存泄漏信息就会通过Notification展示出来。根据提示来对自己的代码进行分析。

public class LeakApplication extends Application {
    private RefWatcher refWatcher;
    @Override
    public void onCreate() {
        super.onCreate();
        refWatcher = setupRefWatcher();
        
    }

    private RefWatcher setupRefWatcher() {
        if (LeakCanary.isInAnalyzerProcess(this)){
            return RefWatcher.DISABLED;
        }
        return LeakCanary.install(this);
    }

    public static RefWatcher getRefWatcher(Context context) {
        LeakApplication application = (LeakApplication) context.getApplicationContext();
        return application.refWatcher;
    }
}
public class MainActivity extends AppCompatActivity{

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        LeakThread leakThread = new LeakThread();
        leakThread.start();
    }

    class LeakThread extends Thread{
        @Override
        public void run() {
            try {
                Thread.sleep(6 * 60 * 1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        RefWatcher refWatcher = LeakApplication.getRefWatcher(this);
        refWatcher.watch(this);
    }
}

参考:《Android进阶解密》

你可能感兴趣的:(内存泄漏和内存优化)