前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >卡顿优化

卡顿优化

作者头像
Yif
发布2020-04-23 17:40:54
1.7K0
发布2020-04-23 17:40:54
举报
文章被收录于专栏:Android 进阶

耗时任务

StrictMode

帮助开发者检查代码不规范问题 严苛模式:Android 提供的一种运行检查机制 方便强大,容易被忽视,包含线程策略与虚拟机检测策略

线程策略

  1. 自定义耗时调用:detectCustomSlowCalls()
  2. 磁盘读取策略:detectDiskReads()
  3. 网络操作:detectNetwork()

虚拟机策略

  1. Activity泄漏:detectActivityLeaks()
  2. Sqlite泄漏:detectLeakedSqlitebjects()
  3. 检测实例数量:setClassInstanceLimit()
代码语言:javascript
复制
通过日志进行输出不规范的信息,在日志输出窗口中使用StrictMode进行过滤输出的信息
//在release版本中不建议开启严格模式
    private boolean DEV_MODE = true;
 
    /**
     * StrictMode 用来检测代码规范问题,有两种测量,一种是线程策略,一种是虚拟机策略
     */
    private void initStrictMode() {
        if (DEV_MODE) {
            StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
                    .detectCustomSlowCalls()//检测执行缓慢的代码或者潜在的缓慢代码
                    .detectDiskReads()
                    .detectDiskWrites()
                    .detectNetwork()
                    .penaltyDialog()//弹窗违规提示框
                    .penaltyLog()//进行违规日志打印
                    .penaltyFlashScreen()
                    .build());
 
            StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
                    .detectActivityLeaks()//activity 泄漏
                    .detectLeakedClosableObjects()//未关闭的closeable对象泄漏
                    .detectLeakedSqlLiteObjects()//泄漏SQlite对象
                    .penaltyLog()
                    // .penaltyDeath()//使用严格模式,系统会做出相应的反应,比如打印日志,弹出对话框,以及崩溃,这里进行崩溃操作
                    .build());
        }
    }
 

注意

  1. 在线上环境即Release版本不建议开启严格模式。
  2. 严格模式无法监控JNI中的磁盘IO和网络请求。
  3. 应用中并非需要解决全部的违例情况,比如有些IO操作必须在主线程中进行(Android中io操作在主线程中还是很常见的,比如sp的读写啊,从文件中读取图片啊,缓存啊之类的,并不是都是异步的,只要是不太耗时的就行,但是android中从3.0后就不允许UI线程中进行联网操作了,所以联网是必须独立了)。

数据少量且快速的IO操作是可以放在UI线程的,比如说少量的文件数据读取或者是写入之类的。。 但是如果涉及到数据量较大或者速度较慢的IO操作比如网络请求或者是蓝牙通信,避免放在UI线程中,这会阻塞UI线程,严重的时候甚至会导致app直接闪退报错。。 建议这些耗时的IO操作都放入到新开辟的线程中进行。 UI线程最好只需要负责UI界面的显示更新之类的操作。

Android 监控组件 AndroidPerformanceMonitor

implementation 'com.github.markzhai:blockcanary-android:1.5.0'

AndroidPerformanceMonitor 是一个检测卡顿的开源库,前身是BlockCanary,更前身则是LeakCanary。而其使用与LeakCanary也比较相似,可以自主设置卡顿检测时间,检测到的卡顿同样是以Notification展示,在使用体验上也相当类似,与LeakCanary可以说是孪生兄弟。 原理: 利用了Looper.loop()中每个Message被分发前后的Log打印,而我们设置自己的Printer就可以根据Log的不同的处理: - Message分发前,使用HandlerThread延时发送一个Runnable,这个时间可自己设置; - Message在规定的时间内完成分发,则会取消掉这个Runnable; - Message没有在规定的时间内(实际上是规定时间的0.8)完成分发,那这个Runnable就会被执行,可以获取到当前的堆栈信息;

代码语言:javascript
复制
// 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);
}
 
  1. 非侵入式
  2. 方便精准,定位到某一行代码

初始化操作:BlockCanary.install(this, new AppBlockCanaryContext()).start();

代码语言:javascript
复制
/**
 * BlockCanary配置的各种信息
 */
public class AppBlockCanaryContext extends BlockCanaryContext {
 
    /**
     * Implement in your project.
     *
     * @return Qualifier which can specify this installation, like version + flavor.
     */
    @Override
    public String provideQualifier() {
        return "unknown";
    }
 
    /**
     * Implement in your project.
     *
     * @return user id
     */
    @Override
    public String provideUid() {
        return "uid";
    }
 
