• Android性能优化----卡顿优化


    前言

    无论是启动,内存,布局等等这些优化,最终的目的就是为了应用不卡顿。应用的体验性好坏,最直观的表现就是应用的流畅程度,用户不知道什么启动优化,内存不足,等等,应用卡顿,那么这个应用就不行,被卸载的概率非常大。所以说为了保证用户留存率,卡顿优化是非常非常的重要。在这篇文章,咱们不讨论是什么原因造成卡顿,其实在前面写的性能优化文章中,都是造成卡顿的原因,需要需要做好卡顿优化,最好从头开始一步一步来处理。今天我们主要是介绍一些针对卡顿检测的一些工具使用。

    检测卡顿常用工具

    Systrace

    Systrace这个工具在《布局优化》一章节中已经介绍过了,这里就不在赘述。地址:https://www.cnblogs.com/huangjialin/p/13353541.html

    StrictMode的使用

    StrictMode,严苛模式。StrictMode是在Android开发过程中不可缺少的性能检测工具,它能够检测出在开发过程中不合理的代码块,非常方便。

    策略分类

    StrictMode分为线程策略(ThreadPolicy)和虚拟机策略(VmPolicy)

    使用方式
     //开启Thread策略模式
            StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
                    .detectNetwork()//监测主线程使用网络io
    //                .detectCustomSlowCalls()//监测自定义运行缓慢函数
    //                .detectDiskReads() // 检测在UI线程读磁盘操作
    //                .detectDiskWrites() // 检测在UI线程写磁盘操作
                    .detectAll()
                    .penaltyLog() //写入日志
                    .penaltyDialog()//监测到上述状况时弹出对话框
                    .build());
    
            //开启VM策略模式
            StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
    //                .detectLeakedSqlLiteObjects()//监测sqlite泄露
    //                .detectLeakedClosableObjects()//监测没有关闭IO对象
    //                .setClassInstanceLimit(MainActivity.class, 1) // 设置某个类的同时处于内存中的实例上限,可以协助检查内存泄露
    //                .detectActivityLeaks()
                    .detectAll()
                    .penaltyLog()//写入日志
                    .build());
    

    上面基本都注释好了,这里就不在一一说明了。如果我们在开发过程中,能够通过StrictMode这个工具类来规避掉这些问题,那么将会大大的减少很多性能相关的问题。

    BlockCanary使用

    我们先看看怎么使用,然后在看BlockCanary
    依赖

    debugImplementation 'com.github.bzcoder:blockcanarycompat-android:0.0.4'
    

    在application中

    BlockCanary.install(mContext, appBlockCanaryContext).start();
    

    appBlockCanaryContext类

    /**
     * BlockCanary配置的各种信息
     */
    public class AppBlockCanaryContext extends BlockCanaryContext {
    
        /**
         * Implement in your project.
         *
         * @return Qualifier which can specify this installation, like version + flavor.
         */
        public String provideQualifier() {
            return "unknown";
        }
    
        /**
         * Implement in your project.
         *
         * @return user id
         */
        public String provideUid() {
            return "uid";
        }
    
        /**
         * Network type
         *
         * @return {@link String} like 2G, 3G, 4G, wifi, etc.
         */
        public String provideNetworkType() {
            return "unknown";
        }
    
        /**
         * Config monitor duration, after this time BlockCanary will stop, use
         * with {@code BlockCanary}'s isMonitorDurationEnd
         *
         * @return monitor last duration (in hour)
         */
        public int provideMonitorDuration() {
            return -1;
        }
    
        /**
         * Config block threshold (in millis), dispatch over this duration is regarded as a BLOCK. You may set it
         * from performance of device.
         *
         * @return threshold in mills
         */
        public int provideBlockThreshold() {
            return 500;
        }
    
        /**
         * Thread stack dump interval, use when block happens, BlockCanary will dump on main thread
         * stack according to current sample cycle.
         * <p>
         * Because the implementation mechanism of Looper, real dump interval would be longer than
         * the period specified here (especially when cpu is busier).
         * </p>
         *
         * @return dump interval (in millis)
         */
        public int provideDumpInterval() {
            return provideBlockThreshold();
        }
    
        /**
         * Path to save log, like "/blockcanary/", will save to sdcard if can.
         *
         * @return path of log files
         */
        public String providePath() {
            return "/blockcanary/";
        }
    
        /**
         * If need notification to notice block.
         *
         * @return true if need, else if not need.
         */
        public boolean displayNotification() {
            return true;
        }
    
        /**
         * Implement in your project, bundle files into a zip file.
         *
         * @param src  files before compress
         * @param dest files compressed
         * @return true if compression is successful
         */
        public boolean zip(File[] src, File dest) {
            return false;
        }
    
        /**
         * Implement in your project, bundled log files.
         *
         * @param zippedFile zipped file
         */
        public void upload(File zippedFile) {
            throw new UnsupportedOperationException();
        }
    
    
        /**
         * Packages that developer concern, by default it uses process name,
         * put high priority one in pre-order.
         *
         * @return null if simply concern only package with process name.
         */
        public List<String> concernPackages() {
            return null;
        }
    
        /**
         * Filter stack without any in concern package, used with @{code concernPackages}.
         *
         * @return true if filter, false it not.
         */
        public boolean filterNonConcernStack() {
            return false;
        }
    
        /**
         * Provide white list, entry in white list will not be shown in ui list.
         *
         * @return return null if you don't need white-list filter.
         */
        public List<String> provideWhiteList() {
            LinkedList<String> whiteList = new LinkedList<>();
            whiteList.add("org.chromium");
            return whiteList;
        }
    
        /**
         * Whether to delete files whose stack is in white list, used with white-list.
         *
         * @return true if delete, false it not.
         */
        public boolean deleteFilesInWhiteList() {
            return true;
        }
    
        /**
         * Block interceptor, developer may provide their own actions.
         */
        public void onBlock(Context context, BlockInfo blockInfo) {
            Log.i("lz","blockInfo "+blockInfo.toString());
        }
    }
    
    

    就是这么简单,一旦发生卡顿,那么将会以通知的形式在通知栏中显示出对应的堆栈信息(和leakcanary类似,但是两者没有任何关系)。或者在
    SD卡中的blockcanary文件夹中生成对应的log日志

    那么,问题来了BlockCanary是怎么检测卡顿的呢?大家注意AppBlockCanaryContext类中有一个provideBlockThreshold()方法,return了一个500ms。我们知道Android中消息的分发处理都是通过handler来的,handler中有一个looper对象,looper对象中有一个loop方法,看一下源码

    ...
    
     for (;;) {
                Message msg = queue.next(); // might block
               ...//省略若干
                if (logging != null) {
                    logging.println(">>>>> Dispatching to " + msg.target + " " +
                            msg.callback + ": " + msg.what);
                }
    
               ...//省略若干
                
                try {
                    msg.target.dispatchMessage(msg);
                    dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
                } finally {
                    if (traceTag != 0) {
                        Trace.traceEnd(traceTag);
                    }
                }
                ...//省略若干
    
                if (logging != null) {
                    logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
                }
    
               ...//省略若干
            }
    
    
    ...
    

    应用中的所有事件都是通过dispatchMessage这个方法来进行处理,那么是不是说dispatchMessage执行的时间就是某个事件执行的时间。如果这个时间大于我们在provideBlockThreshold()定义的时间,我们就认为发送了卡顿了,需要优化,就会获取到对应的堆栈信息保存起来并提示。

    ANR-WatchDog介绍

    发生卡顿,严重时很容易导致anr,我们知道发生anr的情况基本上就是以下几种情况
    1、触摸事件在5s没有得到响应。
    2、Bradcast 是10s,后台广播是60s
    3、service:前台service 20s 后台service 200s

    用户在使用应用时,出现ANR,那么体验将会是非常非常差,现在很多手机厂商为了用户体验,去掉了这个ANR弹框,但是还是会一直卡在这里。
    ANR-WatchDog 使用
    添加依赖

    implementation 'com.github.anrwatchdog:anrwatchdog:1.3.0'
    

    在application中的onCreate()方法

    new ANRWatchDog().setIgnoreDebugger(true).start();
    

    setIgnoreDebugger 是指忽略点断点的情况
    当然如果这样设置的话,一旦出现ANR,那么ANR-Watchdog是会将应用杀死的,如果不想杀死应用那么需要设置一个监听

    new ANRWatchDog().setANRListener(new ANRWatchDog.ANRListener() {
        @Override
        public void onAppNotResponding(ANRError error) {
            // Handle the error. For example, log it to HockeyApp:
           
        }
    }).start();
    

    ANR-WatchDog 原理
    我用自己的话来总结一下原理:前面我们说了所有的消息都是通过handler中的dispatchMessage方法进行分发的,而ANR-WatchDog 原理就是通过dispatchMessage的时间长度来判断是否出现anr的,过程大概是这样的:ANR-WatchDog 开启一个监控线程,并向主线程发一个消息,然后自己休眠5s,5s后看主线程有没有处理这个消息,如果处理了,那么继续发送,进入一下次检查。如果没有处理,说明主线程发生了阻塞,收集对应的anr信息。

    Lancet的介绍

    前面写的几篇性能优化文章基本都介绍有对应的hook框架,这里再介绍一个AOP框架---Lancet,这几个框架的比对,后面会再开一篇。这里先看看怎么使用。Lancet是一个轻量级Android AOP框架,特点:
    1、编译速度快,并且支持增量编译
    2、简洁的API,几行代码完成注入需求
    3、没有任何多余代码插入apk
    4、支持用于SDK,可以在SDK编写注入代码来修改依赖SDK的APP

    使用方式
    在根目录的build.gradle
    dependencies{
    classpath 'me.ele:lancet-plugin:1.0.5'
    }

    在APP目录的build.gradle
    apply plugin: 'me.ele.lancet'

    dependencies {
    provided 'me.ele:lancet-base:1.0.5'
    }

    开一个类aaaa.java

    @Proxy("i")
    @TargetClass("android.util.Log")
    public static int anyName(String tag, String msg){
    msg = msg + "lancet";
    return (int) Origin.call();
    }

    这样,就完成了,当我们通过Log.i("lancet","你好“);来打印日志的时候,输出的文本先被Lancet出来后,在输出。"你好,lancet"

    常见卡顿问题解决方案

    1、内存抖动的问题,GC过于频繁
    2、方法太耗时了(CPU占用)
    3、布局过于复杂,渲染速度慢
    ....

  • 相关阅读:
    mtu
    OC2_使用系统协议
    OC1_协议语句
    Json文件/网址解析
    文件归档
    Plist文件
    NS-Date/NSDateFormatter
    OC10_文件练习
    OC9_文件操作
    OC8_NSData
  • 原文地址:https://www.cnblogs.com/huangjialin/p/13389421.html
Copyright © 2020-2023  润新知