美文网首页Android性能优化与实践
《Android性能优化》一次失败的启动速度优化

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

作者: 林栩link | 来源:发表于2019-06-21 22:16 被阅读0次

前言

有一天晚上12点之后,还在加班的我,收到一条销售部主管发来的信息,大致内容,竞争对手的投标书中提到了,对方的APP上架了很多冷门的应用商店,希望我们也上架对应的应用商店。还有就是竞争对手的APP启动速度要比我们的APP快,需要在下次演示时超过对方。
看完之后,我不禁感叹,现在的竞争环境都这么恶劣吗?感叹归感叹,很快,APP启动速度的优化提上日程了。
本篇除了讲解如何优化APP启动速度,也同时讲述一下当时翻车的悲惨经历。

正文

在优化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);

    @Override
    public void onCreate() {
        super.onCreate();
        THREAD_POOL_EXECUTOR.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    //模拟耗时的初始化操作
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                initPush();
            }
        });
        THREAD_POOL_EXECUTOR.execute(new Runnable() {
            @Override
            public void run() {
                initHttp();
                //initHttp执行完毕
                countDownLatch.countDown();
            }
        });
        THREAD_POOL_EXECUTOR.execute(new Runnable() {
            @Override
            public 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优化要以其他方向的优化为主。

相关文章

网友评论

    本文标题:《Android性能优化》一次失败的启动速度优化

    本文链接:https://www.haomeiwen.com/subject/bxojqctx.html