一个应用App的启动速度能够影响用户的首次体验,启动速度较慢(感官上)的应用可能导致用户再次开启App的意图下降,或者卸载放弃该应用程序。本文会通过以下几个方面来介绍应用启动的相关指标和优化,提供应用的启动速度。
整体文章思路如下:
启动优化
通常来说,启动方式分为两种:冷启动和热启动。
两者之间的特点如下:
冷热启动时间的计算命令:
adb shell am start -W [packageName]/[packageName.XxxActivity]
冷启动
热启动
可以看到两者时间相差比较大。
根据该命令基本可以看出一个应用的启动速度了,从冷启动热启动的相关关系,当我们需要优化启动速度的时候,优化冷启动速度即可。
但是该命令我们只是大概知道应用的启动速度,但并不知道我们的应用具体哪个位置耗时,影响启动速度,后续我会介绍如何获取启动具体耗时时间。
常规获取时间方法无非就是在方法执行前记录下时间,在方法执行完毕后记录时间,两者时间之差就是该方法执行的时间,封装一个基础类如下:
public class LaunchTimer {
private static final String TAG = "LaunchTimer";
private static long sTime;
public static void startRecord() {
sTime = System.currentTimeMillis();
}
public static void endRecord() {
long cost = System.currentTimeMillis() - sTime;
NLog.i(TAG, "执行耗时:%s", cost);
}
}
使用方式如下,可以直观的看出createController方法执行的时间
createController耗时
这样已经很直观了,可以具体到该方法的执行时间,如果要继续分析则对该方法内部继续执行该代码即可。但是这里有一个问题如果要知道10个或者更多方法的执行时间,这个方法看起来是可以,但写起来过于繁琐,且不符合程序员的习惯,关于这种场景后面会介绍如何处理。
@Override
public void onCreate(final Bundle icicle) {
setTheme(R.style.BrowserTheme);
Intent intent = getIntent();
NLog.i(LOGTAG,"onCreate");
super.onCreate(icicle);
//开始记录,且该方法可以设置文件大小和路径
Debug.startMethodTracing("browser.trace");
Controller controller=createController();
mController = controller;
getWindow().getDecorView().setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN|View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
controller.handleThirdPartyIntent(intent);
//结束记录
Debug.stopMethodTracing();
}
如上可以在目录下可以生成如下文件
/sdcard/Android/data/com.xxx.xx.browser/files/browser.trace
导出改文件,通过Android Studio的profile打开改文件
traceview
1处可以看出有多少线程。
2处可以看出具体方法的耗时。
3处有两个选项:
requestPermission();
ExecutorService service = Executors.newSingleThreadExecutor(new NamedThreadFactory("Controller"));
Future<Boolean> future = service.submit(new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
try {
asyncInit();
} catch (Exception e) {
return false;
}
return true;
}
});
requestPermission()方法执行在main线程中,因此我们可以把其放在Controller0线程中执行,从而减少main线程的的时间
ExecutorService service = Executors.newSingleThreadExecutor(new NamedThreadFactory("Controller"));
Future<Boolean> future = service.submit(new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
try {
requestPermission();
asyncInit();
} catch (Exception e) {
return false;
}
return true;
}
});
经测试发现无问题,且对比此时的trace文件发现修改前后main线程时间相对来说减少很多。
在代码的开始位置加上tag
TraceCompat.beginSection("AppOnCreate");
然后指定位置结束
TraceCompat.endSection();
即可以抓取到整个应用在此过程的相关信息,例如在onCreate方法中添加上述两行代码,执行相关python命令:
python systrace.py -b 32768 -t 10 -a com.xxx.xxx.browser -o browser.html sched gfx view wm am app
操作相关应用,即可以抓取整个过程的相关信息:
systrace
即可以看到添加的tag“AppOnCreate”,对应的时间信息:
AspectJ实际上是对AOP编程思想的一个实践,当然,除了AspectJ以外,还有很多其它的AOP实现,例如ASMDex,但目前最好、最方便的,依然是AspectJ。
AspectJ的使用如下:
根目录gradle下引用:
classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.0'
app目录gradle文件下引用:
implementation 'org.aspectj:aspectjrt:1.8.+'
此两处引用完成之后,就是代码编写:
package com.xx.xxx.browser.aspect;
import android.util.Log;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
@Aspect
public class IntercepLifeCycleAOP {
//获取该Activity下的所有on开头的方法耗时
@Around("execution(* com.xxx.xxx.BrowserActivity.on**(..))")
public Object getTime(ProceedingJoinPoint joinPoint) {
Object proceed = null;
long start = System.currentTimeMillis();
try {
proceed = joinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
long end = System.currentTimeMillis();
Log.d("IntercepLifeCycleAOP", joinPoint.getSignature().getName() + ":执行时间:" + (end - start));
return proceed;
}
}
引入之后结果如下:
AspectJ
可以看到具体方法的耗时。
采用注解方式,其中Around 需要有一定的AspectJ相关的语法
1.采用线程加载一些资源,比如sdk初始化,配置信息拉取等相关资源。线程,线程池,IntentServices均可以,配合延迟效果更好。
2.当我们采用线程之间的可能会存在各线程之间相互等待依赖等相关问题,资源A线程必须在资源B加载完成,才能加载,但两者又会在不同的线程之间,此时简单的办法可以采用CountDownLatch来实现。其整体思路如下图
CountDownLatch.png
3.使用 Pipeline 机制,根据业务优先级规定业务初始化时机,制定启动框架,它们为各个任务建立依赖关系,最终构成一个有向无环图。对于可以并发的任务,会通过线程池最大程度提升启动速度。无论是微信的mmkernel 还是阿里的Alpha 都具备这种能力。
4.其他方案:
除了上述几种,我们也可以利用IdealHandler,dex分包等相关方式做到启动优化。
上面主要介绍了如何获取启动的相关事件和相关优化知识点。关于时间就是尽量使用工具,关于优化整体思路就是能预加载能延迟加载的资源尽量去预加载去延迟加载,能异步的业务尽量异步。
当然优化这个话题也是要根据具体的业务逻辑来定,总之:
对于启动优化要警惕 KPI 化,我们要解决的不是一个数字,而是用户真正的体验问题。
上述只是提供一些思路和方式,还有很多奇淫技巧,欢迎给位大佬评论指出。
感谢大家能耐着性子看完啰里啰嗦的文章
在这里我也分享一份私货,自己收录整理的Android学习PDF+架构视频+面试文档+源码笔记,还有高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习
如果你有需要的话,可以点赞+评论+转发,关注我,然后私信我【进阶】我发给你