在Android 应用开发中,我们最关注的莫过于Activity的启动速度了.可以说启动速度的好坏一直是我们应用能否成功吸引用户的关键所在. 试想一个界面从点击入口到真正界面显示的过程,如果太长,用户将失去耐心,进而无情抛弃这个应用.
那么如何加快一个界面的显示速度呢?我们一般都会去研究Activity的初始化流程.实际上,如果我们新建一个Activity,在它的生命周期内什么都不干,那么他的启动和显示已经足够快了. 因为它是系统级的流程控制.我们不需要过多的去关注.
到最后我们都会得出一个很无奈的结论,Activity的启动速度实际跟我们初始化耗时是相关的. 初始化代码快,启动就快,初始化慢,启动就慢.
有人可能会想那如果我把初始化过程放在异步任务中去做不就可以了吗?
确实有很多应用是这么做的, 在onCreate()的时候去开启一个异步线程,然后开始干活,最后把结果Handler到主线程.
但这么做仍然降低了启动速度! 启动线程的损耗, 耗时任务抢占CPU的损耗,最终导致界面仍然会慢几个节奏.
有的人则会想到如果等界面完全显示出来了,再行初始化不就好了吗?
于是在onCreate()中用postDelay()的方式,进行人工的延时处理.此举确实解决了延时初始化的问题,但是却引入了另一个问题, 100ms的延时时间真的就是界面初始化显示完成的时间吗? 对于不同的手机,这将是一个未知数. 因此,方案最终表现出来的效果是,在有的手机上界面很快显示,但却要继续等一段时间才开始初始化. 而另一些手机,则因为延时太短导致界面没有显示完全时就开始初始化,最终抢占了主线程,降低了启动速度.
通过在Activity的各个生命周期函数中打上日志,你以为界面完全显示是在onStart()中吗?NO! 你以为界面完全显示是在onResume()中吗? NO! 你以为是在onAttachToWindow()中吗? NO! 当你在这些生命周期中进行初始化的时候,你会发现界面仍然需要等到初始化完成才会显示出来.
为什么呢? 最终问题的思路定位到了Message-Loop中. 主线程是一个完全不能承受耗时操作的循环.而Activity的生命周期函数都是通过Loop的消息依次被执行的. 所以,如果有哪一个生命周期函数被耗时操作卡住,那么这个循环是无法继续滚动,也无法继续发送界面渲染消息进行界面绘制的. 相信大家都知道,View.setVisible(VISIBLE) 实际上是发送了一个渲染的消息的.而不是真正的去直接绘制界面了. 因此如果这个消息被阻塞,那么界面仍然会是一片空白.
那么问题来了?我们如何确定Activity一定被绘制完成呢? 通过追寻Loop的实现,我们找到了一个东西. Looper.myQueue().addIdleHandler().
这个函数用于给Looper中添加一个用于处理Looper空闲时的事件响应-Handle. Looper何时会空闲呢?答案就是当一个Activity的所有初始化的生命周期过程被执行完成,并且界面渲染也完成的时候,Looper里边就没有其他的消息了. 于是我们就可以开始进行初始化了.不早也不晚.
甚至非常准时的在界面迅速显示完成的时候就开始初始化内容.
为此我们写了一个简单的封装:
public class BaseActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Looper.myQueue().addIdleHandler(new IdleHandler() {
@Override
public boolean queueIdle() {
Log.i("IdleHandler","queueIdle");
onInit();
return false; //false 表示只监听一次IDLE事件,之后就不会再执行这个函数了.
}
});
}
//子类重写此函数即可,而不需要在onCreate()中去初始化.
protected void onInit() {
Log.e("BaseActivity", "onInit");
}
}
经测试,在onInit()中做耗时操作时界面的显示速度完全没有影响,因为在做耗时操作前界面已经成功显示到屏幕上了!
当然并不是说就可以在onInit()中做非常耗时的操作了,它毕竟还是在主线程中执行的,因此会卡住界面直到执行完成,此期间如果想点击某个按钮,抱歉,我正在耗时,你点不动.
但是对于100ms,50ms这样的初始化耗时来说,完全可以接受的,因为从界面显示出来到人眼反应过来,最后再到人手有意识的开始操作一般都会在1s以上. 而在1s内,初始化已经完成了.所以onInit()中仍然仅适合做一些微小耗时的操作. 而不适合做同步任务.你仍然需要将一些非常耗时的操作放入异步任务中去.
这里的优化,真正的意义是,加快Activity界面展现的速度,让用户第一眼尽快看到界面.然后才开始做你的初始化过程.而不是等初始化完成才给用户看界面.