zoukankan      html  css  js  c++  java
  • android 内存处理工具

    1. LeakCanary 检查内存泄露

    LeakCanary 是一个开源的在debug版本中检测内存泄漏的java库。

    让我们来看看一个cait的例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    class Cat {
    }
    class Box {
      Cat hiddenCat;
    }
    class Docker {
      static Box container;
    }
     
    // ...
     
    Box box = new Box();
    Cat schrodingerCat = new Cat();
    box.hiddenCat = schrodingerCat;
    Docker.container = box;

    创建一个RefWatcher实例,然后给它一个对象让它观察:

    1
    2
    // We expect schrodingerCat to be gone soon (or not), let's watch it.
    refWatcher.watch(schrodingerCat);

    当检测出泄漏的时候,你会自动得到一个漂亮的泄漏线索:

    1
    2
    3
    * GC ROOT static Docker.container
    * references Box.hiddenCat
    * leaks Cat instance

    我们知道你的时间宝贵,因此我们让它非常好设置。只需几行代码,LeakCanary就能自动检测Activity的泄漏:

    1
    2
    3
    4
    5
    6
    public class ExampleApplication extends Application {
      @Override public void onCreate() {
        super.onCreate();
        LeakCanary.install(this);
      }
    }

    当内存不足时,会有一个通知和良好的展示界面:

    leaktrace.png

    结论

    在启用LeakCanary之后,我们发现和修复了许多内存泄漏的问题。我们甚至发现了一些 Android SDK中的泄漏

    结果是非常令人惊奇的,我们现在减少了94%的oom崩溃问题。

    oom_rate.png

    2.BlockCanary 检查ui卡顿

    BlockCanary对主线程操作进行了完全透明的监控,并能输出有效的信息,帮助开发分析、定位到问题所在,迅速优化应用。其特点有:

    • 非侵入式,简单的两行就打开监控,不需要到处打点,破坏代码优雅性。
    • 精准,输出的信息可以帮助定位到问题所在(精确到行),不需要像Logcat一样,慢慢去找。

    目前包括了核心监控输出文件,以及UI显示卡顿信息功能。仅支持Android端。

    原理

    熟悉Message/Looper/Handler系列的同学们一定知道 Looper.java 中这么一段: 

    private static Looper sMainLooper;  // guarded by Looper.class
    
    ...
    
    /**
     * Initialize the current thread as a looper, marking it as an
     * application's main looper. The main looper for your application
     * is created by the Android environment, so you should never need
     * to call this function yourself.  See also: {@link #prepare()}
     */
    public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }
    
    /** Returns the application's main looper, which lives in the main thread of the application.
     */
    public static Looper getMainLooper() {
        synchronized (Looper.class) {
            return sMainLooper;
        }
    }
    

    即整个应用的主线程,只有这一个looper,不管有多少handler,最后都会回到这里。

    如果再细心一点会发现在Looper的loop方法中有这么一段 

    public static void loop() {
        ...
    
        for (;;) {
            ...
    
            // This must be in a local variable, in case a UI event sets the logger
            Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }
    
            msg.target.dispatchMessage(msg);
    
            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }
    
            ...
        }
    }
    

    是的,就是这个Printer - mLogging,它在每个message处理的前后被调用,而如果主线程卡住了,不就是在dispatchMessage里卡住了吗?

    核心流程图:

    该组件利用了主线程的消息队列处理机制,通过

    Looper.getMainLooper().setMessageLogging(mainLooperPrinter);
    

    并在 mainLooperPrinter 中判断start和end,来获取主线程dispatch该message的开始和结束时间,并判定该时间超过阈值(如2000毫秒)为主线程卡慢发生,并dump出各种信息,提供开发者分析性能瓶颈。 

    ...
    @Override
    public void println(String x) {
        if (!mStartedPrinting) {
            mStartTimeMillis = System.currentTimeMillis();
            mStartThreadTimeMillis = SystemClock.currentThreadTimeMillis();
            mStartedPrinting = true;
        } else {
            final long endTime = System.currentTimeMillis();
            mStartedPrinting = false;
            if (isBlock(endTime)) {
                notifyBlockEvent(endTime);
            }
        }
    }
    
    private boolean isBlock(long endTime) {
        return endTime - mStartTimeMillis > mBlockThresholdMillis;
    }
    ...
    

    说到此处,想到是不是可以用mainLooperPrinter来做更多事情呢?既然主线程都在这里,那只要parse出app包名的第一行,每次打印出来,是不是就不需要打点也能记录出用户操作路径? 再者,比如想做onClick到页面创建后的耗时统计,是不是也能用这个原理呢? 之后可以试试看这个思路(目前存在问题是获取线程堆栈是定时3秒取一次的,很可能一些比较快的方法操作一下子完成了没法在stacktrace里面反映出来)。

    功能

    BlockCanary会在发生卡顿(通过MonitorEnv的getConfigBlockThreshold设置)的时候记录各种信息,输出到配置目录下的文件,并弹出消息栏通知(可关闭)。

    简单的使用如在开发、测试、Monkey的时候,Debug包启用

    • 开发可以通过图形展示界面直接看信息,然后进行修复
    • 测试可以把log丢给开发,也可以通过卡慢详情页右上角的更多按钮,分享到各种聊天软件(不要怀疑,就是抄的LeakCanary)
    • Monkey生成一堆的log,找个专人慢慢过滤记录下重要的卡慢吧

    还可以通过Release包用户端定时开启监控并上报log,后台匹配堆栈过滤同类原因,提供给开发更大的样本环境来优化应用。

    本项目提供了一个友好的展示界面,供开发测试直接查看卡慢信息(基于LeakCanary的界面修改)。

    dump的信息包括:

    • 基本信息:安装包标示、机型、api等级、uid、CPU内核数、进程名、内存、版本号等
    • 耗时信息:实际耗时、主线程时钟耗时、卡顿开始时间和结束时间
    • CPU信息:时间段内CPU是否忙,时间段内的系统CPU/应用CPU占比,I/O占CPU使用率
    • 堆栈信息:发生卡慢前的最近堆栈,可以用来帮助定位卡慢发生的地方和重现路径

    sample如下图,可以精确定位到代码中哪一个类的哪一行造成了卡慢。

    总结

    BlockCanary作为一个Android组件,目前还有局限性,因为其在一个完整的监控系统中只是一个生产者,还需要对应的消费者去分析日志,比如归类排序,以便看出哪些卡慢更有修复价值,需要优先处理;又比如需要过滤机型,有些奇葩机型的问题造成的卡慢,到底要不要去修复是要斟酌的。扯远一点的话,像是埋点除了统计外,完全还能用来做链路监控,比如一个完整的流程是A -> B -> D -> E, 但是某个时间节点突然A -> B -> D后没有到达E,这时候监控平台就可以发出预警,让开发人员及时定位。很多监控方案都需要C/S两端的配合。

    目前阿里内多个Android项目接入并使用BlockCanary来优化Android应用的性能。

    3. MAT 内存溢出 http://blog.csdn.net/aaa2832/article/details/19419679

    4. 过度绘制 - 开发者模式里面的“过度绘制”  http://www.cnblogs.com/liuling/p/2015-10-08-2.html

  • 相关阅读:
    Tensorflow2.0语法
    Neural Networks and Deep Learning--Introduction to Deep Leraning
    机器学习---吴恩达---Week11(机器学习应用举例分析)
    机器学习---吴恩达---Week10(机器学习概述与单变量线性回归方程分析)
    机器学习---吴恩达---Week9_2(推荐系统)
    机器学习---吴恩达---Week9_1(异常检测)
    机器学习---吴恩达---Week8_2(非监督学习_PCA)
    机器学习---吴恩达---Week8(非监督学习_集群算法)
    机器学习---吴恩达---Week7(支持向量机学习SVM)
    机器学习---吴恩达---Week6_2(机器学习系统设计)
  • 原文地址:https://www.cnblogs.com/wangzehuaw/p/5288880.html
Copyright © 2011-2022 走看看