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.rhkb.cn/news/94530.html

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

相关文章

Python入门【TCP建立连接的三次握手、 TCP断开连接的四次挥手、套接字编程实战、 TCP编程的实现、TCP双向持续通信】(二十七)

👏作者简介:大家好,我是爱敲代码的小王,CSDN博客博主,Python小白 📕系列专栏:python入门到实战、Python爬虫开发、Python办公自动化、Python数据分析、Python前后端开发 📧如果文章知识点有错误…

使用PDF文件入侵任何操作系统

提示:我们8月28号开学,所以我得快点更新了,不能拖了😥 文章目录 前言一、打开终端总结 前言 PDF文件被广泛应用于共享信息,电子邮件,网站或文档或存储系统的真实链接 它可以用于恶意软件的载体。 不要问我什么意思&am…

部署lawyer-llama

Git - Downloading PackageGit - Downloading PackageGit - Downloading Package 下载git,wget需要下载一下 (GNU Wget 1.21.4 for Windows), Windows中git bash完全可以替代原生的cmd,但是对于git bash会有一些Linu…

DevExpress WinForms数据编辑器组件,提供丰富的数据输入样式!(一)

DevExpress WinForms超过80个高影响力的WinForms编辑器和多用途控件,从屏蔽数据输入和内置数据验证到HTML格式化,DevExpress数据编辑库提供了无与伦比的数据编辑选项,包括用于独立数据编辑或用于容器控件(如Grid, TreeList和Ribbon)的单元格。…

【AI】《动手学-深度学习-PyTorch版》笔记(十八):卷积神经网络模型(LeNet、AlexNet、VGG、NiN)

AI学习目录汇总 1、LeNet 1.1 介绍 发布时间:1989年 模型目的:识别手写数字 1.2 网络结构 1.3 定义模型 1.3.1 相关函数原型 1)nn.Conv2d:卷积层 torch.nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, paddin

当众讲话培训的需求分析

标题:当众讲话培训的需求分析 摘要:当众讲话是现代社会中一项重要的技能,对于个人和职业发展都具有重要意义。然而,许多人面临着当众讲话的困难和挑战。本论文旨在分析当众讲话培训的需求,探讨为什么人们需要这种培训…

深入源码分析kubernetes informer机制(零)简单了解informer

[阅读指南] 基于kubernetes 1.27 stage版本 为了方便阅读,后续所有代码均省略了错误处理及与关注逻辑无关的部分。 文章目录 关于client-goInformer是什么为什么需要informerInformer工作流程后续分析计划 关于client-go client-go是kubernetes节点与服务端进行资源…

Ubuntu 20.04配置静态ip

ip配置文件 cd /etc/netplan配置 根据需求增加 # Let NetworkManager manage all devices on this system network:version: 2renderer: NetworkManager # 管理 不是必须ethernets:enp4s0: #网卡名dhcp4: no #关闭ipv4动态分配ip地址dhcp6: no #关闭ipv6动态分配…

python接口自动化测试框架2.0,让你像Postman一样编写测试用例,支持多环境切换、多业务依赖、数据库断言等

项目介绍 接口自动化测试项目2.0 软件架构 本框架主要是基于 Python unittest ddt HTMLTestRunner log excel mysql 企业微信通知 Jenkins 实现的接口自动化框架。 前言 公司突然要求你做自动化,但是没有代码基础不知道怎么做?或者有自动化…

React 高阶组件(HOC)

React 高阶组件(HOC) 高阶组件不是 React API 的一部分,而是一种用来复用组件逻辑而衍生出来的一种技术。 什么是高阶组件 高阶组件就是一个函数,且该函数接受一个组件作为参数,并返回一个新的组件。基本上,这是从 React 的组成…

Android学习之路(4) UI控件之文本框

本节给大家带来的UI控件是:TextView(文本框),用于显示文本的一个控件,另外声明一点,我不是翻译API文档,不会一个个属性的去扣,只学实际开发中常用的,有用的,大家遇到感觉到陌生的属性…

# 快速评估立功科技基于S32K324的TMS方案

文章目录 1.前言2.立功科技的TMS方案介绍2.1 介绍资料2.2 简要介绍 3.S32K3_TriMotor评估板测试3.1 环境搭建S32 Design Studio for S32 Platform 3.4安装RTD 2.0.0安装Freemaster 3.2 3.2 例程测试3.3 例程适配3.4 双核烧录3.5 测试 1.前言 最近和一些做汽车水泵/风机的客户交…

爬虫逆向实战(七)--猿人学第十六题

一、数据接口分析 主页地址:猿人学第十六题 1、抓包 通过抓包可以发现数据接口是api/match/16 2、判断是否有加密参数 请求参数是否加密? 通过查看“载荷”模块可以看出m是加密参数 请求头是否加密? 无响应是否加密? 无cook…

节点不连续伽辽金方法在求解线性和非线性平流方程中的一维实现(Matlab代码实现)

💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…

三维模型OSGB格式轻量化技术在大规模场景的加载和渲染的作用分析

三维模型OSGB格式轻量化技术在大规模场景的加载和渲染的作用分析 在移动设备上,大规模场景的加载和渲染是一个不容忽视的问题。对于OSGB格式轻量化处理来说,大规模场景的加载和渲染也是其中一项重要的任务。本文将重点分析OSGB格式轻量化处理在大规模场景…

如何实现客户自助服务?打造产品知识库

良好的客户服务始于自助服务。根据哈佛商业评论,81% 的客户在联系工作人员之前尝试自己解决问题。92% 的客户表示他们更喜欢使用产品知识库/帮助中心。 所以本文主要探讨了产品知识库是什么,有哪些优势以及如何创建。 产品知识库是什么 产品知识库是将…

Web网页浏览器远程访问jupyter notebook服务器【内网穿透】

文章目录 前言1. Python环境安装2. Jupyter 安装3. 启动Jupyter Notebook4. 远程访问4.1 安装配置cpolar内网穿透4.2 创建隧道映射本地端口 5. 固定公网地址 前言 Jupyter Notebook,它是一个交互式的数据科学和计算环境,支持多种编程语言,如…

无涯教程-Perl - times函数

描述 此函数返回一个四元素列表,为当前进程及其子进程提供用户,系统,子进程和子系统时间。 语法 以下是此函数的简单语法- times返回值 此函数返回ARRAY,($usertime,$systemtime,$childsystem,$childuser) 例 以下是显示其基本用法的示例代码- #!/usr/bin/perl -w($use…

day20 飞机大战射击游戏

有飞行物类 飞行 爆炸 的连环画, 飞行的背景图 , 子弹图, 还有游戏开始 暂停 结束 的画面图。 设计一个飞机大战的小游戏, 玩家用鼠标操作hero飞行机, 射出子弹杀死敌机,小蜜蜂。 敌机可以获得分数&…

五分钟搭建生鲜蔬果小程序

如今,随着移动互联网的快速发展,小程序已经成为众多企业和商家推广产品和服务的重要工具。而生鲜蔬果行业作为一个常见的消费领域,也开始逐渐转向小程序商城来进行销售和服务。那么,如何从零开始搭建一个生鲜蔬果小程序商城呢&…