Android启动速度优化01
一. 启动框架
其实启动框架就是一个任务调度系统,是手淘启动的“大管家”。
管家要做的事情就是把它们的关系梳理得明明白白,有条不紊,合理安排位置、调度时间,同时提升硬件资源的利用率。
二. 启动框架的思路
总结下来无非就是两点:
- 如何保证时序。
- 如何让任务合理的并发执行。
三. 框架的整体实现流程
- 实现任务的拓扑排序.
- 同步执行排好序的任务
- 加入异步操作有效利用CPU资源
- 框架的初始化问题
有向无环图[拓扑排序]
https://gitee.com/luisliuyi/android-optimize-network01.git
四. 任务关系图
image.png五. 入度表
image.png六. 任务依赖表
image.png七. Startup.java任务的基本封装
public interface Dispatcher {
/**
* 返回是否在主线程执行
*/
boolean callCreateOnMainThread();
/**
* 让每个任务都可以指定自己执行在哪个线程沲
*
*/
Executor executor();
/**
* 指定线程的优先级
*/
int getThreadPriority();
/**
* 等待
*/
void toWait();
/**
* 有父任务执行完毕
* 计数器-1
*/
void toNotify();
/**
* 是否需要主线程等待该任务执行完成
* callCreateOnMainThread()方法返回false才有意义
*/
boolean waitOnMainThread();
}
public interface Startup<T> extends Dispatcher {
/**
* 给程序员写任务逻辑使用
*/
T create(Context context);
/**
* 本任务依赖哪些任务
*
*/
List<Class<? extends Startup<?>>> dependencies();
/**
* 入度数
*/
int getDependenciesCount();
}
public abstract class AndroidStartup<T> implements Startup<T> {
//入度数初始化
private CountDownLatch mWaitCountDown = new CountDownLatch(getDependenciesCount());
@Override
public List<Class<? extends Startup<?>>> dependencies() {
return null;
}
@Override
public int getDependenciesCount() {
List<Class<? extends Startup<?>>> dependencies = dependencies();
return dependencies == null ? 0 : dependencies.size();
}
@Override
public void toWait() {
try {
mWaitCountDown.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public void toNotify() {
mWaitCountDown.countDown();
}
/**
* 默认使用io线程沲
*/
@Override
public Executor executor() {
return ExecutorManager.ioExecutor;
}
/**
* 默认最高优先级
*/
@Override
public int getThreadPriority() {
return Process.THREAD_PRIORITY_DEFAULT;
}
/**
* 默认在子线程执行
*/
@Override
public boolean callCreateOnMainThread() {
return false;
}
/**
* 默认不需要主线程等待自己执行完成
*/
@Override
public boolean waitOnMainThread() {
return false;
}
}
public class Task1 extends AndroidStartup<Void> {
@Nullable
@Override
public Void create(Context context) {
LogUtils.log(" Task1:学习Java基础");
SystemClock.sleep(3_000);
LogUtils.log(" Task1:掌握Java基础");
return null;
}
//执行此任务需要依赖哪些任务
@Override
public List<Class<? extends Startup<?>>> dependencies() {
return super.dependencies();
}
}
public class Task2 extends AndroidStartup<Void> {
static List<Class<? extends Startup<?>>> depends;
static {
depends = new ArrayList<>();
depends.add(Task1.class);
}
@Override
public Void create(Context context) {
LogUtils.log(" Task2:学习Socket");
SystemClock.sleep(800);
LogUtils.log(" Task2:掌握Socket");
return null;
}
@Override
public List<Class<? extends Startup<?>>> dependencies() {
return depends;
}
}
public class Task3 extends AndroidStartup<Void> {
static List<Class<? extends Startup<?>>> depends;
static {
depends = new ArrayList<>();
depends.add(Task1.class);
}
@Override
public Void create(Context context) {
LogUtils.log(" Task3:学习设计模式");
SystemClock.sleep(2_000);
LogUtils.log(" Task3:掌握设计模式");
return null;
}
// 执行此任务需要依赖哪些任务
@Override
public List<Class<? extends Startup<?>>> dependencies() {
return depends;
}
}
public class Task4 extends AndroidStartup<Void> {
static List<Class<? extends Startup<?>>> depends;
static {
depends = new ArrayList<>();
depends.add(Task2.class);
}
@Override
public Void create(Context context) {
LogUtils.log( " Task4:学习Http");
SystemClock.sleep(1_000);
LogUtils.log( " Task4:掌握Http");
return null;
}
@Override
public List<Class<? extends Startup<?>>> dependencies() {
return depends;
}
}
public class Task5 extends AndroidStartup<Void> {
static List<Class<? extends Startup<?>>> depends;
static {
depends = new ArrayList<>();
depends.add(Task3.class);
depends.add(Task4.class);
}
@Override
public Void create(Context context) {
String t = Looper.myLooper() == Looper.getMainLooper()
? "主线程: " : "子线程: ";
LogUtils.log(t + " Task5:学习OkHttp");
SystemClock.sleep(500);
LogUtils.log(t + " Task5:掌握OkHttp");
return null;
}
//执行此任务需要依赖哪些任务
@Override
public List<Class<? extends Startup<?>>> dependencies() {
return depends;
}
}
八. TopologySort.java对任务进行拓扑排序
public class TopologySort {
public static StartupSortStore sort(List<? extends Startup<?>> startupList) {
//入度表
Map<Class<? extends Startup>, Integer> inDegreeMap = new HashMap<>();
//0度表
Deque<Class<? extends Startup>> zeroDeque = new ArrayDeque<>();
//任务表
Map<Class<? extends Startup>, Startup<?>> startupMap = new HashMap<>();
//任务依赖表
Map<Class<? extends Startup>, List<Class<? extends Startup>>> startupChildrenMap = new HashMap<>();
//1.找出图中入度数为0的节点 填表的过程
for (Startup<?> startup : startupList) {
startupMap.put(startup.getClass(), startup);
//记录每个任务的入度数(依赖的任务数)
int dependenciesCount = startup.getDependenciesCount();
inDegreeMap.put(startup.getClass(), dependenciesCount);
//记录入度数(依赖的任务数)为0的任务
if (dependenciesCount == 0) {
zeroDeque.offer(startup.getClass());
} else {
//遍历本任务的依赖任务,生成任务依赖表
for (Class<? extends Startup<?>> parent : startup.dependencies()) {
List<Class<? extends Startup>> children = startupChildrenMap.get(parent);
if (children == null) {
children = new ArrayList<>();
//记录这个父任务的所有子任务
startupChildrenMap.put(parent, children);
}
children.add(startup.getClass());
}
}
}
//2.删除图中入度为0的这些顶点,并更新全图,,最后完成排序
List<Startup<?>> result = new ArrayList<>();
//处理入度为0的任务
while (!zeroDeque.isEmpty()) {
Class<? extends Startup> cls = zeroDeque.poll();
Startup<?> startup = startupMap.get(cls);
result.add(startup);
//删除此入度为0的任务
if (startupChildrenMap.containsKey(cls)) {
List<Class<? extends Startup>> childStartup = startupChildrenMap.get(cls);
for (Class<? extends Startup> childCls : childStartup) {
Integer num = inDegreeMap.get(childCls);
inDegreeMap.put(childCls, num - 1);
if (num - 1 == 0) {
zeroDeque.offer(childCls);
}
}
}
}
return new StartupSortStore(result, startupMap, startupChildrenMap);
}
}
九. StartupSortStore.java用于保存排序结果
public class StartupSortStore {
//所有的任务
List<Startup<?>> result;
Map<Class<? extends Startup>, Startup<?>> startupMap;
//当前任务的子任务
Map<Class<? extends Startup>, List<Class<? extends Startup>>> startupChildrenMap;
public StartupSortStore(List<Startup<?>> result, Map<Class<? extends Startup>, Startup<?>> startupMap, Map<Class<? extends Startup>, List<Class<? extends Startup>>> startupChildrenMap) {
this.result = result;
this.startupMap = startupMap;
this.startupChildrenMap = startupChildrenMap;
}
public List<Startup<?>> getResult() {
return result;
}
public void setResult(List<Startup<?>> result) {
this.result = result;
}
public Map<Class<? extends Startup>, Startup<?>> getStartupMap() {
return startupMap;
}
public void setStartupMap(Map<Class<? extends Startup>, Startup<?>> startupMap) {
this.startupMap = startupMap;
}
public Map<Class<? extends Startup>, List<Class<? extends Startup>>> getStartupChildrenMap() {
return startupChildrenMap;
}
public void setStartupChildrenMap(Map<Class<? extends Startup>, List<Class<? extends Startup>>> startupChildrenMap) {
this.startupChildrenMap = startupChildrenMap;
}
}
十. StartupCacheManager.java用于保存任务的执行结果
/**
* 缓存执行完成的任务的结果
*/
public class StartupCacheManager {
//用于缓存每一个任务的执行结果 Result用来给ConcurrentHashMap防null
private ConcurrentHashMap<Class<? extends Startup>, Result> mInitializedComponents =
new ConcurrentHashMap();
private static StartupCacheManager mInstance;
private StartupCacheManager() {
}
public static StartupCacheManager getInstance() {
if (mInstance == null) {
synchronized (StartupCacheManager.class) {
if (mInstance == null) {
mInstance = new StartupCacheManager();
}
}
}
return mInstance;
}
/**
* save result of initialized component.
*/
public void saveInitializedComponent(Class<? extends Startup> zClass, Result result) {
mInitializedComponents.put(zClass, result);
}
/**
* check initialized.
*/
public boolean hadInitialized(Class<? extends Startup> zClass) {
return mInitializedComponents.containsKey(zClass);
}
public <T> Result<T> obtainInitializedResult(Class<? extends Startup<T>> zClass) {
return mInitializedComponents.get(zClass);
}
public void remove(Class<? extends Startup> zClass) {
mInitializedComponents.remove(zClass);
}
public void clear() {
mInitializedComponents.clear();
}
}
十一. StartupManager.java执行所有已经排好序的任务
public class StartupManager {
private Context context;
private List<Startup<?>> startupList;
private StartupSortStore startupSortStore;
private CountDownLatch awaitCountDownLatch;
public StartupManager(Context context, List<Startup<?>> startupList, CountDownLatch awaitCountDownLatch) {
this.context = context;
this.startupList = startupList;
this.awaitCountDownLatch = awaitCountDownLatch;
}
public StartupManager start() {
if (Looper.myLooper() != Looper.getMainLooper()) {
throw new RuntimeException("请在主线程调用!");
}
startupSortStore = TopologySort.sort(startupList);
for (Startup<?> startup : startupSortStore.getResult()) {
StartupRunnable startupRunnable = new StartupRunnable(context, startup, this);
if (startup.callCreateOnMainThread()) {
startupRunnable.run();
} else {
startup.executor().execute(startupRunnable);
}
}
return this;
}
public void notifyChildren(Startup<?> startup) {
if (!startup.callCreateOnMainThread() &&
startup.waitOnMainThread()) {
awaitCountDownLatch.countDown();
}
//获得已经完成的当前任务的所有子任务
if (startupSortStore
.getStartupChildrenMap().containsKey(startup.getClass())) {
List<Class<? extends Startup>> childStartupCls = startupSortStore
.getStartupChildrenMap().get(startup.getClass());
for (Class<? extends Startup> cls : childStartupCls) {
//通知子任务 startup父任务已完成
Startup<?> childStartup = startupSortStore.getStartupMap().get(cls);
childStartup.toNotify();
}
}
}
public void await() {
try {
awaitCountDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//基于分阶段处理任务,不能用单例
public static class Builder {
private List<Startup<?>> startupList = new ArrayList<>();
public Builder addStartup(Startup<?> startup) {
startupList.add(startup);
return this;
}
public Builder addAllStartup(List<Startup<?>> startups) {
startupList.addAll(startups);
return this;
}
public StartupManager build(Context context) {
//记录有多少个在子线程执行,又需要主线程等待的任务
AtomicInteger needAwaitCount = new AtomicInteger();
for (Startup<?> startup : startupList) {
//记录有多少个在子线程执行,又需要主线程等待的任务
if (!startup.callCreateOnMainThread() &&
startup.waitOnMainThread()) {
needAwaitCount.incrementAndGet();//i++
}
}
//根据任务数新建一个闭锁
CountDownLatch awaitCountDownLatch = new CountDownLatch(needAwaitCount.get());
return new StartupManager(context, startupList, awaitCountDownLatch);
}
}
}
十二. ExecutorManager进行线程沲的管理
public class ExecutorManager {
public static ThreadPoolExecutor cpuExecutor;
public static ExecutorService ioExecutor;
public static Executor mainExecutor;
//获得CPU核心数
private static int CPU_COUNT = Runtime.getRuntime().availableProcessors();
private static int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 5));
private static int MAX_POOL_SIZE = CORE_POOL_SIZE;
private static long KEEP_ALIVE_TIME = 5L;
static {
cpuExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE, MAX_POOL_SIZE,
KEEP_ALIVE_TIME, TimeUnit.SECONDS,
new LinkedBlockingDeque<Runnable>(),
Executors.defaultThreadFactory());
cpuExecutor.allowCoreThreadTimeOut(true);
ioExecutor = Executors.newCachedThreadPool(Executors.defaultThreadFactory());
mainExecutor = new Executor() {
Handler handler = new Handler(Looper.getMainLooper());
@Override
public void execute(Runnable command) {
handler.post(command);
}
};
}
}
十三. 框架的初始化问题
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
new StartupManager.Builder()
.addStartup(new Task2())
.addStartup(new Task4())
.addStartup(new Task5())
.addStartup(new Task3())
.addStartup(new Task1())
.build(this)
.start().await();
}
}
可用方案
APT,字节码插桩,利用ContentProvider
面试题LeakCanary 为什么不需要在Application中手动初始化?
<provider
android:name=".startup.provider.StartupProvider"
android:authorities="${applicationId}.android_startup"
android:exported="false">
<meta-data
android:name="com.luisliuyi.demo.optimize.startup.tasks.Task5"
android:value="android.startup" />
</provider>
public class StartupProvider extends ContentProvider {
@Override
public boolean onCreate() {
try {
List<Startup<?>> startups = StartupInitializer
.discoverAndInitializ(getContext(), getClass().getName());
new StartupManager.Builder()
.addAllStartup(startups)
.build(getContext())
.start()
.await();
} catch (Exception e) {
e.printStackTrace();
}
return true;
}
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
return null;
}
@Nullable
@Override
public String getType(@NonNull Uri uri) {
return null;
}
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
return null;
}
@Override
public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
return 0;
}
@Override
public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
return 0;
}
}
public class StartupInitializer {
public static String META_VALUE = "android.startup";
public static List<Startup<?>> discoverAndInitializ(Context context,
String providerName) throws Exception {
//用来保存有向无环图 任务的结构图
Map<Class<? extends Startup>, Startup<?>> startups = new HashMap<>();
//获得manifest contentProvider中的meta-data
ComponentName provider = new ComponentName(context, providerName);
ProviderInfo providerInfo = context.getPackageManager().getProviderInfo(provider,
PackageManager.GET_META_DATA);
//得到manifest中配置的任务
for (String key : providerInfo.metaData.keySet()) {
String value = providerInfo.metaData.getString(key);
if (TextUtils.equals(META_VALUE, value)) {
Class<?> clazz = Class.forName(key);//"com.example.appstartup_step4.tasks.Task5"
if (Startup.class.isAssignableFrom(clazz)) {//clazz是Startup的子类就返回true
doInitialize((Startup<?>) clazz.newInstance(), startups);
}
}
}
List<Startup<?>> result = new ArrayList<>(startups.values());
return result;
}
private static void doInitialize(Startup<?> startup,
Map<Class<? extends Startup>, Startup<?>> startups) throws Exception {
//避免重复 不能使用List
startups.put(startup.getClass(), startup);
if (startup.getDependenciesCount() != 0) {
//遍历父任务
for (Class<? extends Startup<?>> dependency : startup.dependencies()) {
doInitialize(dependency.newInstance(), startups);
}
}
}
}
十四. 运行结果
image.png十五. 代码地址
https://gitee.com/luisliuyi/android-optimize-startup.git
十六. 推荐中小项目可用框架
https://github.com/idisfkj/android-startup/blob/master/README-ch.md
Android启动速度优化01
启动流程
①点击桌面App图标,Launcher进程采用Binder IPC向system_server进程发起 startActivity请求;
②system_server进程接收到请求后,向zygote进程发送创建进程的请求;
③Zygote进程fork出新的子进程,即App进程;
④App进程,通过Binder IPC向sytem_server进程发起attachApplication请求;
⑤system_server进程在收到请求后,进行一系列准备工作后,再通过binder IPC 向App进程发送scheduleLaunchActivity请求;
⑥App进程的binder线程(ApplicationThread)在收到请求后,通过handler向主 线程发送LAUNCH_ACTIVITY消息;
⑦主线程在收到Message后,通过反射机制创建目标Activity,并回调 Activity.onCreate()等方法。
⑧到此,App便正式启动,开始进入Activity生命周期,执行完 onCreate/onStart/onResume方法,UI渲染结束后便可以看到App的主界面。
启动状态
应用有三种启动状态,每种状态都会影响应用向用户显示所需的时间:冷启动、温启动与热启动。
-
冷启动
冷启动是指应用从头开始启动:系统进程在冷启动后才创建应用进程。发生冷启动的情况包括应用自设备 启动后或系统终止应用后首次启动。 -
热启动
在热启动中,系统的所有工作就是将 Activity 带到前台。只要应用的所有 Activity 仍驻留在内存中, 应用就不必重复执行对象初始化、布局加载和绘制。 -
温启动
温启动包含了在冷启动期间发生的部分操作;同时,它的开销要比热启动高。有许多潜在状态可视为温启 动。例如:
1.用户在退出应用后又重新启动应用。进程可能未被销毁,继续运行,但应用需要执行 onCreate() 从 头开始重新创建 Activity
2.系统将应用从内存中释放,然后用户又重新启动它。进程和 Activity 需要重启,但传递到 onCreate() 的已保存的实例savedInstanceState对于完成此任务有一定助益
启动耗时统计
-
系统日志统计
在 Android 4.4(API 级别 19)及更高版本中,logcat 包含一个输出行,其中包含名为 Displayed 的值。 此值代表从启动进程到在屏幕上完成对应 Activity 的绘制所用的时间 -
adb命令统计
adb shell am start-S-W [packageName]/[activityName image.png
adb命令启动应用,一般会输入三个值:ThisTime、TotalTime与WaitTime。
1.WaitTime:包括前一个应用Activitypause的时间和新应用启动的时间;
2.ThisTime:表示一连串启动Activity的最后一个Activity的启动耗时;
3.TotalTime:表示新应用启动的耗时,包括新进程的启动和Activity的启动,但不包括前一个应用Activitypause
的耗时。
CPUProfile
image.pngStrictMode
StrictMode是一个开发人员工具,它可以检测出我们可能无意中做的事情,并将它们提请我们注意,以便我 们能够修复它们。 StrictMode最常用于捕获应用程序主线程上的意外磁盘或网络访问。帮助我们让磁盘和网络操作远离主线程, 可以使应用程序更加平滑、响应更快
启动黑白屏
当系统加载并启动 App 时,需要耗费相应的时间,这样会造成用户会感觉到当点击 App 图标时会有 “延迟” 现象,
为了解决这一问题,Google 的做法是在 App 创建的过程中,先展示一个空白页面,让用户体会到点击图标之后立
马就有响应。
如果你的application或activity启动的过程太慢,导致系统的BackgroundWindow没有及时被替换,就会出现启动
时白屏或黑屏的情况(取决于Theme主题是Dark还是Light)。
消除启动时的黑/白屏问题,大部分App都采用自己在Theme中设置背景图的方式来解决。
<style name="AppTheme.Launcher">
<item name="android:windowBackground">@drawable/bg</item>
</style>
<activity android:name=".activity.SplashActivity" android:screenOrientation="portrait" android:theme="@style/AppTheme.Launcher">
<intent-filter>
<action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
然后在Activity的onCreate方法,把Activity设置回原来的主题。
@Override protected void onCreate(Bundle savedInstanceState) {
//替换为原来的主题,在onCreate之前调用 setTheme(R.style.AppTheme);
super.onCreate(savedInstanceState);
}
这么做,只是提高启动的用户体验。并不能做到真正的加快启动速度。
启动优化相关
- 合理的使用异步初始化、延迟初始化、懒加载机制。
- 启动过程避免耗时操作,如数据库 I/O操作不要放在主线程执行。
- 类加载优化:提前异步执行类加载。
- 合理使用IdleHandler进行延迟初始化。
- 简化布局
网友评论