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 SharedPreference
    Log.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 bugly
    Log.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() {
        @Override
        public void run() {
            initSQLite();
        }
    });
    service.submit(new Runnable() {
        @Override
        public 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() {
       @Override
       public void run() {
           initSQLite();
       }
   });
   service.submit(new Runnable() {
       @Override
       public 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 源码分析实战

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

学无止境·运维高阶⑤(LVS-DR 群集 配置Nginx负载均衡)

LVS-DR 群集 && 配置Nginx负载均衡 一、LVS-DR 群集1、相关配置环境2、在RS上配置并启动脚本2.1相关脚本2.2 启动脚本,另一台RS同样步骤 3、LVS-DR模式配置脚本4、测试 二、Nginx负载均衡1、安装Nginx并关闭相应设置2、向主机 node2,node3 写入内…

Spring Clould 消息队列 - RabbitMQ

视频地址:微服务(SpringCloudRabbitMQDockerRedis搜索分布式) 初识MQ-同步通讯的优缺点(P61,P62) 同步和异步通讯 微服务间通讯有同步和异步两种方式: 同步通讯:就像打电话&…

比ChatGPT更强的星火大模型V2版本发布!

初体验 测试PPT生成 结果: 达到了我的预期,只需要微调就可以直接交付,这点比ChatGPT要强很多. 测试文档问答 结果: 这点很新颖,现在类似这种文档问答的AI平台收费都贵的离谱,星火不但免费支持而且效果也…

TiDB v7.1.0 跨业务系统多租户解决方案

本文介绍了 TiDB 数据库的资源管控技术,并通过业务测试验证了效果。资源管控技术旨在解决多业务共用一个集群时的资源隔离和负载问题,通过资源组概念,可以限制不同业务的计算和 I/O 资源,实现资源隔离和优先级调度,提高…

优思学院|六西格玛黑带的9大任务和7大技能

六西格玛黑带是六西格玛管理中最为重要的一个角色,他们专职(也可以是兼职)从事六西格玛改进项目,是成功完成六西格玛项目的技术骨干成员,是六西格玛组织的核心力量。他们的努力程度决定着六西格玛管理的成败。 六西格玛…

新榜 | CityWalk本地生活商业价值洞察报告

如果说现在有人问,最新的网络热词是什么? “CityWalk”,这可能是大多数人的答案。 近段时间,“CityWalk”刷屏了各种社交媒体,给网友们带来了一场“城市漫步”之旅。 脱离群体狂欢,这个在社交媒体引发热议的词汇背后又…

HCIP VLAN实验

VLAN实验 拓扑图配置和分析分析配置LSW1LSW2R1 测试dhcp获取ipICMP测试 拓扑图 配置和分析 分析 从题目来看,因为 pc 1 3都是vlan2而且还是不同网段,pc 2 4 5 6在同一网段,所以可以将pc 1 2 5 4 6分在一个网段 pc4不通5 6 ,那就…

【sgDragSize】自定义拖拽修改DIV尺寸组件,适用于窗体大小调整

核心原理就是在四条边、四个顶点加上透明的div,给不同方向提供按下移动鼠标监听 ,对应计算宽度高度、坐标变化 特性: 支持设置拖拽的最小宽度、最小高度、最大宽度、最大高度可以双击某一条边,最大化对应方向的尺寸;再…

JVM——配置常用参数,GC调优策略

文章目录 JVM 配置常用参数Java内存区域常见配置参数概览堆参数回收器参数项目中常用配置常用组合 常用 GC 调优策略GC 调优原则GC 调优目的GC 调优策略 JVM 配置常用参数 Java内存区域常见配置参数概览堆参数;回收器参数;项目中常用配置;常…

嵌入式Linux开发实操(九):CAN接口开发

前言: CAN网络在汽车中的使用可以说相当广泛。而CAN网络需要的收发器最常用的就是NXP 的TJA1042: CAN网络:

嵌入式编译FFmpeg6.0版本并且组合x264

下载直通车:我用的是6.0版本的 1.准备编译: 2.进入ffmpeg源码目录,修改Makefile,添加编译选项: CFLAGS -fPIC 不加会报错 3.使用命令直接编译 ./configure --cross-prefix/home/xxx/bin/arm-linux-gnueabihf- --enable-cross-compile --targ…

一次Linux中的木马病毒解决经历(6379端口---newinit.sh)

病毒入侵解决方案 情景 最近几天一直CPU100%,也没有注意看到了以为正常的服务调用,直到腾讯给发了邮件警告说我的服务器正在入侵其他服务器的6379端口,我就是正常的使用不可能去入侵别人的系统的,这是违法的. 排查 既然入侵6379端口,就怀疑是通过我的Redis服务进入的我的系统…

​《乡村振兴战略下传统村落文化旅游设计 》在2023年畅销榜排名465位

​《乡村振兴战略下传统村落文化旅游设计 》在2023年畅销榜排名465位

[Docker精进篇] Docker镜像构建和实践 (三)

前言: Docker镜像构建的作用是将应用程序及其依赖打包到一个可移植、自包含的镜像中,以便在不同环境中快速、可靠地部署和运行应用程序。 文章目录 Docker镜像构建1️⃣是什么?2️⃣为什么?3️⃣镜像构建一、用现有容器构建新镜像…

k8s简介及虚拟机快速搭建k8s集群

文章目录 1、k8s简介1.1、部署方式的变迁1.2、定义1.3、Kubernetes提供的功能 2、虚拟机快速搭建k8s集群2.1、虚拟机配置(centos7 2G内存2个处理器)2.2、基础环境准备2.3、docker安装(易踩坑)2.4、安装k8s组件2.5、master节点部署…

浅谈5G技术会给视频监控行业带来的一些变革情况

5G是第五代移动通信技术,能够提供更高的带宽和更快的传输速度,这将为视频技术的发展带来大量机会。随着5G技术的逐步普及与商用,人们将能够享受到更加流畅的高清视频体验,并且5G技术还拥有更低的延迟和更高的网络容量。这些优势不…

操作符详解上(非常详细)

目录 二进制介绍二进制2进制转10进制10进制转2进制数字2进制转8进制和16进制2进制转8进制2进制转16进制 原码、反码、补码移位操作符左移操作符右移操作符 位操作符:&、|、^逗号表达式 二进制介绍 在初学计算机时我们常常会听到2进制、8进制、10进制、16进制……

专访阿里云席明贤,视频云如何运用大模型与小模型来破茧升级2.0

不久前,LiveVideoStack与阿里云视频云负责人席明贤(花名右贤)展开一场深度的对话,一个是圈内专业的社区媒体,一个是20年的IT老兵,双方有交集、有碰撞、有火花。 面对风云变幻的内外环境,阿里云…

9.利用matlab完成 泰勒级数展开 和 符号表达式傅里叶变换和反变换 (matlab程序)

1.简述 matlab之傅里叶变换和逆变换 首先生成一个方波(或者其他组合波形),然后对这个信号做傅里叶变换,拆解到频域,可以看到这个信号是由哪些频率的信号叠加而来。 然后把频域信号,用傅里叶逆变换恢复到时…

linux 命令- systemctl

systemctl 参数说明 1、使用语法 用法:systemctl [OPTIONS…] {COMMAND} … 2 、参数说明 参数参数说明start立刻启动后面接的unitstop立刻关闭后面接的unitrestart立刻关闭后启动后面接的unit,亦即执行stop再start的意思reload不关闭后面接的unit的…