Android性能优化----执行时间优化

作者:lu人皆知

在APP做启动优化时,Application会做一些初始化的工作,但不要在Application中做耗时操作,然而有些初始化工作可能是很耗时的,那怎么办?初始化操作可以开启子线程来完成。

计算执行时间

  • 常规方案(手动埋点标记)
  • AOP方式获取

1、常规方案

常规方案就是在执行前埋点标记开始时间,在执行后埋点标记结束时间,然后计算开始时间和结束时间的差值,时间差值就是耗时时间。

具体的耗时计算实现如下代码所示,在 Application 中 onCreate 方法中调用了很多初始化的方法,我们通过手动埋点标记的方式在每一个调用的方法内部都去计算其方法的耗时时间。

//Application.java
@Override
public void onCreate() {initSharedPreference();initImageLoader();initSQLite();//.....
}private void initSharedPreference() {long startTime = System.currentTimeMillis();//init SharedPreferenceLog.d(TAG, "initSharedPreference cost :" + (System.currentTimeMillis() - startTime));    
}private void initImageLoader() {long startTime = System.currentTimeMillis();Fresco.initialize(this);Log.d(TAG, "initImageLoader cost :" + (System.currentTimeMillis() - startTime));
}
private void initSQLite() {long startTime = System.currentTimeMillis();//init buglyLog.d(TAG, "initSQLite cost :" + (System.currentTimeMillis() - startTime));    
}

上面的计算方式,是容易想到的实现方式,但缺点也很明显:

  • 每个方法都是标记并计算耗时时间,代码也不够优雅。
  • 对项目的入侵性很大,工作量大。

2、AOP方式获取

AOP 就是我们常说的面向切面编程,它可以针对同一类问题进行统一处理

下面我们就来通过 AOP 的方式来优雅的获取Application每一个方法的执行时间。

2.1引入 AspectJ

在 Android 中通过 AspectJ 这个库来使用 AOP ,接下来来引入这个库:

  • 在工程根 build.gradle 中引入 AspectJ 插件
    • classpath ‘com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.4’
  • 在Module中 build.gradle 应用插件
    • apply plugin: ‘android-aspectjx’
  • 在Module中build.gradle 中引入 aspectj 库
    • implementation ‘org.aspectj:aspectjrt:1.8.9’

2.2AOP的具体使用

  • 定义一个类PerformanceAop使用@Aspect注解
  • @Around(“execution(* com.lu.aop.MyApplication.**(…))”)表示需要对 MyApplication中的每一个方法都做 hook 处理。
  • 记录方法执行前后的时间戳,并计算对应的时间差。
@Aspect
public class PerformanceAop {private static final String TAG = PerformanceAop.class.getSimpleName();@Around("execution(* com.lu.aop.MyApplication.**(..))")public void getTime(ProceedingJoinPoint joinPoint) {Signature signature = joinPoint.getSignature();//得到方法的名字,例如:MyApplication.attachBaseContext(..)String name = signature.toShortString();//记录方法执行前的时间戳long startTime = System.currentTimeMillis();try {//执行该方法joinPoint.proceed();} catch (Throwable throwable) {throwable.printStackTrace();}//记录执行该方法的时间long costTime = System.currentTimeMillis() - startTime;Log.e(TAG, "method " + name + " cost:" + costTime);}
}

运行结果
2019-08-18 17:09:12.946 10094-10094/com.lu.aop E/PerformanceAop: method MyApplication.attachBaseContext(..) cost:1
2019-08-18 17:09:12.979 10094-10094/com.lu.aop E/PerformanceAop: method MyApplication.initSQLite() cost:11
2019-08-18 17:09:13.002 10094-10094/com.lu.aop E/PerformanceAop: method MyApplication.initImageLoader() cost:17
2019-08-18 17:09:13.002 10094-10094/com.lu.aop E/PerformanceAop: method MyApplication.onCreate() cost:28

AOP 这种方式是一个比较优雅的方式实现的,它对已有代码是零侵入性的,修改也方便。

用异步执行耗时任务

在 Application 去执行这些第三方库的初始化,是会拖慢整个应用的启动过程的,因此用子线程与主线程并行的方式来分担主线程的工作,从而减少主线程的执行时间。

在子线程中执行任务

我们可能会容易想到以下这两种方式:

