《Android性能优化》一次失败的启动速度优化

news/2024/10/17 22:24:23/

正文

在优化APP启动之前,我们首先需要知道,APP启动时究竟发生了什么,才能有的放矢的优化。

APP的启动过程

APP的启动过程就是指,在手机屏幕上点击某个APP的图标,到APP的首页显示在用户面前的过程。
一般APP的启动过程分为一下几步:

  • Launcher通知AMS(ActivityManagerService)启动APP
  • AMS检查APP是否已经启动,如果已经启动,则直接唤醒即可。否则创建一个新的进程,AMS在新进程中创建一个ActivityThread对象,启动其中的main函数。
  • ActivityThread启动bindApplication,bindApplication通过反射创建APP中的Application
  • 启动之后通知AMS,AMS再通知APP启动xml中配置的Activity。
  • 首页Activity启动后调用onCreate方法,然后加载页面布局,布置屏幕,进行首桢绘制。

从APP的启动过程中看,程序员能优化的地方实际上非常少,主要就是Application的创建过程和首页Activity的绘制过程。

不过在优化之前,我们需要精确的知道APP的启动时间,以及各个方法执行的耗时

精确测量启动耗时

1.使用adb 命令获得启动耗时
例如,测量手机中默认浏览器的启动时长,在Android Studio的控制台运行如下指令即可,
adb shell am start -W com.android.browser/com.android.browser.BrowserActivity
其中com.android.browser和com.android.browser.BrowserActivity分别是浏览器的包名和指定浏览器Activity,实际开发中换成我们自己的包名和首页Activity即可。
运行结果如下

其中
ThisTime:最后一个activity的启动耗时
TotalTime:所有Activity的总启动耗时
WaitTime:AMS启动Activity的总耗时
使用adb命令获取的启动耗时,并不够绝对精确,不能为我们提供优化方向,只能大致反应一个APP的启动耗时。
2.使用埋点的方式获得方法的耗时
埋点的方式有很多中方式,因为埋点不是本文重点,这里只介绍一种最简单也最low的方式。例如,如果需要知道某个方法的执行时间,最简单的方式就是在方法的执行前记录一个时间点,然后在方法执行完毕后,再记录一个时间点,两个时间相减,就可以得出该方法的耗时。

        long time;time = SystemClock.currentThreadTimeMillis();initAMap();time = SystemClock.currentThreadTimeMillis() - time;

 

上述代码就可以得出 initAMap()的耗时了。

实际上,获取一个方法的精确执行耗时,还可以通过Android Studio中各种辅助工具,因为不是本文的重点,同时这些工具也有一定的学习成本,所以这里不再介绍,后续有时间再介绍。

启动优化

1.首页Activity设定不同的Theme
在APP开发中我们都会遇到启动页面白屏的问题,这主要就是,APP启动时会先创建一个空白的window导致的,这会让用户觉得app启动很慢,通过在AndroidManifest.xml中给首页Activity设定一个带有首页图片的主题,即可解决。具体代码,百度即可。需要注意的是,此种方式只能解决肉眼上的延迟,实际上APP的启动速度,并没有任何优化。
2.异步任务
我们公司的APP启动慢,主要是Application中大量的第三方和第一方框架在onCreate中初始化造成的,onCreate中的方法默认是在主线程中执行。既然如此,那就简单了,将这些初始化框架的方法放到子线程运行就可以。
实际执行后发现,app的启动速度确实奇迹般的快了不少,但是小范围上线后,就直接翻车了。
上线之后,后台监控显示部分手机的开屏页会出现空指针异常,调查之后发现,我们的APP开屏也会请求后台的热修复接口,并执行下载任务,在一部分手机,进入开屏页时,网络框架尚未初始化完毕,导致了网络操作出现空指针异常。
处理方式很简单,使用CountDownLatch。Application的onCreate方法是运行在主线程,如果子线程中初始化方法尚未执行完,主线程暂时阻塞不向后执行就可以了。
完整的代码如下:

    //cpu 核心数private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();//核心线程数private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));//最大非核心线程数private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;//空闲时间private static final int KEEP_ALIVE_SECONDS = 30;//线程池public static final Executor THREAD_POOL_EXECUTOR;private static final ThreadFactory sThreadFactory = new ThreadFactory() {private final AtomicInteger mCount = new AtomicInteger(1);public Thread newThread(Runnable r) {return new Thread(r, "LaunchTask #" + mCount.getAndIncrement());}};//非核心任务线程队列private static final BlockingQueue<Runnable> sPoolWorkQueue =new LinkedBlockingQueue<Runnable>(128);static {ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,sPoolWorkQueue, sThreadFactory);threadPoolExecutor.allowCoreThreadTimeOut(true);THREAD_POOL_EXECUTOR = threadPoolExecutor;}private CountDownLatch countDownLatch = new CountDownLatch(1);@Overridepublic void onCreate() {super.onCreate();THREAD_POOL_EXECUTOR.execute(new Runnable() {@Overridepublic void run() {try {//模拟耗时的初始化操作Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}initPush();}});THREAD_POOL_EXECUTOR.execute(new Runnable() {@Overridepublic void run() {initHttp();//initHttp执行完毕countDownLatch.countDown();}});THREAD_POOL_EXECUTOR.execute(new Runnable() {@Overridepublic void run() {initX5WebView();}});try {//在此处等待 initHttp执行完毕countDownLatch.await();} catch (InterruptedException e) {e.printStackTrace();}}

 3.其他优化技巧
