Android 性能优化 ---- 启动优化

1、为什么要进行启动优化

一款应用的第一印象很重要,第一印象往往决定了用户的去留。打开一款应用,如果速度很快,很顺畅,那么很容易让人觉得这款应用背后的技术实力很强,用户潜意识中会对这款应用更加的信赖。

其次,网上也流行一种说法,就是8秒定律,意思是说,如果用户在打开一个页面,在8秒的时间内还没有打开,那么用户大概的会放弃掉,意味着一个用户的流失。从这里就可以看出,启动优化的重要性了。

2、启动的分类

2.1 冷启动

先来看看冷启动的流程图

从图中可以看出,APP启动的过程是:ActivityManagerProxy 通过IPC来调用AMS(ActivityManagerService),AMS通过IPC启动一个APP进程,ApplicationThread通过反射来创建Application并且绑定,最后通过ActivityThread来控制activity的生命周期,在相关页面的生命周期中通过ViewRootImpl来对view的实现。从而完成应用的启动。

2.2 热启动

热启动的速度是最快的,它就是进程从后台切换到前台的一个过程。

2.3 温启动

温启动只会重新走一遍页面的生命周期,但是对于进程,application不会重新在创建。

3、优化方向

上面介绍了启动的几种方式可以看出,我们针对启动优化,基本只是优化冷启动就可以了。但是从冷启动的启动流程中很多都是系统做的,我们没有办法操控。我们能做的,就是application的生命周期和activity的生命周期这部分,启动优化往往就是从这两块入手。

4、启动时间的测量方式

4.1 使用adb 命令方式(线下使用方便)

  1. adb shell am start -W 包名/包名+类名

ThisTime:最后一个activity的启动耗时

TotalTime:所有activity的启动耗时

WaitTime:AMS启动activity的总耗时

这里由于我直接进入到主界面,中间并没有SplashActivity,所有ThisTime 和 TotalTime的时间是一样的

优势:在线下使用方便,适合于跑线下的产品,和获取竞品的时间,然后比对

缺点:不能带到线上,获取的时间,只能说是一个大概时间,不是很严谨。

4.2 手动打点方式

通过System.currentTimeMillis()来打时间戳

缺点:很明显,对代码侵入性非常的大,如果说我想要打出每一个任务花费的时间,那么代码看起来就很恶心了

5、优雅获取方法耗时

5.1 AOP Aspect Oriented Programming 面向切面编程

AOP:通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。它的核心思想就是将应用程序中的业务逻辑处理部分同对其提供通用服务部分即“横切关注点”进行分离。

OOP:引入封装,继承,多态等概念来建立一种对象层次结构,它允许开发者定义纵向的关系,但并不适合横向的关系。

可以说AOP是OOP的一种补充和完善。

5.2 aspectj的使用

AspectJ是一个面向切面编程的框架,是对java的扩展且兼容java,AspectJ定义了AOP语法,它有一个专门的编译器来生成遵守java字节编码规范的Class文件。

在项目的根目录的build.gradle添加依赖:

  1. dependencies {
  2. classpath 'com.android.tools.build:gradle:3.5.2'
  3. classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.0'
  4. // NOTE: Do not place your application dependencies here; they belong
  5. // in the individual module build.gradle files
  6. }

在app下的build.gradle添加依赖

  1. apply plugin: 'android-aspectjx'

在dependencies中添加

  1. implementation 'org.aspectj:aspectjrt:1.9.4'

