Android 性能优化

(二)Android 性能优化 Memory Profiler

2020-06-29  本文已影响0人  科技猿人

小酌鸡汤

学贵有疑,小疑则小进,大疑则大进。

本文来源《Android 性能优化 全家桶》

什么是内存泄漏?

内存泄露:无用对象持续占有内存或无用对象的内存得不到及时的释放(程序申请分配内存空间后,使用完毕后未释放。结果会导致一直占据该内存单元,也无法再使用该内存单元,直到程序结束)
 内存泄漏的影响:容易使得应用程序发生内存溢出,即 OOM(Out of Memory) 。
内存溢出:OOM(这个就很严重了),程序向系统申请的内存空间超出了系统能给的最大内存单元。
 内存溢出的影响:导致ANR,甚至应用Crash。
内存抖动:内存抖动是指在短时间内有大量的对象被创建或者被回收的现象,主要是循环中大量创建、回收对象,从而使UI线程被频繁阻塞,导致画面卡顿。
 它们三者的重要等级分别:内存溢出 > 内存泄露 > 内存抖动。

内存泄漏的根本原因

 本该回收的对象,因为某些原因(对象的强引用被另外一个正在使用的对象所持有,且没有及时释放),进而造成内存单元一直被占用,造成内存泄漏,甚至可能造成内存溢出!
 简言之:持有引用者的生命周期 > 被引用者的生命周期。
 原理基础知识:关于JVM的内存分区和GC机制(先请大家自行百度,后续我会为此单开一篇)

Android中内存泄漏分类

内存泄漏分类
(一)线程未结束(举栗子:Handler)
(二)非静态内部类
(三)单例模式
(四)static变量引用
(五)集合中对象没清理造成的内存泄漏
(六)BitMap占用过多内存
(七)属性动画
(八)资源未及时关闭
(九)订阅/取消订阅不匹配(比如Service业务监听/广播反注册)
(十)任务不及时取消(Service/WebView/Retrofit/Rxjava等)
(十一)执行频率高的地方或循环中创建对象(onMeasure/onDraw)
(十二)Toast显示

小憩一下,正式开始

为什么要用 memory profiler?

 Memory Profiler 是 Android Profiler中的一个组件,可帮助您识别可能会导致应用卡顿、冻结甚至崩溃的内存泄露和内存抖动。它显示一个应用内存使用量的实时图表,让您可以捕获堆转储、强制执行垃圾回收以及跟踪内存分配。
 Memory Profiler 加入了自动检查 Activity 和 Fragment 中的内存泄漏的功能。使用这一功能非常的简单。

现在,就一起实操体验profiler吧!