上面已经说到了,APP启动慢的原因主要在于大量的框架需要初始化,处了异步初始化以外,还有一些其他的注意要点:

  • 一些重量级且不重要的框架的初始化尽量将其移出Application,可以在用到它的时候再做初始化,具体需要根据业务作出判断。
  • SharedPreferences的初始化可以提前到attachBaseContext中。
  • 延迟子进程的开启时机,因为子进程会共享主进程的cpu资源。

结尾

总得来说,APP的启动优化是一个优先级并不高的优化方向,相对于内存优化、CPU优化、电量优化、网络优化等等,它对用户的影响并不大,从我个人接触过的APP来看,《掘金》就是一个启动比较慢的APP,从点击图标到看到开屏图片,平均比其他APP要慢上0.5秒左右。所以,一般来说,如果没有特殊要求,APP优化要以其他方向的优化为主。


http://www.ppmy.cn/news/47244.html

相关文章

【数据分析之道-NumPy(三)】numpy切片与索引

文章目录 专栏导读1、前言2、NumPy数组切片2.1一维数组切片2.2多维数组切片 3、NumPy数组索引3.1一维数组索引3.2多维数组索引 4、NumPy数组高级索引4.1整数数组索引4.2布尔数组索引4.3数组索引 总结 专栏导读 ✍ 作者简介&#xff1a;i阿极&#xff0c;CSDN Python领域新星创作…

【一】MATLAB基础知识

【一】MATLAB基础知识 1 数值数据类型的分类 整型 无符号整数&#xff1a;无符号8位整数、无符号16位整数、无符号32位整数、 无符号64位整数。 带符号整数&#xff1a;带符号8位整数、带符号16位整数、带符号32位整数、 带符号64位整数。 无符号8位整数数据范围&#xff…

耗时半月,终于把牛客网上的软件测试面试八股文整理成了PDF合集(测试基础+linux+MySQL+接口测试+自动化测试+测试框架+jmeter测试+测试开发)

大家好&#xff0c;最近有不少小伙伴在后台留言&#xff0c;近期的面试越来越难了&#xff0c;要背的八股文越来越多了&#xff0c;考察得越来越细&#xff0c;越来越底层&#xff0c;明摆着就是想让我们徒手造航母嘛&#xff01;实在是太为难我们这些程序员了。 这不&#xf…

软件生存周期

软件生存周期 同任何事物一样&#xff0c;一个软件产品或软件系统也要经历孕育、诞生、成长、成熟、衰亡的许 多阶段&#xff0c;一般称为软件生存周期。把整个软件生存周期划分为若干阶段&#xff0c;每个阶段的任务相对 独立&#xff0c;而且比较简单&#xff0c;便于不同人员…

嵌入式Linux(7):字符设备驱动--申请设备号

文章目录 1、字符设备和杂项设备的区别2、注册字符类设备号的两个办法第一种&#xff1a;静态分配一个设备号第二种&#xff1a;动态分配注销设备号 写代码不带参数测试&#xff08;动态分配&#xff09;&#xff1a;带参数测试&#xff08;静态设置&#xff09;&#xff1a; 建…

C/C++编译器内存优化技术:内存优化关注程序对内存的访问和使用,以提高内存访问速度和减少内存占用。

目录标题 引言缓存优化数据局部性 数据对齐&#xff1a;优化数据结构的布局&#xff0c;以提高内存访问速度。内存池&#xff1a;为对象分配使用预先分配的内存池&#xff0c;以减少动态内存分配和释放的开销。垃圾收集优化&#xff1a;针对使用垃圾收集的语言&#xff0c;优化…

jsp有哪些内置对象?作用分别是什么?

JSP(Java Server Pages)中有以下九个内置对象&#xff1a; 1.request: 表示客户端的HTTP请求。可以使用它来获取客户端提交的表单数据、URL参数、HTTP头等信息。 <!DOCTYPE html> <html> <head><title>获取request对象信息</title> </head&…

运行时内存数据区之方法区(二)

方法区的演进细节 首先明确&#xff1a;只有HotSpot才有永久代。BEA JRockit、IBMJ9等来说&#xff0c;是不存在永久代的概念的。原则上如何实现方法区属于虚拟机实现细节&#xff0c;不受《]Va虚拟机规范》管束&#xff0c;并不要求统一。Hotspot中方法区的变化&#xff1a; …