启动速度优化总结(下)

1、背景

我们继上一篇介绍如何计算启动时间以及工具的使用后,我们现在结合输入法项目通过TraceView来讲解优化过程。

2、分析

  • Multidex的使用
    通过使用TraceView分析trace文件,发现Multidex调用的方法比较耗时。
    MultiDex工作在主线程,而Dex的提取与DexOpt的过程都是耗时的操作,所以ANR的问题是必然存在;
    拆分出来的Dex越多,对应ANR的几率也就越高。所以Multidex不光会拖慢启动速度,而且严重的时候会导致应用ANR。
    值得一提的是Android 5.0 以及更高版本使用ART,所以不存在由于分包的原因导致首次启动比较慢的问题,
    ART 在应用安装时执行预编译,扫描 classesN.dex 文件,并将它们编译成单个 .oat 文件,供 Android 设备执行。
    如果您的 minSdkVersion 为 21 或更高值,则不需要添加分包支持库。
    对于Android 5.0以下的设备由于分包导致的启动比较慢的问题,只能通过减小包体减少方法数,从而使得在apk中的dex文件不需要进行分包来解决问题。
  • 数据库初始化
    通过分析trace文件,以下初始化方法调用耗时较大:
    1.GoKeyboardDataProviderEmoji.onCreate()
    2.AdSdkApi.initSDK()
    其实它们有个共性,就是都使用到了数据库。 在GoKeyboardDataProviderEmoji.onCreate()和AdSdk.init()都会创建一个DatabaseHelper对象,在DatabaseHelper 的构造函数中都会调用getWritableDatabase(),我们看下getWritableDatabase()这个方法的注释:
/**
    *Create and/or open a database that will be used for reading and writing.
    * The first time this is called, the database will be opened and
    * {@link #onCreate}, {@link #onUpgrade} and/or {@link #onOpen} will be
    * called.
    *
    * 

class="caution">Database upgrade may take a long time, you * should not call this method from the application main thread, including * from {@link android.content.ContentProvider#onCreate ContentProvider.onCreate()}. * ... */ public SQLiteDatabase getWritableDatabase() { synchronized (this) { return getDatabaseLocked(true); } }

通过注释一目了然,因为调用getWritableDatabase()会打开数据库并调用onCreate,onUpgrade,这会是个耗时操作,不推荐在应用主线程或者ContentProvider.onCreate()中调用。

我们在看看SQLiteOpenHelper构造函数:

/**
    * Create a helper object to create, open, and/or manage a database.
    * This method always returns very quickly.  The database is not actually
    * created or opened until one of {@link #getWritableDatabase} or
    * {@link #getReadableDatabase} is called.
    *.....
    */
    public SQLiteOpenHelper(Context context, String name, CursorFactory factory, int version) {
        this(context, name, factory, version, null);
    }

SQLiteOpenHelper构造函数会很快执行完毕,因为并没有真正创建数据库,只有在调用getWritableDatabase或者getReadableDatabase时才会打开数据库

我们看看SQLiteOpenHelper类注释:

/**
 * A helper class to manage database creation and version management.
*....
 *  * 

This class makes it easy for {@link android.content.ContentProvider} * implementations to defer opening and upgrading the database until first use, * to avoid blocking application startup with long-running database upgrades. * ..... */ public abstract class SQLiteOpenHelper { }

其实SQLiteOpenHelper就是用来实现ContentProvider延迟打开数据库使用,目的就是避免阻塞应用首次启动。
解决办法: 在DatabaseHelper 构造函数中只调用父类方法,避免调用getWritableDatabase与getReadableDatabase。

  • SDK初始化
    许多SDK都需要在application中进行初始化,这也是启动慢的原因之一。
    以热词SDK为例,通过分析trace文件,跟踪NavigationApi.init()方法执行可以发现主要耗时代码是在以下两部分:
    1.DrawUtils.resetDensity,这里会使用反射获取状态栏,执行耗时较长
    2.NPManager.getInstance,这里初始化管理类会创建线程池
    其实除了热词SDK,还有像积分墙SDK等第三方SDK。
    因为热词sdk并不会在启动时用到,所以将导航栏初始化进行异步延迟初始化,避免影响启动速度。

而如何开启线程同样也有学问:Thread、ThreadPoolExecutor、AsyncTask、HandlerThread、IntentService等都各有利弊;
例如通常情况下ThreadPoolExecutor比Thread更加高效、优势明显,但是特定场景下单个时间点的表现Thread会比ThreadPoolExecutor好:同样的创建对象,ThreadPoolExecutor的开销明显比Thread大;
正确的开启线程也不能包治百病,例如执行网络请求会创建线程池,而在Application中正确的创建线程池势必也会降低启动速度;因此延迟操作也必不可少。

这里推荐一种方式:

/**
     * 异步初始化第三方SDK
     */
    public void asyInitThirdSDK() {
        new Thread() {
            @Override
            public void run() {
                super.run();
                // 设置线程优先级;不与主线程抢资源
                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                // 初始化第三方SDK
            }
        }.start();
    }

延迟初始化可以在application中做,也可以在首屏做。在首屏渲染出来后再进行加载:

getWindow().getDecorView().post(new Runnable() {
            @Override
            public void run() {
                // 初始化
            }
        });

解决办法: 根据业务划分优先级,分别进行初始化。对启动不会使用的业务进行异步延迟初始化,避免影响启动速度。(并不是每一个组件的初始化以及操作都可以异步或延迟;是否可以取决组件的调用关系以及自己项目具体业务的需要。保证一个准则:可以异步的都异步,不可以异步的尽量延迟。让应用先启动,再操作。)

- 业务单例的初始化
其他耗时部分是一些业务单例的初始化,根据业务性质,将一些无必要启动时构造的可以进行异步延迟初始化

3、总结

测试机器:LG-D858 Android 5.0.1

目前修改了以下点:
1. 将GoKeyboardDataProviderEmoji 中的DBHelper初始化进行优化,不在构造函数中开启数据库
2. 将热词SDK,积分墙SDK,进行延迟异步初始化。

可以看到平均提升接近2s,后续持续优化补充。
启动速度优化总结(下)_第1张图片

你可能感兴趣的:(Android-性能优化,Android,启动优化)