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

news2024/11/24 12:00:40

作者: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 源码分析实战

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

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

相关文章

Android控件【ProgressBar】

文章目录 常用属性项目结构主要代码 常用属性 max:进度条的最大值progress:进度条已完成进度值indeterminate:如果设置成true,则进度条不精确显示进度style"?android:attr/progressBarStyleHorizontal":水…

ubuntu 22.04 LTS 在 llvm release/17.x 分支上编译 cookbook llvm example Chapter 02

下载 llvm-cookbook example: $ git clone https://github.com/elongbug/llvm-cookbook.git 开发环境: 从apt source 安装llvm、clang和lldb: $ apt install llvm $ apt install llvm-dev $ apt install clang $ apt install lldb $ cd llvm-cookboo…

c语言生成.exe文件的方法

一、在Windows shell下使用gcc命令 1、需要下载获取GCC安装包 官网:MinGW-w64 - for 32 and 64 bit Windows - Browse Files at SourceForge.net 其他下载: 文件文件大小:48.0 M|https://wwnb.lanzoul.com/iIsq815lktfchttps://download.c

归并排序算法细节演示

【样例输入】 5 11 8 34 2 9 【样例输出】 2 8 9 11 34 #include<iostream> using namespace std; int n;//n个数 int data[100]; int tmp[100]; int nums 1; void ss(){cout<<endl<<"-----------------"<<endl; for(int i1;i<n;i){cou…

创建一个简单的HTML Viewer应用程序

使用wxPython和内嵌浏览器来创建一个简单的HTML Viewer应用程序。 在本篇文章中&#xff0c;我们将使用Python和wxPython模块来创建一个简单的HTML Viewer应用程序。这个应用程序可以让用户输入HTML内容&#xff0c;并在内嵌浏览器中显示该内容的效果。 准备工作 在开始之前…

Shell学习笔记之基础部分

Shell基础&#xff1a; 查看操作系统支持的shell&#xff1a; [rootrhel9 ansible]# cat /etc/shells /bin/sh /bin/bash /usr/bin/sh /usr/bin/bashShell的基本元素&#xff1a; 声明&#xff1a;声明用哪个命令解释器来解释并执行当前脚本文件中的语句&#xff0c;一般写的…

Cross-Site Scripting

文章目录 反射型xss(get)反射型xss(post)存储型xssDOM型xssDOM型xss-xxss-盲打xss-过滤xss之htmlspecialcharsxss之href输出xss之js输出 反射型xss(get) <script>alert("123")</script>修改maxlength的值 反射型xss(post) 账号admin密码123456直接登录 …

tauri-react:快速开发跨平台软件的架子,支持自定义头部UI拖拽移动和窗口阴影效果

tauri-react 一个使用 taurireacttsantd 开发跨平台软件的模板&#xff0c;支持窗口头部自定义和窗口阴影&#xff0c;不用再自己做适配了&#xff0c;拿来即用&#xff0c;非常 nice。而且已经封装好了 tauri 的 http 请求工具&#xff0c;省去很多弯路。 开原地址&#xff…

抖音短视频SEO矩阵系统源码开发

一、概述 抖音短视频SEO矩阵系统源码是一项综合技术&#xff0c;旨在帮助用户在抖音平台上创建并优化短视频内容。本文将详细介绍该系统的技术架构、核心代码、实现过程以及优化建议&#xff0c;以便读者更好地理解并应用这项技术。 二、技术架构 抖音短视频SEO矩阵系统采用前…

ai之美:探索写真照片软件的创造力

小青&#xff1a;嘿&#xff0c;小华&#xff0c;你知道最近ai艺术写真非常流行吗&#xff1f; 小华&#xff1a;真的吗&#xff1f;我还不知道呢。告诉我更多细节吧&#xff01; 小青&#xff1a;好的&#xff0c;ai艺术写真是指使用人工智能技术将照片转化为艺术作品的过程…

飞天使-k8s简单搭建(编写中)

