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
- 开机Init 进程
- 开机启动 Zygote 进程
- 开机启动 SystemServer 进程
- Binder 驱动
- AMS 的启动过程
- PMS 的启动过程
- Launcher 的启动过程
- Android 四大组件
- Android 系统服务 - Input 事件的分发过程
- Android 底层渲染 - 屏幕刷新机制源码分析
- Android 源码分析实战
相关文章:

Android性能优化----执行时间优化
作者:lu人皆知 在APP做启动优化时,Application会做一些初始化的工作,但不要在Application中做耗时操作,然而有些初始化工作可能是很耗时的,那怎么办?初始化操作可以开启子线程来完成。 计算执行时间 常规…...

基于Python的微博大数据舆情分析,舆论情感分析可视化系统,可作为Python毕业设计
运行效果图 基于Python的微博大数据舆情分析,舆论情感分析可视化系统 系统介绍 微博舆情分析系统,项目后端分爬虫模块、数据分析模块、数据存储模块、业务逻辑模块组成。 先后进行了数据获取和筛选存储,对存储后的数据库数据进行提取分析处…...
被迫学习一波Linux命令
事情起因 部署一个服务,人家说了最低配置是3G,我没当回事,拿着个2G的服务器直接就上了,结果,哈哈,都能猜到结果:服务器内存爆了!!!而且最可气的是服务器还登…...
字符串变量拼接操作的底层原理
在java中,字符串变量拼接操作使用的是StringBuilder或StringBuffer类,这两个类都是可变的字符串缓冲区。java中的字符串是不可变的,因此在进行字符串拼接时需要使用可变的字符串缓冲区,以避免不必要的内存分配和复制。具体来说&am…...

Wlan安全——认证与加密方式(WPA/WPA2)
目录 终端认证技术 WEP认证 PSK认证 802.1x认证与MAC认证 Portal认证 数据加密技术 WEP加密 TKIP加密 CCMP加密 TKIP和CCMP生成密钥所需要的密钥信息 802.11安全标准 WEP共享密钥认证、加密工作原理 WEP共享密钥认证 WEP加解密过程 PSK认证以及生成动态密钥的工…...

Leetcode-每日一题【剑指 Offer 31. 栈的压入、弹出序列】
题目 输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如,序列 {1,2,3,4,5} 是某栈的压栈序列,序列 {4,5,3,2,1} 是该压栈序列对应的一个弹出序列…...

软件需求-架构师之路(五)
软件需求 软件需求: 指用户 对系统在功能、行为、性能、设计约束等方面的期望。 分为 需求开发 和 需求管理 两大过程。 需求开发: 需求获取需求分析需求定义(需求规格说明书)需求验证:拉客户一起评审,…...
Python自带的IDLE有什么用
在Python的官方解释器中,自带了一个名为IDLE(Interactive DeveLopment Environment)的集成开发环境。 一、简化代码调试过程 很多初学者在编写Python代码时,经常会遇到一些问题需要调试。而在IDLE中,我们可以通过设置断点、单步调试等方法&…...

设计模式之简单工厂模式
一、概述 定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂模式使一个类的实例化延迟到其子类。 简单工厂模式:又叫做静态工厂方法模式,是由一个工厂对象决定创建出哪一种产品类的实例。 二、适用性 1.当一个类不知道它所必须…...

从SaaS到RPA,没有真正“完美”的解决方案!
众所周知,SaaS行业越来越卷,利润也越来越“薄”,这是传统软件厂商的悲哀,也是未来数字化行业不得不面对的冷峻现状之一。 随着基于aPaaS、低代码的解决方案之流行,SaaS行业变得越来越没有技术门槛,IT人员的…...

miniconda克隆arcpy
arcpy环境克隆 前言尝试思考到此结束 前言 最近遇到了一些问题,需要用到arcpy来处理一些东西,但众所周知,arcgis的arcpy是python 2.0的,我不是很喜欢;所以我安装了arcgis pro 2.8,我发现这也是个坑&#x…...

一万字关于java数据结构堆的讲解,让你从入门到精通
目录 java类和接口总览 队列(Queue) 1. 概念 2. 队列的使用 以下是一些常用的队列操作: 1.入队操作 2.出队操作 3.判断队列是否为空 4.获取队列大小 5.其它 优先级队列(堆) 1. 优先级队列概念 Java中的PriorityQueue具有以下特点 2.常用的PriorityQue…...
Java集合底层源码剖析-ArrayList和LinkedList
文章目录 ArrayList基本原理优缺点核心方法的原理数组扩容以及元素拷贝LinkedList基本原理优缺点双向链表数据结构插入元素的原理获取元素的原理删除元素的原理Vector和Stack栈数据结构的源码剖析ArrayList 基本原理 ArrayList是Java中的一个非常常用的数据结构,它实现了Lis…...