然后创建一个类

  1. package com.noahedu.myapplication.aspectj;
  2. import android.util.Log;
  3. import org.aspectj.lang.ProceedingJoinPoint;
  4. import org.aspectj.lang.Signature;
  5. import org.aspectj.lang.annotation.Around;
  6. import org.aspectj.lang.annotation.Aspect;
  7. /**
  8. * @Description: //@Before 在切入点之前运行
  9. * // @After("")
  10. * //@Around 在切入点前后都运行
  11. * @Author: huangjialin
  12. * @CreateDate: 2020/7/10 14:07
  13. */
  14. @Aspect
  15. public class MyApplicationAspectj {
  16. @Around("call(* com.noahedu.myapplication.MyApplication.**(..))")
  17. public void getTime(ProceedingJoinPoint joinPoint){
  18. Signature signature = joinPoint.getSignature();
  19. String name = signature.getName();
  20. long time = System.currentTimeMillis();
  21. try {
  22. joinPoint.proceed();
  23. } catch (Throwable throwable) {
  24. throwable.printStackTrace();
  25. }
  26. Log.e("MyApplicationAspectj " ,(name + " cost " + (System.currentTimeMillis() - time)));
  27. }
  28. }

这样我们运行的时候,就会直接在logcat中打印出application中的onCreate方法中所有调用方法的耗时情况了

  1. 2020-07-10 14:22:27.151 1619-1619/? E/MyApplicationAspectj: taskOne cost 150
  2. 2020-07-10 14:22:29.203 1619-1619/com.noahedu.myapplication E/MyApplicationAspectj: taskTwo cost 2052
  3. 2020-07-10 14:22:29.554 1619-1619/com.noahedu.myapplication E/MyApplicationAspectj: taskThrid cost 351
  4. 2020-07-10 14:22:30.556 1619-1619/com.noahedu.myapplication E/MyApplicationAspectj: taskFour cost 1001

这样我们几乎没有碰Application中的任何代码,也就够得出各个方法的耗时,几乎对代码无侵入。

6、启动优化的工具选择

6.1 traceview

TraceView是Android SDK中内置的一个工具,他可以加载trace文件,以图形化的形式展示相应代码的执行时间,次数及调用栈,便于我们分析。

  1. Debug.startMethodTracing("MyApplication");
  2. //TODO
  3. Debug.stopMethodTracing();

运行项目就可以我们的SD卡中找到对应的trace文件了,如果是Android studio可以直接在右下角找到

DeviceFileExporer -->sdcard --> Android -- > data -->files --->自己项目的包名

然后双击即可查看文件了

优点:使用简单,图形形式展示所执行的时间,调用栈等。

缺点:会影响到我们优化的方向,由于是图形化展示,也是比较占用CPU资源的,所以得到的时间往往是比实际的要大。

7、启动器

上面介绍了多了几个获取任务执行时间的方式和工具,那么当我们知道某个方法耗时了,我们该怎么处理呢?

  1. package com.noahedu.myapplication;
  2. import android.app.Application;
  3. import android.os.Debug;
  4. import android.util.Log;
  5. /**
  6. * @Description: java类作用描述
  7. * @Author: huangjialin
  8. * @CreateDate: 2020/7/10 9:59
  9. */
  10. public class MyApplication extends Application {
  11. @Override
  12. public void onCreate() {
  13. super.onCreate();
  14. Debug.startMethodTracing("MyApplication");
  15. taskOne();
  16. taskTwo();
  17. taskThrid();
  18. taskFour();
  19. Debug.stopMethodTracing();
  20. }
  21. public void taskOne(){
  22. try {
  23. Thread.sleep(150);
  24. } catch (InterruptedException e) {
  25. e.printStackTrace();
  26. }
  27. }
  28. public void taskTwo(){
  29. try {
  30. Thread.sleep(2050);
  31. } catch (InterruptedException e) {
  32. e.printStackTrace();
  33. }
  34. }
  35. public void taskThrid(){
  36. try {
  37. Thread.sleep(350);
  38. } catch (InterruptedException e) {
  39. e.printStackTrace();
  40. }
  41. }
  42. public void taskFour(){
  43. try {
  44. Thread.sleep(1000);
  45. } catch (InterruptedException e) {
  46. e.printStackTrace();
  47. }
  48. }
  49. }

现在application的onCreate方法中有几个任务,各个耗时是不一样的。可能很多同学就会说了,异步处理啊,开线程,放到线程池中,或者创建一个IntentService来执行。那么我们就要考虑几个问题了