(1)profiler实操环境(可选项,用自己的环境和代码也一样)
(2)打开profiler
(3)来吧,一起预览一下吧:
profiler概览
(4)点击MEMORY分类栏,就可以进入到memory profiler详情页:
memory-profiler详情页
窗口详细说明:
(5)随时查看内存分配(在时间轴上拖动以选择要查看哪个区域的分配):
memory-profiler查看内存分配
对各个窗口进行说明:
从上图可以查看对象分配的以下信息:
(6)捕获堆转储:
memory-profiler捕获堆转储
窗口说明:
捕获堆转储后,您可以查看以下信息:
(7)SamplePop示例代码:
(一)Handler内存泄漏演练,先看下代码:
@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d(TAG, "onCreate: ");
        setContentView(R.layout.activity_memory_profiler);
        mResultTv = findViewById(R.id.memory_type_result);
        ((RadioGroup) findViewById(R.id.memory_handler_type)).
                setOnCheckedChangeListener(mOnCheckedChangeListener);
    }

    private RadioGroup.OnCheckedChangeListener mOnCheckedChangeListener =
            new RadioGroup.OnCheckedChangeListener() {
        @Override
        public void onCheckedChanged(RadioGroup radioGroup, int i) {
            switch (i) {
                case R.id.handler_no_static:
                    memoryTestType = MEMORY_ERROR_NO_STATIC;
                    break;
                case R.id.handler_inner_class:
                    memoryTestType = MEMORY_ERROR_INNER_CLASS;
                    break;
                case R.id.handler_static_weak:
                    memoryTestType = MEMORY_NORMAL_STATIC_WEAK;
                    break;
                case R.id.handler_no_static_clear_message:
                    memoryTestType = MEMORY_NORMAL_MESSAGE_CLEAR;
                    break;
                default:
                    break;
            }
        }
    };

    public void onMemoryHandlerMonitor(View view) {
        Log.d(TAG, "onMemoryHandlerMonitor: ");
        monitorHandler();
    }

    //模拟Handler泄漏(memory profiler以这个来演示实操)
    private static final int MESSAGE_DELAY = 5 * 1000;

    private static final int MEMORY_ERROR_NO_STATIC = 1;
    private static final int MEMORY_ERROR_INNER_CLASS = 2;
    private static final int MEMORY_NORMAL_STATIC_WEAK = 3;
    private static final int MEMORY_NORMAL_MESSAGE_CLEAR = 4;

    //只要动态修改此参数就行
    private int memoryTestType = MEMORY_ERROR_NO_STATIC;
    TextView mResultTv;
    private Handler mHandler;

    @Override
    protected void onDestroy() {
        Log.d(TAG, "onDestroy: ");
        if (MEMORY_NORMAL_MESSAGE_CLEAR == memoryTestType) {
            mHandler.removeCallbacksAndMessages(null);
        }
        super.onDestroy();
    }

    //Button click 触发模拟操作
    private void monitorHandler() {
        Log.d(TAG, "monitorHandler: ");
        switch (memoryTestType) {
            case MEMORY_ERROR_NO_STATIC:
                //泄漏1 : 使用no-static内部类
                mHandler = new NoStaticHandler();
                break;
            case MEMORY_ERROR_INNER_CLASS:
                //泄漏2:使用匿名内部类
                mHandler = new Handler() {
                    @Override
                    public void handleMessage(@NonNull Message msg) {
                        Log.d(TAG, "inner class handler handleMessage: " + msg.what);
                        mResultTv.setText("MEMORY_ERROR_INNER_CLASS");
                    }
                };
                break;
            case MEMORY_NORMAL_STATIC_WEAK:
                //不泄露3:静态+弱引用(为了保证Handler中消息队列中的所有消息都能被执行,
                //推荐使用 静态内部类 + 弱引用的方式)
                mHandler = new StaticHandler(this);
                break;
            case MEMORY_NORMAL_MESSAGE_CLEAR:
                //不泄露4:destroy()方法对handler进行消息队列清空,结束Handler生命周期
                mHandler = new NoStaticHandler();
                break;
            default:
                Log.e(TAG, "monitorHandler: default no handle");
                return;
        }

        //执行事件延迟队列发送
        mHandler.sendEmptyMessageDelayed(1, MESSAGE_DELAY);
    }

    //no-static内部类:自定义Handler子类
    class NoStaticHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            Log.d(TAG, "NoStaticHandler handleMessage: " + msg.what);
            mResultTv.setText(memoryTestType == MEMORY_ERROR_NO_STATIC ?
                    "MEMORY_ERROR_NO_STATIC" : "MEMORY_NORMAL_MESSAGE_CLEAR");
        }
    }

    private static class StaticHandler extends Handler {
        private WeakReference<Activity> reference;

        public StaticHandler(Activity activity) {
            super(activity.getMainLooper());
            reference = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(@NonNull Message msg) {
            Log.d(TAG, "StaticHandler handleMessage: " + msg.what);
            Activity activity = reference.get();
            if (null != activity) {
                Log.d(TAG, "StaticHandler handleMessage: ui update");
                ((TextView) activity.findViewById(R.id.memory_type_result)).
                        setText("MEMORY_NORMAL_STATIC_WEAK");
            }
        }
    }

(二)MemoryProfilerActivity界面如下:
内存优化模拟界面
(三)我们对第六步的截图【memory-profiler捕获堆转储】增加操作步骤说明:

 上面的堆转储测试的是Handler的非静态内部类,流程如下:
  1.进入主界面MainActivity,点击进入MemoryProfilerActivity
  2.选择【handler使用no-static内部类】,点击【模拟handler泄漏】
  3.点击【back】返回MainActivity,等待1秒,点击AS(AndroidStudio)的【堆转储按钮】
  4.生成堆转储,准备下面的分析

(四)分析上面的堆转储文件

  1.我们选择AS的【Activity/Fragment Leaks】查看发现确实有MemoryProfilerActivity泄漏;
  2.在类名窗口中我们点击泄漏的Actvity类,在 Instance View中跟踪发现是NoStaticHandler持有了MemoryProfilerActivity的引用,而NoStaticHandler的消息队列中仍然存在消息,所以导致MemoryProfilerActivity在执行堆转储时,是不满足GC条件的。
  3.AS的【Activity/Fragment Leaks】是指是否满足GC条件,而不是是否已经GC。举个栗子:在进入一个Activity后(不执行任何操作),然后退出,这当然不会引起内存泄漏,因为它满足GC条件,但是也不是说它会立马被GC掉,你会在堆转储文件中可以看到此实例任然存在(可能会存在很久),如果你尝试N次主动GC,然后再看堆转储文件,发现就被GC掉了。
  下面就让我们去优化它,走起!

(五)优化Handler内存泄漏:静态内部类 + 弱引用的方式

  1.同样按照第三步的操作顺序执行,只是要选择【handler静态内部类 + 弱引用】
  2.查看堆转储文件,查看【Activity/Fragment Leaks】下面,是不是MemoryProfilerActivity没有内存泄漏了?

memory-profiler捕获堆转储-优化后
(六)技巧分享

 AS这个IDE的代码风险提示做的很好,大家在开发过程中要注意特殊颜色的标识,然后按照提示将其优化掉,基本就能解决大部分问题。大家可以在SamplePop的代码中感受下那些可能会引起内存泄漏等问题的特殊颜色标志:

内存泄漏IDE提示-1 内存泄漏IDE提示-2
(七)课后小练习(奥里给)
    //模拟订阅/反订阅缺失泄漏
    @SuppressLint("MissingPermission")
    private void monitorRegister() {
        Log.d(TAG, "monitorRegister: ");
        LocationManager locationManager = (LocationManager) getSystemService(LOCATION_SERVICE);
        locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER,
                TimeUnit.MINUTES.toMillis(5), 100, new LocationListener() {
                    @Override
                    public void onLocationChanged(@NonNull Location location) {
                        Log.d(TAG, "onLocationChanged: ");
                    }
                });
    }

    //模拟非静态内部类
    private static Object inner;
    private void monitorNoStaticInnerClass() {
        Log.d(TAG, "monitorNoStaticInnerClass: ");
        class InnerClass {

        }
        inner = new InnerClass();
    }

小编的扩展链接

参考链接

唯有美景不可辜负

举手之劳,赞有余香! ❤ 比心 ❤

上一篇 下一篇

猜你喜欢

热点阅读