【数据分享】2006-2021年我国城市级别的市政公用设施建设固定资产投资相关指标(30多项指标)
《中国城市建设统计年鉴》中细致地统计了我国城市市政公用设施建设与发展情况,在之前的文章中,我们分享过基于2006-2021年《中国城市建设统计年鉴》整理的2006—2021年我国城市级别的市政设施水平相关指标(可查看之前的文章获悉详情ÿ…...

学点Selenium玩点新鲜~,让分布式测试有更多玩法
前 言 我们都知道 Selenium 是一款在 Web 应用测试领域使用的自动化测试工具,而 Selenium Grid 是 Selenium 中的一大组件,通过它能够实现分布式测试,能够帮助团队简单快速在不同的环境中测试他们的 Web 应用。 分布式执行测试其实并不是一…...

【SpringBoot学习笔记】04. Thymeleaf模板引擎
模板引擎 所有的html元素都可以被thymeleaf替换接管 th:元素名 templates下的只能通过Controller来跳转,templates前后端分离,需要模板引擎thymeleaf支持 模板引擎的作用就是我们来写一个页面模板,比如有些值呢,是动态的&#x…...

【uni-app】 .sync修饰符与$emit(update:xxx)实现数据双向绑定
最近在看uni-app文档,看到.sync修饰符的时候,觉得很有必要记录一下 其实uni-app是一个基于Vue.js和微信小程序开发框架的跨平台开发工具 所以经常会听到这样的说法,只要你会vue,uni-app就不难上手 在看文档的过程中,发…...

链表之第二回
欢迎来到我的:世界 该文章收入栏目:链表 希望作者的文章对你有所帮助,有不足的地方还请指正,大家一起学习交流 ! 目录 前言第一题:反转一个链表第二题:链表内指定区间反转第三题:判断一个链表…...

【sgDragSize】自定义拖拽修改DIV尺寸组件,适用于窗体大小调整
核心原理就是在四条边、四个顶点加上透明的div,给不同方向提供按下移动鼠标监听 ,对应计算宽度高度、坐标变化 特性: 支持设置拖拽的最小宽度、最小高度、最大宽度、最大高度可以双击某一条边,最大化对应方向的尺寸;再…...
Gson与FastJson详解
Gson与FastJson详解 Java与JSON 做什么? 将Java中的对象 快速的转换为 JSON格式的字符串. 将JSON格式的字符串, 转换为Java的对象. Gson 将对象转换为JSON字符串 转换JSON字符串的步骤: 引入JAR包 在需要转换JSON字符串的位置编写如下代码即可: String json new Gson().to…...
C++:std::is_convertible
C++标志库中提供is_convertible,可以测试一种类型是否可以转换为另一只类型: template <class From, class To> struct is_convertible; 使用举例: #include <iostream> #include <string>using namespace std;struct A { }; struct B : A { };int main…...
React Native 导航系统实战(React Navigation)
导航系统实战(React Navigation) React Navigation 是 React Native 应用中最常用的导航库之一,它提供了多种导航模式,如堆栈导航(Stack Navigator)、标签导航(Tab Navigator)和抽屉…...

阿里云ACP云计算备考笔记 (5)——弹性伸缩
目录 第一章 概述 第二章 弹性伸缩简介 1、弹性伸缩 2、垂直伸缩 3、优势 4、应用场景 ① 无规律的业务量波动 ② 有规律的业务量波动 ③ 无明显业务量波动 ④ 混合型业务 ⑤ 消息通知 ⑥ 生命周期挂钩 ⑦ 自定义方式 ⑧ 滚的升级 5、使用限制 第三章 主要定义 …...

定时器任务——若依源码分析
分析util包下面的工具类schedule utils: ScheduleUtils 是若依中用于与 Quartz 框架交互的工具类,封装了定时任务的 创建、更新、暂停、删除等核心逻辑。 createScheduleJob createScheduleJob 用于将任务注册到 Quartz,先构建任务的 JobD…...

SpringBoot+uniapp 的 Champion 俱乐部微信小程序设计与实现,论文初版实现
摘要 本论文旨在设计并实现基于 SpringBoot 和 uniapp 的 Champion 俱乐部微信小程序,以满足俱乐部线上活动推广、会员管理、社交互动等需求。通过 SpringBoot 搭建后端服务,提供稳定高效的数据处理与业务逻辑支持;利用 uniapp 实现跨平台前…...

算法笔记2
1.字符串拼接最好用StringBuilder,不用String 2.创建List<>类型的数组并创建内存 List arr[] new ArrayList[26]; Arrays.setAll(arr, i -> new ArrayList<>()); 3.去掉首尾空格...
Web中间件--tomcat学习
Web中间件–tomcat Java虚拟机详解 什么是JAVA虚拟机 Java虚拟机是一个抽象的计算机,它可以执行Java字节码。Java虚拟机是Java平台的一部分,Java平台由Java语言、Java API和Java虚拟机组成。Java虚拟机的主要作用是将Java字节码转换为机器代码&#x…...
MySQL 索引底层结构揭秘:B-Tree 与 B+Tree 的区别与应用
文章目录 一、背景知识:什么是 B-Tree 和 BTree? B-Tree(平衡多路查找树) BTree(B-Tree 的变种) 二、结构对比:一张图看懂 三、为什么 MySQL InnoDB 选择 BTree? 1. 范围查询更快 2…...
vue3 daterange正则踩坑
<el-form-item label"空置时间" prop"vacantTime"> <el-date-picker v-model"form.vacantTime" type"daterange" start-placeholder"开始日期" end-placeholder"结束日期" clearable :editable"fal…...

VisualXML全新升级 | 新增数据库编辑功能
VisualXML是一个功能强大的网络总线设计工具,专注于简化汽车电子系统中复杂的网络数据设计操作。它支持多种主流总线网络格式的数据编辑(如DBC、LDF、ARXML、HEX等),并能够基于Excel表格的方式生成和转换多种数据库文件。由此&…...