第一:异步处理,如果在某个页面需要用到某个SDK,但是又没有初始化完成呢?

第二:假如说taskTwo需要taskOne的某个返回值呢?怎么保证taskOne在taskTwo之前执行完毕呢?

第三:开线程,开多少个线程呢?多了会造成资源浪费,少了资源又没有合理的利用。

我个人觉得针对于启动优化,在Application中的onCreate()进行初始化任务操作,我们首先需要对这些任务进行一个优先级划分,针对于那些优先级高的任务,我们可以优先进行处理,对于那些优先级较低的,我们可以适当的延迟进行加载。

其次有很多同学喜欢把那些优先级较低的任务进行延迟加载,比如new Handler().postDelayed(),这种我觉得是非常不可取的,假如说放在postDelayed中的任务耗时2s,延迟1s进行处理,那么在执行2s任务的过程中,有用户进行操作,那岂不是很卡吗,很明显,这是指标不治本的。

7.1 启动器的思想

针对上面说的几个痛点,怎么在处理上面的几个痛点,又能保证代码的可维护性呢?换句话说就是一个新人不需要理解整个过程,直接就可以开干呢?那么,启动器来了。

启动器核心思想:充分利用CPU多核,自动梳理任务顺序

7.2 启动器的原理

1、任务全部封装成Task对象,传入到集合中。

2、根据所有的任务依赖关系,形成一个有向无环图,然后通过拓扑排序排列出任务的执行流程

3、通过CountDownLatch来控制某一个任务是否执行完毕才进行下一步。

4、线程池创建核心线程的数量,由手机的核数量决定的。

7.3启动器使用方式

7.4启动器核心代码