    /**
     * Network type
     *
     * @return {@link String} like 2G, 3G, 4G, wifi, etc.
     */
    @Override
    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)
     */
    @Override
    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
     */
    @Override
    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)
     */
    @Override
    public int provideDumpInterval() {
        return provideBlockThreshold();
    }
 
    /**
     * Path to save log, like "/blockcanary/", will save to sdcard if can.
     *
     * @return path of log files
     */
    @Override
    public String providePath() {
        return "/blockcanary/";
    }
 
    /**
     * If need notification to notice block.
     *
     * @return true if need, else if not need.
     */
    @Override
    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
     */
    @Override
    public boolean zip(File[] src, File dest) {
        return false;
    }
 
    /**
     * Implement in your project, bundled log files.
     *
     * @param zippedFile zipped file
     */
    @Override
    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.
     */
    @Override
    public List<String> concernPackages() {
        return null;
    }
 
    /**
     * Filter stack without any in concern package, used with @{code concernPackages}.
     *
     * @return true if filter, false it not.
     */
    @Override
    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.
     */
    @Override
    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.
     */
    @Override
    public boolean deleteFilesInWhiteList() {
        return true;
    }
 
    /**
     * Block interceptor, developer may provide their own actions.
     */
    @Override
    public void onBlock(Context context, BlockInfo blockInfo) {
        Log.i("lz","blockInfo "+blockInfo.toString());
    }
}
 

ANR

AMS让应用组件做的操作,没有在规定的时间内完成,就会弹出这个AppNotRespondingDialog弹框告诉该应用未响应。 1. KeyDispacthTimeOut 5s 2. BroadcastTimeout 前台10s, 后台60s 3. ServiceTimeout 前台20s,后台200s 在源码中appNotRespondingmUIHandler通过发送消息弹出一个DialogAppNotRespondingDialog,这里的mUIHandler不是systemServer的主线程,而是子线程

Service超时

realStartServiceLocked中进行启动service,如果应用没有在规定时间内启动服务,就会触发一个超时机制,scheduleServiceTimeoutLocked,就会触发serviceTimeout,弹出AppNotRespondingDialog。如果service正常启动完成就会在handleCreateService中调用AMS的serviceDoneExecutingLocked中移除超时消息

ANR 执行流程

  1. 发送ANR
  2. 进程接收异常终止信号,开始写入进程ANR信息
  3. 弹出ANR提示框(Room表现不一,有些手机厂商会把提示框给去掉)

ANR 解决方式

adb pull data/anr/traces.txt存储路径,然后分析CPU、IO及锁

ANR 测试

代码语言:javascript
复制
 //给主线程造成卡顿,在子线程中获取锁,并让主线中等待20s,在让它获取锁
        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (MainActivity.this){
                    try {
                        Thread.sleep(20000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
 //申请这把锁
        synchronized (MainActivity.this){
            LogUtils.i("");
        }
 

ANR 监控

  1. 通过FileObserver监控文件变化,高版本权限问题
  2. 使用ANR-WatchDog

ANR-WatchDog

非侵入式ANR检测组件 https://github.com/SalomonBrys/ANR-WatchDog 使用:new ANRWatchDog().start();

原理

ANR-WatchDog同样是一个检测卡顿的检测库,与AndroidPerformanceMonitor不一样的是它的原理相对简单: 原理是开启一个线程,持续循环不断的往UI线程中Post一个Runnable(修改一个数的大小),然后在规定时间之后检测这个Runnable是否被执行(数的大小有没有 被修改过来)。没有被执行的话说明主线程执行上一个Message超时,然后获取当前堆栈信息; ANR-WatchDog的原理更加简单,但是根据使用情况来看准确性不及AndroidPerformanceMonitor高,而且可设置的配置不如AndroidPerformanceMonitor丰富;

AndroidPerformanceMonitor与 ANR-WatchDog 区别
  • AndroidPerformanceMonitor:监控Msg
  • ANR-WatchDog:看最终结果
  • 前者适合监控卡顿,后者适合补充ANR监控
ANR-WatchDog优缺点

优点 1. 兼容性好,各个机型版本通用 2. 无需修改APP逻辑代码,非侵入式 3. 逻辑简单,性能影响不大

缺点 无法保证能捕捉所有ANR,对阈值的设置直接影响捕获概率

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020年4月22日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 耗时任务
  • StrictMode
    • 线程策略
      • 虚拟机策略
        • 注意
        • Android 监控组件 AndroidPerformanceMonitor
        • ANR
          • Service超时
            • ANR 执行流程
              • ANR 解决方式
                • ANR 测试
                  • ANR 监控
                    • ANR-WatchDog
                      • 原理
                      • AndroidPerformanceMonitor与 ANR-WatchDog 区别
                      • ANR-WatchDog优缺点
                  领券
                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档