近日在看《Pro Android Apps Performance Optimization》,读到一个概念——Lazy Initialization,翻译成中文就是“推迟初始化”,或叫“延迟初始化”,下面分享一下个人的粗浅见解。
这个概念在Android性能优化中还是非常重要的,从某种意义上来说,迟到也不一定是坏事啊,正所谓来的早不如来的巧,这里的巧就是指当需要你时,你再来;从这种意义上来说,来的早反而就是坏事了,因为你来的早,我总得招待你吧,那么很早就开始消费我的时间和资源了。
回到程序中来,如果某个对象不是立即就需要使用时,我们完全可以推迟创建,等需要时再创建它。这样做好处非常明显,因为创建对象需要内存,分配内存需要时间,如果我们消耗了很多内存和时间来创建并不是马上需要使用的对象时,对性能将是很大的影响。也许你会说,我提前创建了某个对象,当后面需要使用时就不再需要创建了,这样后面就可以节省时间和内存了,是这样的。但是,也许我们还未等到需要这个对象时,就需要结束这个程序呢?岂不是白白浪费了最初的创建过程?举个例子,一个Activity中有一个button,当点击button,加载一个bitmap,如果我们在onCreate()时就创建好了Bitmap对象,然而当还未点击button时,应用就直接退出了,很明显初始时创建对象完全浪费了。因此,还是建议把对象的初始化推迟到需要使用的时候,比如该例子中在button的点击事件里创建bitmap对象。
依然拿Activity为例,一般我们都是在onCreate()中执行所有的初始化操作,这是可以的,而我们也是这样做的,但是必要的及不必要的初始化都在这里执行的话,必然导致onCreate()的运行时间过长,而只有onCreate()结束后,才会执行onStart()和onResume(),可见,过多不必要的初始化会延迟应用的启动,造成非常不好的用户体验。记得从哪里看到一篇文字说,就因为onCreate()里调用setContentView(),这个布局初始化的操作会占用onCreate()、onStart()和onResume()总时间的90%多(具体数据不记得了,大意就是指初始化很费时间),因此我们应尽可能地将一些不必要的对象推迟初始化。
有两个这样做的实例:Lazy Initialization的单例模式及Android的ViewStub。
1.先来看一个经典的单例模式(在不考虑多线程并发、Double-Check及内存分配等各种问题的情况下):
public class Singleton {
private static Singleton instance = null;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
以上这种实现方式就是延迟了Singleton对象的初始化,在需要的地方才创建对象。
再来看一个饿汉式的单例实现:
public class Singleton {
private Singleton() {}
private static final Singleton instance= new Singleton();
public static Singleton getInstance() {
return instance;
}
}
以上这种实现方式在一开始就创建了Singleton对象,当然没有绝对的好与不好,第二种饿汉式的单例是线程安全的,如果从性能优化的角度考虑,建议使用第一种推迟初始化的单例实现方式,当然需要考虑多线程并发访问的问题。
2.再来说Android布局中的ViewStub,这个控件看起来有点陌生,那是因为我们用的比较少(其实ViewStub的适用范围也比较窄,因为它只适用于那些只需要inflate一次,很少变动的布局,如果某个布局需要经常的显示与隐藏,那么ViewStub并不适用)。前文说到,Activity在启动时很多的时间都花费在创建布局上,而我们往往把各种需要或暂时不需要的控件都写在布局xml文件中,这样加载布局就会花过长的时间。使用Android.view.ViewStub可以推迟部分控件的初始化,来加快应用的启动速度。ViewStub是一个轻量级且不可见的一个视图,占用很少的资源,它可以包含一个布局,哪怕是很复杂的布局。一开始初始化时,ViewStub包含的布局并不会展开,这样就能加快布局的加载速度,当在需要的时候,可以展开ViewStub中包含的布局视图,即推迟初始化布局,这样会很好的提升应用的性能。下面是一个ViewStub的使用示例:
布局文件中的ViewStub:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<ViewStub
android:id="@+id/stubid"
android:inflatedId="@+id/myid"
android:layout="@layout/mylayout"/>
</LinearLayout>
在需要时展开ViewStub中的布局,即推迟初始化布局:
ViewStub stub = (ViewStub) findViewById(R.id.stubid);
View inflatedView = stub.inflate();
不过,只能inflate()一次,ViewStub在展开其包含的布局之后就会被移除。
以上只是两个推迟初始化的例子,可能还有很多地方是这么做的,其实有时候性能优化也是一个矛盾的、需要权衡的过程,甚至最后只是一个折衷,我们应当细细体会。