文章目录 k8s概念安装部署无密钥配置与hosts与关闭swap开启ipv4转发安装前启用脚本开启ip_vs安装指定版本docker 安装kubeadm kubectl kubelet k8s单节点部署参考链接地址 k8s概念 K8sMaster : 管理K8sNode的。 K8sNode:具有docker环境 和k8s组件&#xff08;kubelet、k-prox…

C#小轮子:MiniExcel,快速操作Excel

文章目录 前言环境安装功能测试普通读写读新建Excel表格完全一致测试&#xff1a;成功大小写测试&#xff1a;严格大小写别名读测试&#xff1a;成功 写普通写别名写内容追加更新模板写 其它功能xlsx和CSV互转 前言 Excel的操作是我们最常用的操作&#xff0c;Excel相当于一个…

【markdown】使用 Mermaid 制作的图表和图形的一些示例

Mermaid是一种用于绘制流程图,状态图,序列图和甘特图的文本标记语言。 Mermaid的主要特点包括: 基于文本 - 使用类似Markdown的语法来描述图表,之后可以转换为SVG图表。支持流程图 - 使用图形节点和箭头来表示流程步骤和流转方向。支持状态图 - 可以绘制状态之间的转换条件。支…

【使用Zookeeper当作注册中心】自己定制负载均衡常见策略

自己定制负载均衡常见策略 一、前言随机&#xff08;Random&#xff09;策略的实现轮询&#xff08;Round Robin&#xff09;策略的实现哈希&#xff08;Hash&#xff09;策略 一、前言 大伙肯定知道&#xff0c;在分布式开发中&#xff0c;目前使用较多的注册中心有以下几个&…

Centos 8 网卡connect: Network is unreachable错误解决办法

现象1、ifconfig没有ens160配置 [testlocalhost ~]$ ifconfig lo: flags73<UP,LOOPBACK,RUNNING> mtu 65536 inet 127.0.0.1 netmask 255.0.0.0 inet6 ::1 prefixlen 128 scopeid 0x10<host> loop txqueuelen 1000 (Local Loopba…

css学习4(背景)

1、CSS中&#xff0c;颜色值通常以以下方式定义: 十六进制 - 如&#xff1a;"#ff0000"RGB - 如&#xff1a;"rgb(255,0,0)"颜色名称 - 如&#xff1a;"red" 2、background-image 属性描述了元素的背景图像. 默认情况下&#xff0c;背景图像进…

SaaS当然是一门好生意了啊

&#xff08;1&#xff09;SaaS关键特征 1、应用架构&#xff1a;多租户 2、部署&#xff1a;公有IaaS部署 3、商业模式&#xff1a;年度订阅续费 &#xff08;2&#xff09;用户云注册、用户在线付费 用户云注册、用户在线付费&#xff0c;站在中国乙方利益视角&#xff0c;多…

【Linux命令详解 | ssh命令】 ssh命令用于远程登录到其他计算机,实现安全的远程管理

文章标题 简介一&#xff0c;参数列表二&#xff0c;使用介绍1. 连接远程服务器2. 使用SSH密钥登录2.1 生成密钥对2.2 将公钥复制到远程服务器 3. 端口转发3.1 本地端口转发3.2 远程端口转发 4. X11转发5. 文件传输与远程命令执行5.1 文件传输5.1.1 从本地向远程传输文件5.1.2 …

WPF国际化的实现方法(WpfExtensions.Xaml)

https://blog.csdn.net/eyupaopao/article/details/120090431 resx资源文件实现 resx资源文件&#xff0c;实现的过程比第一种复杂&#xff0c;但resx文件本身编辑比较简单&#xff0c;维护起来比较方便。需要用到的框架&#xff1a;WpfExtensions.Xaml 为每种语言添加.resx资…

AI 绘画Stable Diffusion 研究(十)sd图生图功能详解-精美二维码的制作

免责声明: 本案例所用安装包免费提供&#xff0c;无任何盈利目的。 大家好&#xff0c;我是风雨无阻。 为了让大家更直观的了解图生图功能&#xff0c;明白图生图功能到底是干嘛的&#xff0c;能做什么事情&#xff1f;今天我们继续介绍图生图的实用案例-精美二维码的制作。 对…