进行任务排序

  1. package com.noahedu.launchertool.launchstarter.sort;
  2. import com.noahedu.launchertool.launchstarter.task.Task;
  3. import com.noahedu.launchertool.launchstarter.utils.DispatcherLog;
  4. import java.util.ArrayList;
  5. import java.util.List;
  6. import java.util.Set;
  7. import androidx.annotation.NonNull;
  8. import androidx.collection.ArraySet;
  9. public class TaskSortUtil {
  10. private static List<Task> sNewTasksHigh = new ArrayList<>();// 高优先级的Task
  11. /**
  12. * 任务的有向无环图的拓扑排序
  13. *
  14. * @return
  15. */
  16. public static synchronized List<Task> getSortResult(List<Task> originTasks,
  17. List<Class<? extends Task>> clsLaunchTasks) {
  18. long makeTime = System.currentTimeMillis();
  19. Set<Integer> dependSet = new ArraySet<>();
  20. Graph graph = new Graph(originTasks.size());
  21. for (int i = 0; i < originTasks.size(); i++) {
  22. Task task = originTasks.get(i);
  23. if (task.isSend() || task.dependsOn() == null || task.dependsOn().size() == 0) {
  24. continue;
  25. }
  26. for (Class cls : task.dependsOn()) {
  27. int indexOfDepend = getIndexOfTask(originTasks, clsLaunchTasks, cls);
  28. if (indexOfDepend < 0) {
  29. throw new IllegalStateException(task.getClass().getSimpleName() +
  30. " depends on " + cls.getSimpleName() + " can not be found in task list ");
  31. }
  32. dependSet.add(indexOfDepend);
  33. graph.addEdge(indexOfDepend, i);
  34. }
  35. }
  36. List<Integer> indexList = graph.topologicalSort();
  37. List<Task> newTasksAll = getResultTasks(originTasks, dependSet, indexList);
  38. DispatcherLog.i("task analyse cost makeTime " + (System.currentTimeMillis() - makeTime));
  39. printAllTaskName(newTasksAll);
  40. return newTasksAll;
  41. }
  42. @NonNull
  43. private static List<Task> getResultTasks(List<Task> originTasks,
  44. Set<Integer> dependSet, List<Integer> indexList) {
  45. List<Task> newTasksAll = new ArrayList<>(originTasks.size());
  46. List<Task> newTasksDepended = new ArrayList<>();// 被别人依赖的
  47. List<Task> newTasksWithOutDepend = new ArrayList<>();// 没有依赖的
  48. List<Task> newTasksRunAsSoon = new ArrayList<>();// 需要提升自己优先级的,先执行(这个先是相对于没有依赖的先)
  49. for (int index : indexList) {
  50. if (dependSet.contains(index)) {
  51. newTasksDepended.add(originTasks.get(index));
  52. } else {
  53. Task task = originTasks.get(index);
  54. if (task.needRunAsSoon()) {
  55. newTasksRunAsSoon.add(task);
  56. } else {
  57. newTasksWithOutDepend.add(task);
  58. }
  59. }
  60. }
  61. // 顺序:被别人依赖的————》需要提升自己优先级的————》需要被等待的————》没有依赖的
  62. sNewTasksHigh.addAll(newTasksDepended);
  63. sNewTasksHigh.addAll(newTasksRunAsSoon);
  64. newTasksAll.addAll(sNewTasksHigh);
  65. newTasksAll.addAll(newTasksWithOutDepend);
  66. return newTasksAll;
  67. }
  68. private static void printAllTaskName(List<Task> newTasksAll) {
  69. if (true) {
  70. return;
  71. }
  72. for (Task task : newTasksAll) {
  73. DispatcherLog.i(task.getClass().getSimpleName());
  74. }
  75. }
  76. public static List<Task> getTasksHigh() {
  77. return sNewTasksHigh;
  78. }
  79. /**
  80. * 获取任务在任务列表中的index
  81. *
  82. * @param originTasks
  83. * @return
  84. */
  85. private static int getIndexOfTask(List<Task> originTasks,
  86. List<Class<? extends Task>> clsLaunchTasks, Class cls) {
  87. int index = clsLaunchTasks.indexOf(cls);
  88. if (index >= 0) {
  89. return index;
  90. }
  91. // 仅仅是保护性代码
  92. final int size = originTasks.size();
  93. for (int i = 0; i < size; i++) {
  94. if (cls.getSimpleName().equals(originTasks.get(i).getClass().getSimpleName())) {
  95. return i;
  96. }
  97. }
  98. return index;
  99. }
  100. }

执行任务代码

  1. package com.noahedu.launchertool.launchstarter.task;
  2. import android.os.Looper;
  3. import android.os.Process;
  4. import com.noahedu.launchertool.launchstarter.TaskDispatcher;
  5. import com.noahedu.launchertool.launchstarter.stat.TaskStat;
  6. import com.noahedu.launchertool.launchstarter.utils.DispatcherLog;
  7. /**
  8. * 任务真正执行的地方
  9. */
  10. public class DispatchRunnable implements Runnable {
  11. private Task mTask;
  12. private TaskDispatcher mTaskDispatcher;
  13. public DispatchRunnable(Task task) {
  14. this.mTask = task;
  15. }
  16. public DispatchRunnable(Task task,TaskDispatcher dispatcher) {
  17. this.mTask = task;
  18. this.mTaskDispatcher = dispatcher;
  19. }
  20. @Override
  21. public void run() {
  22. DispatcherLog.i(mTask.getClass().getSimpleName()
  23. + " begin run" + " Situation " + TaskStat.getCurrentSituation());
  24. Process.setThreadPriority(mTask.priority());
  25. long startTime = System.currentTimeMillis();
  26. mTask.setWaiting(true);
  27. mTask.waitToSatisfy();
  28. long waitTime = System.currentTimeMillis() - startTime;
  29. startTime = System.currentTimeMillis();
  30. // 执行Task
  31. mTask.setRunning(true);
  32. mTask.run();
  33. // 执行Task的尾部任务
  34. Runnable tailRunnable = mTask.getTailRunnable();
  35. if (tailRunnable != null) {
  36. tailRunnable.run();
  37. }
  38. if (!mTask.needCall() || !mTask.runOnMainThread()) {
  39. printTaskLog(startTime, waitTime);
  40. TaskStat.markTaskDone();
  41. mTask.setFinished(true);
  42. if(mTaskDispatcher != null){
  43. mTaskDispatcher.satisfyChildren(mTask);
  44. mTaskDispatcher.markTaskDone(mTask);
  45. }
  46. DispatcherLog.i(mTask.getClass().getSimpleName() + " finish");
  47. }
  48. }
  49. /**
  50. * 打印出来Task执行的日志
  51. *
  52. * @param startTime
  53. * @param waitTime
  54. */
  55. private void printTaskLog(long startTime, long waitTime) {
  56. long runTime = System.currentTimeMillis() - startTime;
  57. if (DispatcherLog.isDebug()) {
  58. DispatcherLog.i(mTask.getClass().getSimpleName() + " wait " + waitTime + " run "
  59. + runTime + " isMain " + (Looper.getMainLooper() == Looper.myLooper())
  60. + " needWait " + (mTask.needWait() || (Looper.getMainLooper() == Looper.myLooper()))
  61. + " ThreadId " + Thread.currentThread().getId()
  62. + " ThreadName " + Thread.currentThread().getName()
  63. + " Situation " + TaskStat.getCurrentSituation()
  64. );
  65. }
  66. }
  67. }