  • 方式一
  public void onCreate(){new Thread() {public run() {//执行任务1//执行任务2//执行任务3}}.start();}
  • 方式二
public void onCreate(){new Thread() {public run() {//执行任务1}}.start();new Thread() {public run() {//执行任务2}}.start();new Thread() {public run() {//执行任务3}}.start();
}

方式二更加充分地利用 CPU资源,但是直接创建线程还是不够优雅,所以使用线程池来管理这些线程会更好一些。

线程池管理

获取到对应的线程池,但是这个线程个数不能随意填,我们要能充分利用到 CPU 资源,因此我们可以参考 AsyncTask 它是如何去设置核心线程数的。

Executors service = Executors.newFixedThreadPool(核心线程个数);

看到AsyncTask源码中了解核心线程数设置

private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
//CORE_POOL_SIZE 就是核心线程数
private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));

这样我们就可以设置核心线程数了

//参考AsyncTask来设置线程的个数。
ExecutorService service = Executors.newFixedThreadPool(CORE_POOL_SIZE);

在MyApplication中实现异步执行任务:

@Override
public void onCreate() {super.onCreate();//参考AsyncTask来设置线程的个数。ExecutorService service = Executors.newFixedThreadPool(CORE_POOL_SIZE);service.submit(new Runnable() {@Overridepublic void run() {initSQLite();}});service.submit(new Runnable() {@Overridepublic void run() {initImageLoader();}});
}

异步加载的代码执行结果
2019-08-18 19:09:38.022 13948-13948/com.lu.aop E/PerformanceAop: method MyApplication.attachBaseContext(..) cost:1
2019-08-18 19:09:38.062 13948-13948/com.lu.aop E/PerformanceAop: method MyApplication.onCreate() cost:4
2019-08-18 19:09:38.078 13948-13967/com.lu.aop E/PerformanceAop: method MyApplication.initSQLite() cost:9
2019-08-18 19:09:38.094 13948-13968/com.lu.aop E/PerformanceAop: method MyApplication.initImageLoader() cost:15

从Log日志数据对比,可以看出主线程执行的 onCreate 方法的执行时间从原来的 28ms 减到到了 4ms 。所以用子线程执行耗时任务还是相当好的。但是到这里又遇到一个问题,就是有一些方法是必须在 Application onCreate 执行完成之前完成初始化的,因为在 MainActivity 中就需要使用到,那我们上面的异步就会有问题了,那如何解决这个问题呢?

异步任务必须在某一个阶段执行完成

以initImageLoader()为例,不知道Application中的子线程什么时候才完成初始化任务,但是这个时候已经进入了MainActivity了,用到ImageLoader,ImageLoader在Application中还没有完成初始化就用不了,所以得控制ImageLoader在 Application onCreate 执行完成之前完成初始化。这时就需要使用到 CountDownLatch 了。

CountDownLatch是一种java.util.concurrent包下一个同步工具类,它允许一个或多个线程等待直到在其他线程中一组操作执行完成。

  • 在Application中定义CountDownLatch
//Application
private CountDownLatch countDownLatch = new CountDownLatch(1);
  • 在initImageLoader方法中执行完毕时,执行 countDownLatch.countDown()
private void initImageLoader() {Fresco.initialize(this);//try {//模拟耗时//Thread.sleep(3000);//} catch (Exception e) {// e.printStackTrace();//}//Log.e(TAG, "初始化initImageLoader完毕");//数量减一countDownLatch.countDown();}
  • 等待 countDownLatch.await()

在 onCreate 方法结束点等待,如果在此处之前之前调用了countDownLatch.countDown(),那么就直接跳过,否则就在此等待。

public void onCreate() {super.onCreate();//参考AsyncTask来设置线程的个数。ExecutorService service = Executors.newFixedThreadPool(CORE_POOL_SIZE);service.submit(new Runnable() {@Overridepublic void run() {initSQLite();}});service.submit(new Runnable() {@Overridepublic void run() {initImageLoader();}});//在 onCreate 方法中等待,如果在此处之前之前调用了countDownLatch.countDown(),那么就直接跳过,否则就在此等待。try {countDownLatch.await();} catch (InterruptedException e) {e.printStackTrace();}Log.e(TAG, "Application onCreate 执行完毕");
}

这样,我们的 Application onCreate 方法就会等待异步任务 initImageLoader 执行完毕之后才会结束 onCreate 这个方法的生命周期。

总结

  • 了解计算执行任务时间
  • 了解AOP面向切面编程知识
  • 了解AsyncTask的核心线程数及运用
  • 学习了初始化数据时异步优化方案

为了帮助到大家更好的全面清晰的掌握好性能优化,准备了相关的核心笔记(还该底层逻辑):https://qr18.cn/FVlo89

性能优化核心笔记:https://qr18.cn/FVlo89

启动优化

内存优化

UI优化

网络优化

Bitmap优化与图片压缩优化https://qr18.cn/FVlo89

多线程并发优化与数据传输效率优化

体积包优化

《Android 性能监控框架》:https://qr18.cn/FVlo89

《Android Framework学习手册》:https://qr18.cn/AQpN4J

  1. 开机Init 进程
  2. 开机启动 Zygote 进程
  3. 开机启动 SystemServer 进程
  4. Binder 驱动
  5. AMS 的启动过程
  6. PMS 的启动过程
  7. Launcher 的启动过程
  8. Android 四大组件
  9. Android 系统服务 - Input 事件的分发过程
  10. Android 底层渲染 - 屏幕刷新机制源码分析
  11. Android 源码分析实战

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/46901.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MySQL的select ... where ...会加锁吗?

先说答案:不会。但select … where … lock in share mode会加锁。实验如下。 存储引擎innodb,MySQL版本5.7。 1:select … where … 如下图: 1:select … where … lock in share mode 如下图: 接着我…

【SpringCloud】SpringCloudAlibaba官网资料

出现原因 Spring Cloud Netflix Projects Entering Maintenance Mode 官网 博客 https://github.com/alibaba/spring-cloud-alibaba/blob/master/README-zh.md官网 https://spring.io/projects/spring-cloud-alibaba#overview英文 https://github.com/alibaba/spring-cloud-…

五种消息模型简单说明

五种消息模型简单说明 RabbitMQ提供了6种消息模型,但是第6种其实是RPC,并不是MQ,因此不予学习。那么也就剩下5种。但是其实3、4、5这三种都属于订阅模型,只不过进行路由的方式不同。  我们通过一个demo工程来了解下RabbitMQ的…

pytest自动化框架运行全局配置文件pytest.ini

还记得在之前的篇章中有讲到Pytest是目前主要流行的自动化框架之一,他有基础的脚本编码规则以及两种运行方式。 pytest的基础编码规则是可以进行修改,这就是今日文章重点。 看到这大家心中是否提出了两个问题:pytest的基础编码规则在哪可以…

【算法C++实现】5、二叉树

二叉树节点结构体 class Node { public:int val;Node* left;Node* right;Node(int a) : val(a), left(nullptr), right(nullptr) {}Node(int a, Node* l, Node* r): val(a), left(l), right(r) {}}1、递归遍历 递归遍历二叉树,每个节点的遍历顺序叫递归序&#xf…

软件工程模型-架构师之路(四)

软件工程模型 敏捷开发: 个体和交互 胜过 过程和工具、可以工作的软件 胜过 面面俱到的文件、客户合作胜过合同谈判、响应变化 胜过 循序计划。(适应需求变化,积极响应) 敏捷开发与其他结构化方法区别特点:面向人的…

代码随想录第27天|39. 组合总和,40.组合总和II,131.分割回文串

39. 组合总和 分析这道题的搜索过程如下: 因为这道题没有限制要搜索几层,所以可以一直搜索直到sumtarget或者sum>target就return 回溯三部曲 1.递归函数参数 本题还需要startIndex来控制for循环的起始位置,对于组合问题,什么…

P1328 [NOIP2014 提高组] 生活大爆炸版石头剪刀布

[NOIP2014 提高组] 生活大爆炸版石头剪刀布 题目描述 石头剪刀布是常见的猜拳游戏:石头胜剪刀,剪刀胜布,布胜石头。如果两个人出拳一样,则不分胜负。在《生活大爆炸》第二季第 8 集中出现了一种石头剪刀布的升级版游戏。 升级版游戏在传统的石头剪刀布游戏的基础…

在ARM服务器上一键安装Proxmox VE(以在Oracle Cloud VPS上为例)(甲骨文)

前言 如题,具体用到的说明文档如下 virt.spiritlhl.net 具体流程 首先是按照说明,先得看看自己的服务器符不符合安装 Proxmox VE的条件 https://virt.spiritlhl.net/guide/pve_precheck.html#%E5%90%84%E7%A7%8D%E8%A6%81%E6%B1%82 有提到硬件和软…

CSS 选择器

前言 基础选择器 以下是几种常见的基础选择器。 标签选择器&#xff1a;通过HTML标签名称选择元素。 例如&#xff1a; p {color: red; } 上述样式规则将选择所有<p>标签 &#xff0c;并将其文字颜色设置为红色。 类选择器&#xff1a;通过类名选择元素。使用类选择…

课程项目设计--spring security--用户管理功能--宿舍管理系统--springboot后端

写在前面&#xff1a; 还要实习&#xff0c;每次时间好少呀&#xff0c;进度会比较慢一点 本文主要实现是用户管理相关功能。 前文项目建立 文章目录 验证码功能验证码配置验证码生成工具类添加依赖功能测试编写controller接口启动项目 security配置拦截器配置验证码拦截器 …

电脑上安装,多版本node

手上有一个vue3的项目&#xff0c;sass配置如下图所示&#xff1a; 安装了Python3.10和node 16.14.0&#xff0c;项目能正常install 跟run。 因工作需要&#xff0c;收上有一个vue2的项目&#xff0c;sass配置如下图所示&#xff1a; 执行npm intsall 的时候一直报Python2找不…

2022年12月 C/C++(二级)真题解析#中国电子学会#全国青少年软件编程等级考试

第1题:数组逆序重放 将一个数组中的值按逆序重新存放。例如,原来的顺序为8,6,5,4,1。要求改为1,4,5,6,8。 输入 输入为两行:第一行数组中元素的个数n(1<n<100),第二行是n个整数,每两个整数之间用空格分隔。 输出 输出为一行:输出逆序后数组的整数,每两个整数之间…

Java云原生框架Quarkus初探

Java云原生框架Quarkus初探 Quarkus 介绍 Quarkus 是一个云原生&#xff0c;容器优先的Java应用框架&#xff0c;它号称是超音速和亚原子的框架&#xff0c;主要特点是构建速度、启动速度快和占用资源少等特点。它为OpenJDK HotSpot和GraalVM量身定制&#xff0c; 根据Java库和…

常用消息中间件介绍

RocketMQ 阿里开源&#xff0c;阿里参照kafka设计的&#xff0c;Java实现 能够保证严格的消息顺序 提供针对消息的过滤功能 提供丰富的消息拉取模式 高效的订阅者水平扩展能力 实时的消息订阅机制 亿级消息堆积能力 RabbitMQ Erlang实现&#xff0c;非常重量级&#xff0c;更适…

Nevron 3DChart Crack,可视化界面在运行时可用

Nevron 3DChart Crack,可视化界面在运行时可用 3DChart使用OpenGL 3D图形引擎创建复杂的2D和3D图表&#xff0c;这些图表可以包含静态或动画图像。3DChart包括一个用于生成图表模板的独立应用程序和一个ASP服务器配置实用程序。该组件还包括一个专门设计用于与3DChart集成的工具…

java版本企业电子招标采购系统源码Spring Cloud + Spring Boot +二次开发

java版本企业电子招标采购系统源码Spring Cloud Spring Boot 二次开发 一、立项管理 1、招标立项申请 功能点&#xff1a;招标类项目立项申请入口&#xff0c;用户可以保存为草稿&#xff0c;提交。 2、非招标立项申请 功能点&#xff1a;非招标立项申请入口、用户可以保存为草…

Scratch 游戏 之 随机大地图生成教程

在很多生存 / 沙盒类游戏中&#xff0c;地图往往是随机生成的&#xff0c;例如&#xff1a;饥荒、我的世界等。那我们该如何在scratch中实现这一点呢&#xff1f; 在scratch中有两种办法可以实现——画笔和克隆体。我们这次先聊克隆体。 我们可以先将克隆体设置为方形的&#x…

打怪升级之从零开始的网络协议

序言 三个多月过去了&#xff0c;我又来写博客了&#xff0c;这一次从零开始学习网络协议。 总的来说&#xff0c;计算机网络很像现实生活中的快递网络&#xff0c;其最核心的目标&#xff0c;就是把一个包裹&#xff08;信息&#xff09;从A点发送到B点去。下面是一些共同的…