adb shell start am -W packname/首屏activity
输出:
- This time
:最后一个activity启动耗时
- Totaltime
:所有activity 启动耗时
- WaitTime
:AMS启动activity总耗时
不在onWindowFocusChanged
中进行,它只是首帧时间
在第一条数据获取结束时表示启动结束,进行获取启动完成时间
轻量级,开销小 直观反映CPU利用率
cpu time与 wall time
- cpu time:
代码消耗CPU时间(重点指标,也是优化方向)
- wall time
:代码执行时间
为什么两个时间不一样:锁冲突,比如有一个a方法,它需要获取一把锁,但是这个锁被别人获取者,代码就在a方法停了下来,一直等待,造成wall time比cpu time长,cpu无消耗
在Application
中进行初始化框架操作,可以放在线程池中进行,使用线程池中的线程数量,比如核心与非核心线程,为了可以更好的利用线程数量,可以参考AsyncTask
源码中构建的线程池,进行操作。但是某些框架初始化必须在Application onCreate
中初始化完成,所以可以使用CountDownLatch
让主线程进行等待,在子线程池中进行,CountDownLatch
操作。
public class MyApplication extends Application {
/**
* 使用Asynctask 中创建线程池的方法,进行创建线程池,根据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 CountDownLatch mCountDownLatch = new CountDownLatch(1);
@Override
public void onCreate() {
super.onCreate();
ExecutorService service = Executors.newFixedThreadPool(CORE_POOL_SIZE);
//分开进行初始化操作,可以更好的利用CPU核心创建的线程数量,时间会非常短
service.submit(new Runnable() {
@Override
public void run() {
//初始化bugly
initBugly();
}
});
service.submit(new Runnable() {
@Override
public void run() {
//初始化Fresco 图片框架
initFresco();
}
});
service.submit(new Runnable() {
@Override
public void run() {
//初始化Weex 框架
initWeex();
mCountDownLatch.countDown();
}
});
try {
//不满足条件会一直等待,这里等待weex框架初始化完成
mCountDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void initWeex() {
}
private void initFresco() {
}
private void initBugly() {
}
}
使用ThreadPoolExecutor创建线程池
public class DispatcherExecutor {
private static ThreadPoolExecutor sCPUThreadPoolExecutor;
private static ExecutorService sIOThreadPoolExecutor;
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
// We want at least 2 threads and at most 4 threads in the core pool,
// preferring to have 1 less than the CPU count to avoid saturating
// the CPU with background work
public static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 5));
private static final int MAXIMUM_POOL_SIZE = CORE_POOL_SIZE;
private static final int KEEP_ALIVE_SECONDS = 5;
private static final BlockingQueue<Runnable> sPoolWorkQueue = new LinkedBlockingQueue<>();
private static final DefaultThreadFactory sThreadFactory = new DefaultThreadFactory();
private static final RejectedExecutionHandler sHandler = new RejectedExecutionHandler() {// 一般不会到这里
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
Executors.newCachedThreadPool().execute(r);
}
};
/**
* 获取CPU线程池
* @return
*/
public static ThreadPoolExecutor getCPUExecutor() {
return sCPUThreadPoolExecutor;
}
/**
* 获取IO线程池
* @return
*/
public static ExecutorService getIOExecutor() {
return sIOThreadPoolExecutor;
}
/**
* The default thread factory.
*/
private static class DefaultThreadFactory implements ThreadFactory {
private static final AtomicInteger poolNumber = new AtomicInteger(1);
private final ThreadGroup group;
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix;
DefaultThreadFactory() {
SecurityManager s = System.getSecurityManager();
group = (s != null) ? s.getThreadGroup() :
Thread.currentThread().getThreadGroup();
namePrefix = "TaskDispatcherPool-" +
poolNumber.getAndIncrement() +
"-Thread-";
}
public Thread newThread(Runnable r) {
Thread t = new Thread(group, r,
namePrefix + threadNumber.getAndIncrement(),
0);
if (t.isDaemon())
t.setDaemon(false);
if (t.getPriority() != Thread.NORM_PRIORITY)
t.setPriority(Thread.NORM_PRIORITY);
return t;
}
}
static {
sCPUThreadPoolExecutor = new ThreadPoolExecutor(
CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
sPoolWorkQueue, sThreadFactory, sHandler);
sCPUThreadPoolExecutor.allowCoreThreadTimeOut(true);
sIOThreadPoolExecutor = Executors.newCachedThreadPool(sThreadFactory);
}
}
CountDownLatch
核心数+1
进行设定核心数*(1+平均等待时间/平均工作时间)
进行设定冷启动是指APP在手机启动后第一次运行,或者APP进程被kill掉后在再次启动。 可见冷启动的必要条件是该APP进程不存在,这就意味着系统需要创建进程,APP需要初始化。在这三种启动方式中,冷启动耗时最长,对于冷启动的优化也是最具挑战的。因此本文重点谈论的是对冷启动相关的优化。
App
进程存在,当时Activity可能因为内存不足被回收。这时候启动App不需要重新创建进程,但是Activity
的onCrate
还是需要重新执行的。场景类似打开淘宝逛了一圈然后切到微信去聊天去了,过了半小时再次回到淘宝。这时候淘宝的进程存在,但是Activity可能被回收,这时候只需要重新加载Activity
即可。
App进程存在,并且Activity对象仍然存在内存中没有被回收。可以重复避免对象初始化,布局解析绘制。 场景就类似你打开微信聊了一会天这时候出去看了下日历 在打开微信 微信这时候启动就属于冷启动。 在最近任务给App加锁和启动方式有什么关系
某些厂商为了用户体验提供了给APP上锁的功能,目的就是让用户自己做主是上锁的APP不被杀,启动的时候不会处于冷启动方式,但是加锁也不是万能的,Low memory killer在内存极度吃紧的情况下也会杀死加锁APP,在此启动时也将以冷启动方式运行。
AI在进程管理方面可谓是大有可为。MIUI10发布了进程AI唤醒功能,是APP启动速度远超友商。这其中的道理简单说就是学习用户的使用习惯,提前将App进程创建好,当用户打开APP时不会出去冷启动。比如你是微信重度用户你发现用了MIUI10就再也见不到微信启动页面的那个地球了,这就是AI唤醒的功劳。
由于应用程序启动时冷启动,系统会默认在启动时启动空白窗口 应用程序启动有三种状态,每种状态都会影响应用程序对用户可见所需的时间:冷启动,热启动和温启动。 在冷启动时,应用程序从头开始。在其他状态下,系统需要将正在运行的应用程序从后台运行到前台。我们建议您始终根据冷启动的假设进行优化。这样做也可以改善热启动和温启动的性能。 在冷启动开始时,系统有三个任务。这些任务是: - 加载并启动应用程序。 - 启动后立即显示应用程序空白的启动窗口。 - 创建应用程序进程。
一旦系统创建应用程序进程,应用程序进程就会负责下一阶段。这些阶段是:
- 创建app对象.
- 启动主线程(main thread)
.
- 创建应用入口的Activity对象.
- 填充加载布局Views
- 在屏幕上执行View的绘制过程measure -> layout -> draw
应用程序进程完成第一次绘制后,系统进程会交换当前显示的背景窗口,将其替换为主活动。此时,用户可以开始使用该应用程序。
为了解决启动窗口白屏问题,许多开发者使用透明主题来解决这个问题,但是治标不治本。 虽然解决了上面这个问题,但是仍然有些不足。
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<item name="android:windowFullscreen">true</item>
<item name="android:windowIsTranslucent">true</item>
</style>
为了更顺滑无缝衔接我们的闪屏页,可以在启动 Activity 的 Theme中设置闪屏页图片,这样启动窗口的图片就会是闪屏页图片,而不是白屏。
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<item name="android:windowBackground">@mipmap/launch</item> //闪屏页图片
<item name="android:windowFullscreen">true</item>
<item name="android:windowContentOverlay">@null</item>
</style>
Application
优化
Application
作为 应用程序的整个初始化配置入口,时常担负着它不应该有的负担~
有很多第三方组件(包括App应用本身)都在 Application 中抢占先机,完成初始化操作。
但是在 Application 中完成繁重的初始化操作和复杂的逻辑就会影响到应用的启动性能
通常,有机会优化这些工作以实现性能改进,这些常见问题包括:
- 复杂繁琐的布局初始化
- 阻塞主线程 UI 绘制的操作,如 I/O 读写或者是网络访问.
- Bitmap
大图片或者 VectorDrawable
加载
- 其它占用主线程的操作
我们可以根据这些组件的轻重缓急之分,对初始化做一下分类 : - 必要的组件一定要在 主线程中立即初始化(入口 Activity 可能立即会用到) - 组件一定要在 主线程中初始化,但是可以延迟初始化。 - 组件可以在 子线程中初始化。
放在子线程的组件初始化建议延迟初始化 ,这样就可以了解是否会对项目造成影响! 所以对于上面的分析,我们可以在项目中 Application 的加载组件进行如下优化 : - 将Bugly,x5内核初始化,SP的读写,友盟等组件放到子线程中初始化。(子线程初始化不能影响到组件的使用)
new Thread(new Runnable() {
@Override
public void run() {
//设置线程的优先级,不与主线程抢资源
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
//子线程初始化第三方组件
Thread.sleep(5000);//建议延迟初始化,可以发现是否影响其它功能,或者是崩溃!
}
}).start();
将需要在主线程中初始化但是可以不用立即完成的动作延迟加载(原本是想在入口 Activity 中进行此项操作,不过组件的初始化放在 Application 中统一管理为妙.)
handler.postDelayed(new Runnable() {
@Override
public void run() {
//延迟初始化组件
}
}, 3000);
应用App通常会设置一个固定的闪屏页展示时间,例如2000ms,所以我们可以根据用户手机的运行速度,对展示时间做出调整,但是总时间仍然为 2000ms。 闪屏页政展示总时间 = 组件初始化时间 + 剩余展示时间。 也就是2000ms的总时间,组件初始化了800ms,那么就再展示1200ms即可 获取闪屏时间 Application 初始化后会调用 attachBaseContext() 方法,再调用 Application 的 onCreate(),再到入口 Activity的创建和执行 onCreate() 方法。所以我们就可以在 Application 中记录启动时间。
//Application
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
SPUtil.putLong("application_attach_time", System.currentTimeMillis());//记录Application初始化时间
}
有了启动时间,我们得知道入口的 Acitivty
显示给用户的时间(View绘制完毕),在onWindowFocusChanged()
的回调时机中表示可以获取用户的触摸时间和View的流程绘制完毕,所以我们可以在这个方法里记录显示时间。
//入口Activity
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
long appAttachTime = SPUtil.getLong("application_attach_time");
long diffTime = System.currentTimeMillis() - appAttachTime;//从application到入口Acitity的时间
//所以闪屏页展示的时间为 2000ms - diffTime.
}
所以我们就可以动态的设置应用闪屏的显示时间,尽量让每一部手机展示的时间一致,这样就不会让手机配置较低的用户感觉漫长难熬的闪屏页时间(例如初始化了2000ms,又要展示2000ms的闪屏页时间.),优化用户体验。