基本核心代码就是上面这几个,完整的代码会在后面的demo给出

8、其他优化方案

8.1 对延迟任务进行分批初始化,使用IdleHandler特性,进行空闲执行 (适合优先级不是很高,不急于初始化的第三方SDK)

IdleHandler:IdleHandler 可以用来提升性能,主要用在我们希望能够在当前线程消息队列空闲时做些事情(譬如 UI 线程在显示完成后,如果线程空闲我们就可以提前准备其他内容)的情况下,不过最好不要做耗时操作。简单来说就是,looper对象有空的时候就会执行IdleHandler中的任务。

前面说过,在application的任务进行优先级划分,那么如果优先级低的任务,我们是不是可以不再application的onCreate中进行初始化呢?是否可以放到activity中进行呢?如果可以在activity中,那么在activity那个阶段适合呢?其实在onCreate(),onResume()都是可以的。当然我们也可以使用view中的getViewTreeObserver().addOnPreDrawListener进行监听,这个事件是视图将要绘制的时候会回调该方法。我们可以在回调的方法中将要初始化的SDK放大IdleHandler中。

8.2 提前加载SP 可以放到multidex之前加载,利用此阶段的CPU

SharedPreferences,以键值对的形式进行数据的保存的,会一次性加载到内存中,所以我们可以考虑那个阶段的CPU相对来说比较空闲。如可以放到multidex之前加载,充分利用此阶段的CPU进行

