我们继上一篇介绍如何计算启动时间以及工具的使用后,我们现在结合输入法项目通过TraceView来讲解优化过程。
/**
*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。
而如何开启线程同样也有学问: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() {
// 初始化
}
});
解决办法: 根据业务划分优先级,分别进行初始化。对启动不会使用的业务进行异步延迟初始化,避免影响启动速度。(并不是每一个组件的初始化以及操作都可以异步或延迟;是否可以取决组件的调用关系以及自己项目具体业务的需要。保证一个准则:可以异步的都异步,不可以异步的尽量延迟。让应用先启动,再操作。)
- 业务单例的初始化
其他耗时部分是一些业务单例的初始化,根据业务性质,将一些无必要启动时构造的可以进行异步延迟初始化
测试机器:LG-D858 Android 5.0.1
目前修改了以下点:
1. 将GoKeyboardDataProviderEmoji 中的DBHelper初始化进行优化,不在构造函数中开启数据库
2. 将热词SDK,积分墙SDK,进行延迟异步初始化。