主目錄見:Android高級(jí)進(jìn)階知識(shí)(這是總目錄索引)
?這篇是看了別人的想法之后米愿,發(fā)現(xiàn)這個(gè)方法還是蠻好的利诺,所以在這里給大家一起分享下赞庶,畢竟性能還是大家所追求的翁脆。而且有時(shí)候性能優(yōu)化就是一個(gè)簡單的舉動(dòng)也許就能改變很多眷蚓,使你的App看過去更專業(yè)。上一篇我們講了[布局優(yōu)化(扁平化反番,Merge的使用沙热,ViewStub的使用)],這一篇也會(huì)用到這里的知識(shí)罢缸。當(dāng)然如果你想要這篇文章的例子可以[點(diǎn)擊下載]篙贸。
一.目標(biāo)
今天分享這一篇呢主要就是給大家提供一個(gè)方案吧,也希望大家如果有好的東西可以分享出來給大家知道枫疆。今天的目標(biāo)是:
1.優(yōu)化App的冷啟動(dòng)速度爵川;
2.同時(shí)從性能上說說白屏的解決方法優(yōu)劣。
二.優(yōu)化實(shí)例解析
我們都知道养铸,Activity的啟動(dòng)流程對(duì)于我們的來說雁芙,能優(yōu)化的流程里面只有后面View怎么呈現(xiàn)出來,View渲染完畢數(shù)據(jù)的請(qǐng)求加載钞螟。按理說這部分能做的工作最多兔甘。我們先來說說View是怎么顯示在界面上的?
Android的視圖都是通過window來呈現(xiàn)的鳞滨,不管Activity洞焙,dialog或者Toast等都有一個(gè)對(duì)應(yīng)的window,然后通過WindowManager來管理View拯啦。而且在Acitivity啟動(dòng)過程中又會(huì)通過WindowManagerImpl將window和view
(DecorView)
進(jìn)行關(guān)聯(lián)調(diào)用addView將decorView添加澡匪。接著從ViewRootImpl調(diào)用performTraversals()依次經(jīng)過measure(),layout()和draw()三個(gè)過程將view顯示出來褒链。
從這上面可以可以看出有一大部分的時(shí)間占用在視圖的渲染上面唁情。我們先來看看以前早期我們的啟動(dòng)頁設(shè)計(jì):
- 我們首先會(huì)先創(chuàng)建一個(gè)SplashActivity頁面,然后在這里寫啟動(dòng)頁的展示信息而且也會(huì)有幾秒跳過廣告等甫匹,同時(shí)這里可能做一些數(shù)據(jù)的預(yù)加載甸鸟,然后數(shù)據(jù)加載完畢傳給MainActivity。
然后我們看看我們這篇的做法:
- 我們這里把SplashAcitivity改成Fragment的形式實(shí)現(xiàn)兵迅,然后在MainActivity的時(shí)候進(jìn)行展示這個(gè)Fragment抢韭,在展示這個(gè)Fragment的同時(shí),我們?cè)诖绑w加載完畢之后直接把我們真正的布局加載進(jìn)來恍箭,這個(gè)布局可能會(huì)比較復(fù)雜刻恭,所以我們?cè)谡故綟ragment的時(shí)候我們不渲染,而是用ViewStub進(jìn)行懶加載扯夭,與此同時(shí)鳍贾,我們還可以同時(shí)進(jìn)行異步網(wǎng)絡(luò)數(shù)據(jù)的加載。
從上面的做法我們可以看出交洗,第一種做法贾漏,雖然SplashActivity加載很快,然后數(shù)據(jù)也做了預(yù)加載藕筋,但是到MainActivity的時(shí)候纵散,首頁布局依舊是比較復(fù)雜的所以還是會(huì)經(jīng)過view的measure,layout隐圾,draw整個(gè)過程伍掀,還是拖慢了整個(gè)啟動(dòng)過程,而第二個(gè)方案卻將啟動(dòng)頁和MainActivity主布局合并在一起暇藏,而且在渲染Splash頁面的時(shí)候蜜笤,MainActivity的主布局是放在ViewStub里面的。同時(shí)數(shù)據(jù)可以在Splash頁面展示的時(shí)候就進(jìn)行加載盐碱,用戶感知不到把兔。
1.SplashFragment
首先我們來從啟動(dòng)頁Fragment說起沪伙,這個(gè)頁面我們盡量簡單。這里我們先來看看xml內(nèi)容:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<FrameLayout
android:id="@+id/frame"
android:background="@mipmap/bg_welcome"
android:layout_width="match_parent"
android:layout_height="match_parent" >
</FrameLayout>
</RelativeLayout>
我們看到這里只是簡單的一個(gè)背景而已县好,當(dāng)然有的app會(huì)在這邊播放一個(gè)視頻围橡,或者在這邊加載一個(gè)廣告。但是這是具體的設(shè)計(jì)決定缕贡,我們不參與細(xì)講翁授。我們來看看我們的splashFragment:
public class SplashFragment extends Fragment {
@Override
@Nullable
public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_splash, container,false);
}
}
我們看到我們這里只是簡單地將我們的布局inflate進(jìn)來。然后我們就將這個(gè)fragment在我們的MainAcitivity里面展示晾咪。
2.MainActivity
我們這里先來看看MainActivity里面setContentView的布局:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<ViewStub
android:id="@+id/content_viewstub"
android:layout="@layout/activity_main_viewstub"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<FrameLayout
android:id="@+id/frame"
android:layout_width="match_parent"
android:layout_height="match_parent" >
</FrameLayout>
</RelativeLayout>
我們看到第一個(gè)ViewStub是用于放我們MainActivity主布局activity_main_viewstub的收擦,第二個(gè)FrameLayout是為了放我們SplashFragment的。我們?cè)趧傞_始加載的時(shí)候ViewStub是不會(huì)加載到我們的view樹中的谍倦,所以不占用我們的渲染時(shí)間塞赂。我們看下onCreate里面做了什么:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
splashFragment = new SplashFragment();
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.frame, splashFragment);
transaction.commit();
viewStub = (ViewStub)findViewById(R.id.content_viewstub);
//1.判斷當(dāng)窗體加載完畢的時(shí)候,就把我們真正的布局加載進(jìn)來
getWindow().getDecorView().post(new Runnable() {
@Override
public void run() {
// 開啟延遲加載
mHandler.post(new Runnable() {
@Override
public void run() {
//將viewstub加載進(jìn)來
viewStub.inflate();
}
} );
}
});
//2.判斷當(dāng)窗體加載完畢的時(shí)候執(zhí)行,延遲一段時(shí)間是為了模擬做動(dòng)畫的耗時(shí)。
getWindow().getDecorView().post(new Runnable() {
@Override
public void run() {
// 開啟延遲加載,也可以不用延遲可以立馬執(zhí)行(我這里延遲是為了實(shí)現(xiàn)fragment里面的動(dòng)畫效果的耗時(shí))
mHandler.postDelayed(new DelayRunnable(MainActivity.this, splashFragment) ,2000);
}
});
//3.同時(shí)進(jìn)行異步加載數(shù)據(jù)
}
我們看到程序剛進(jìn)來就將fragment顯示出來昼蛀,然后在窗體加載完畢才將我們的真正布局加載進(jìn)來减途,同時(shí)模擬了做動(dòng)畫或者廣告時(shí)間,當(dāng)然實(shí)際過程中可以在動(dòng)畫完畢或者廣告結(jié)束再去移除Fragment曹洽,或者我們有跳過廣告的時(shí)候我們可以做一個(gè)回調(diào)給MainActivity讓他馬上加載主頁布局鳍置。當(dāng)然同時(shí)我們可以加載我們的網(wǎng)絡(luò)數(shù)據(jù)。接著我們來看我們移除Fragment的代碼:
static class DelayRunnable implements Runnable{
private WeakReference<Context> contextRef;
private WeakReference<SplashFragment> fragmentRef;
public DelayRunnable(Context context, SplashFragment f) {
contextRef = new WeakReference<>(context);
fragmentRef = new WeakReference<>(f);
}
@Override
public void run() {
if(contextRef!=null){
SplashFragment splashFragment = fragmentRef.get();
if(splashFragment==null){
return;
}
FragmentActivity activity = (FragmentActivity) contextRef.get();
FragmentTransaction transaction = activity.getSupportFragmentManager().beginTransaction();
transaction.remove(splashFragment);
transaction.commit();
}
}
}
這里說下為什么要用弱引用來存放著兩個(gè)對(duì)象送淆,我們知道税产,我們?cè)谑褂肏andler或者Timer的時(shí)候,如果是static的類且有時(shí)間延遲的話那么就有可能內(nèi)存溢出偷崩,因?yàn)閟tatic的生存時(shí)間周期可能長于Activity辟拷,這樣會(huì)導(dǎo)致Activity關(guān)閉了,但是引用還是被static類持有阐斜,GC不能回收衫冻。所以這里用弱應(yīng)用,也就是說如果Activity關(guān)閉谒出,那么GC可以隨時(shí)回收這個(gè)Activity隅俘。最后我們看下效果:
我們看到我們的效果跟以前用splashActivity在視覺上沒什么區(qū)別,因?yàn)槲覀兊膯?dòng)頁面布局都比較簡單:
我們可以用指令來進(jìn)行對(duì)比:
adb shell am start -W com.lenovohit.splashoptimize/com.lenovohit.splashoptimize.MainActivity
3.白屏或者黑屏問題
其實(shí)這里的白屏和黑屏都是同個(gè)問題(只是主題的原因有可能會(huì)不同)笤喳,是因?yàn)閣indow的背景为居,因?yàn)镈ecorView默認(rèn)會(huì)有一個(gè)純色的背景,由于我們的activity可能做得工作太多杀狡,或者application初始化太多東西蒙畴,我們的實(shí)際視圖還沒渲染完成,所以我們啟動(dòng)我們Activity會(huì)先顯示window的背景呜象。所以由于設(shè)備的性能原因表現(xiàn)出來的白屏或者黑屏問題也不一樣膳凝。一般來說這有兩種方法解決:
1.在style文件中加入如下代碼:
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
......
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowNoTitle">true</item>
</style>
2.給Window加上背景避免白屏
1.
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 底層白色 -->
<item android:drawable="@color/white" />
<!-- 頂層Logo居中 -->
<item>
<bitmap
android:gravity="center"
android:src="@drawable/ic_github" />
</item>
</layer-list>
2.
<style name="SplashTheme" parent="AppTheme">
<item name="android:windowBackground">@drawable/bg_splash</item>
</style>
3.
<activity
android:name=".activity.MainActivity"
android:screenOrientation="portrait"
android:theme="@style/SplashTheme">
我們看到這上面兩種方式都是會(huì)解決白屏問題碑隆,但是我還是想說一下我個(gè)人的看法,我們知道蹬音,雖然第一種方法讓主題變成透明了上煤,但是其實(shí)android系統(tǒng)還是回去渲染,顯然這也可能影響過度繪制的祟绊,第二種方法也是類似,因?yàn)槲覀儎?dòng)態(tài)給window加上了背景哥捕,相當(dāng)于我們自動(dòng)多了一層背景牧抽,如果我們activity里面的布局也有背景的話,那么也是會(huì)影響過度繪制的遥赚。所以我覺得最好的方式就是歡迎頁盡量簡單扬舒,能迅速渲染出我們的布局,這樣的話白屏的可能性也就變小了凫佛。
總結(jié):今天講的只是一個(gè)例子讲坎,大家可以當(dāng)做一個(gè)思維的拓展,還有最后的白屏黑屏問題如果大家有自己的想法歡迎留言呀愧薛,希望我們共同進(jìn)步哈晨炕。