> 文章列表 > 《Android性能优化》一次失败的启动速度优化

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

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

正文

在优化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优化要以其他方向的优化为主。

免费职业培训