demo地址:[https://github.com/343661629/startOptimization]

Android 性能优化 ---- 启动优化的更多相关文章

  1. 关于android性能,内存优化

    转:http://www.starming.com/index.php?action=plugin&v=wave&tpl=union&ac=viewgrouppost& ...

  2. android 性能分析、优化

    .主要介绍了一些分析工具,比如GT.ITest等http://www.jianshu.com/p/8b77d394b2a6 .详细介绍啦android平台常见性能优化工具http://blog.csd ...

  3. Android性能之启动时间篇

    背景介绍 Android用户也许会经常碰到以下的问题: 1)应用后台开着,手机很快没电了--应用耗电大: 2)首次/非首次启动应用,进入应用特别慢--应用启动慢: 3)应用使用过程中,越来越卡--CP ...

  4. Android内核开发:系统启动速度优化-Android OS启动优化(转)

    Android系统的启动优化主要分为三大部分: (1) Bootloader优化 (2) Linux Kernel的剪裁与优化 (3) Android OS部分的剪裁与优化 本文重点关注Android ...

  5. 关于android应用--内存的优化

    以下内容为转载自网上,然后自己加工贴合到一块的: 原文地址:http://www.cnblogs.com/frydsh/archive/2012/12/09/2810601.html http://w ...

  6. Android性能优化(一)之启动加速35%

    一.前言 随着项目版本的迭代,App的性能问题会逐渐暴露出来,而好的用户体验与性能表现紧密相关,从本篇文章开始,我将开启一个Android应用性能优化的专题,从理论到实战,从入门到深挖,手把手将性能优 ...

  7. Android性能优化-App启动优化

    原文地址:https://developer.android.com/topic/performance/launch-time.html#common 通常用户期望app响应和加载速度越快越好.一个 ...

  8. Android性能优化系列之App启动优化

    Android性能优化系列之布局优化 Android性能优化系列之内存优化 Android性能优化系列之apk瘦身 应用的启动速度缓慢是我们在开发过程中常常会遇到的问题,比方启动缓慢导致的黑屏.白屏问 ...

  9. Android性能优化之App应用启动分析与优化

    前言: 昨晚新版本终于发布了,但是还是记得有测试反馈app启动好长时间也没进入app主页,所以今天准备加个班总结一下App启动那些事! app的启动方式: 1.)冷启动      当启动应用时,后台没 ...

随机推荐

  1. MySQL连接查询驱动表被驱动表以及性能优化

    准备我们需要的表结构和数据 两张表 studnet(学生)表和score(成绩)表, 创建表的SQL语句如下 CREATE TABLE `student` ( `id` int(11) NOT NUL ...

  2. (八)slf4j+logback 的配置与使用

    logback的配置看这篇:https://www.cnblogs.com/lvchengda/p/13054457.html 使用 @Slf4j 1)安装插件lombok 在eclipse/myec ...

  3. 【Laravel】 常用的artisan命令

    全局篇 查看artisan命令php artisanphp artisan list 查看某个帮助命令php artisan help make:model 查看laravel版本php artisa ...

  4. jquery-form详解

    jQuery-Form 概观 jQuery表单插件允许您轻松而不显眼地升级HTML表单以使用AJAX.主要方法ajaxForm和ajaxSubmit从表单元素收集信息以确定如何管理提交过程.这两种方法 ...

  5. 警告Establishing SSL connection without server's identity verification is not recommended

    [本文版权归微信公众号"代码艺术"(ID:onblog)所有,若是转载请务必保留本段原创声明,违者必究.若是文章有不足之处,欢迎关注微信公众号私信与我进行交流!] SpringBo ...

  6. Elasticsearch修改分词器以及自定义分词器

    Elasticsearch修改分词器以及自定义分词器 参考博客:https://blog.csdn.net/shuimofengyang/article/details/88973597

  7. 分词搜索 sphinx+php+mysql

    sphinx3.1.1的安装与使用 下载sphinx3.1.1 wget http://sphinxsearch.com/files/sphinx-3.1.1-612d99f-linux-amd64. ...

  8. C#数据结构与算法系列(十八):冒泡排序算法(BubbleSort)

    1.介绍 冒泡排序的基本思想就是:通过对待排序序列从前向后(从下标较小的元素开始),依次比较相邻元素的值,若发现逆序则交换,使值较大的元素逐渐从前移向后部,就像水底的气泡一样逐渐向上冒泡. 因为排序的 ...

  9. 113资讯网——NGINX 502 Bad Gateway——解决方案

    NGINX 502 Bad Gateway错误出现的原因较多,对于后端连接PHP服务的场景下,常见的原因有php服务响应超时,php进程不足等引起的一类服务器错误. 发生原因: PHP FastCGI ...

  10. DTD约束和Schema约束

    DTD约束 什么是DTD? DTD(Document Type Definition),文档类型定义,用来约束XML文档.规定XML文档中元素的名称,子元素的名称及顺序,元素的属性等. DTD约束长什 ...