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.hqwc.cn/news/72400.html

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

相关文章

Vue在页面输出JSON对象,测试接口可复制使用

效果图&#xff1a; 数据处理前&#xff1a; 数据处理后&#xff1a; 代码实现&#xff1a; HTML: <el-table height"600" :data"tableData" border style"width: 100%" tooltip-effect"dark" size"mini"><el-…

【vue3】固定上导航栏和左侧导航栏,只显示其他内容在主内容区域

实现思路&#xff1a; 在一个单独的vue组件文件中只写出上导航栏和左侧导航栏的内容将你想要展示的页面主内容写到单独的组件中在index.js写路由&#xff0c;将【想要展示的页面主内容的路由】作为【子路由】写在【只写出上导航栏和左侧导航栏的路由】的下面&#xff1b; 在el…

24. 两两交换链表中的节点

给你一个链表&#xff0c;两两交换其中相邻的节点&#xff0c;并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题&#xff08;即&#xff0c;只能进行节点交换&#xff09;。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4] 输出&#xff1a;[2,1,4…

【山河送书第七期】:《强化学习:原理与Python实战》揭秘大模型核心技术RLHF!

《强化学习&#xff1a;原理与Python实战》揭秘大模型核心技术RLHF&#xff01; 一图书简介二RLHF是什么&#xff1f;三RLHF适用于哪些任务&#xff1f;四RLHF和其他构造奖励模型的方法相比有何优劣&#xff1f;五什么样的人类反馈才是好反馈&#xff1f;六如何减小人类反馈带来…

宏工科技十五周年,“归零心态”竞逐全球

长久以来&#xff0c;物料处理领域一直处于被大型跨国企业垄断&#xff0c;或是国内厂商野蛮生长的状态&#xff0c;宏工科技在15年发展中&#xff0c;通过培育自身的技术创新与自主研发能力&#xff0c;在物料处理领域突围&#xff0c;实现跨行业发展、规模化扩张和持续增长。…

【学习FreeRTOS】第10章——FreeRTOS时间片调度

1.时间片调度简介&#xff08;同第2章1.3&#xff09; 同等优先级任务轮流地享有相同的 CPU 时间(可设置)&#xff0c; 叫时间片&#xff0c;在FreeRTOS中&#xff0c;一个时间片就等于SysTick 中断周期 首先Task1运行完一个时间片后&#xff0c;切换至Task2运行Task2运行完…

数据库分片原则和算法

1. 数据分片概念 数据库分片是指将一个大型数据库拆分成多个小型数据库&#xff0c;每个小型数据库称为一个分片。通过这种方式&#xff0c;可以将数据库的负载分散到多个服务器上&#xff0c;从而提升性能瓶颈以及可用性。 数据分片的核心手段就是对关系型数据库进行分库和分表…

uniapp的逆地理编码 和地理编码

1.先打开高德地图api找到那个 地理编码 2.封装好我们的请求 3.逆地理编码 和地理编码 都是固定的 记住自己封装的请求 就可以了 这个 是固定的 方式 下面这个是固定的 可以复制过去 getlocation就是uniapp提供的 获取经纬度 然后 下面的 就是高德地图提供的 方法 要想使用我…

seaborn color palette 调色板颜色图

Here is a list of the Color Brewer palettes, with their names for easy reference: sns.lineplot(datanormal_df, palettesns.color_palette(paletteSet1, n_colors1))

ubuntu向日葵无法连接

近来收到部分用户小伙伴的反馈&#xff0c;在CentOS8、Ubuntu17.10或更高版本上使用向日葵个人版&#xff0c; 当其它设备远程控制自己的时候&#xff0c;控制界面会出现“连接已断开”或显示为黑屏的情况。 这其实是由于系统默认采用了wayland作为显示服务器的缘故&#xff0…

蔚来李斌卖手机:安卓系统,苹果售价,一年一发

‍作者 | Amy 编辑 | 德新 车圈大佬的玩法真让人寻不着套路&#xff01; 苹果的库克和小米的雷布斯&#xff0c;甚至是FF贾老板准备许久&#xff0c;都想分一块新能源车的蛋糕&#xff0c;蔚来李斌却反手进军手机界&#xff0c;从宣布造手机到手机入网仅仅隔了一年。 近期&a…

关于小程序收集用户手机号行为的规范

手机号在日常生活中被广泛使用&#xff0c;是重要的用户个人信息&#xff0c;小程序开发者应在用户明确同意的前提下&#xff0c;依法合规地处理用户的手机号信息。 而部分开发者在处理用户手机号过程中&#xff0c;存在不规范收集行为&#xff0c;影响了用户的正常使用体验&a…