=========================================================
我來說下我所知道的事情。我不知道iOS為什么流暢,但我知道一些Android為什么不流暢的原因。
首先,就題主所說的問題,我用iPad和小米Pad對(duì)比了一下微博滑動(dòng)滾屏這件事情(2014年8月10日目前微博app最新版本)。正如題主所說,直觀感受上明顯感覺iOS要流暢、舒服。
在這件事情上我認(rèn)為主要是這三個(gè)原因:
- 速度曲線。
當(dāng)你滑動(dòng)界面然后松手,這時(shí)界面會(huì)繼續(xù)滑動(dòng),然后速度減小,直到速度為0時(shí)停止。iOS下速度減小的這個(gè)過程比較慢,尤其是快要停的時(shí)候是慢慢停的,視覺上有種很順滑的感覺;Android下則從松手到停要快很多,相比之下有種戛然而止的感覺。 從數(shù)據(jù)/技術(shù)角度來看這個(gè)事情,我們滑動(dòng)界面的最終目的不是為了“動(dòng)”,而是為了“停”,因此只要平滑的到達(dá)目的地,似乎越快完成這個(gè)過程越好,所以Android的選擇是理所當(dāng)然的。但事實(shí)是,大家普遍更喜歡iOS的方式,這樣做顯得更順滑、更優(yōu)雅。
- 幀率。
絕大部分時(shí)間兩者都能保持60FPS左右的滿幀率。但都會(huì)有偶爾的掉幀。并且Android上要比iOS上嚴(yán)重很多。(好吧,比起前兩年,已經(jīng)好太多了。)我前前后后滑動(dòng)了幾十次,iOS在前面遇到1次掉幀,后面就很穩(wěn)定了。而Android幾乎每滑動(dòng)一次都會(huì)伴隨一次掉幀。這完全就是真真實(shí)實(shí)的卡頓,用戶必然會(huì)感覺到那一刻的不流暢。Android掉幀的原因我后面再詳細(xì)分析。
- 觸摸響應(yīng)速度。
從手指碰到觸摸屏,到屏幕上顯示處理這次觸摸產(chǎn)生的畫面,是需要時(shí)間的。時(shí)間越短感覺越跟手。據(jù)說iOS的觸摸屏的處理時(shí)間要比一般的Android手機(jī)快,這不是我的專長,不知道怎么驗(yàn)證。但在軟件系統(tǒng)層面,Android的顯示機(jī)制是app-->SurfaceFlinger-->Display,這比傳統(tǒng)的app-->Display多了一步,主要基于這個(gè)原因,畫面最終輸出到屏幕要比傳統(tǒng)的方式慢一幀(16.7ms)。
我覺得第1點(diǎn)造成的影響最大,恰恰卻是最技術(shù)/設(shè)備無關(guān)的。如果微博app或者Android系統(tǒng)要改變,很容易就可以調(diào)得跟iOS一模一樣。但正是由于這是產(chǎn)品形態(tài)上的差別而不是純粹技術(shù)上的優(yōu)劣,反倒成了Android最不太可能改變的。
第2點(diǎn)的影響其次,當(dāng)然是指在目前這個(gè)大部分時(shí)候都能滿幀的情況下。這方面是Android從早期到現(xiàn)在進(jìn)步最明顯的方面,使用了很多方法來優(yōu)化幀率。但就算現(xiàn)在Android進(jìn)化了很多,硬件性能也進(jìn)化了很多,卻仍舊不可能徹底消滅掉幀的情況。
第3點(diǎn)通俗的講就是跟手性,跟手性的重要性不言而喻,但現(xiàn)在的差別比較細(xì)微,且具體數(shù)據(jù)我也不清楚。 我想過一個(gè)辦法讓桌面、微博這種內(nèi)容和手一起動(dòng)的應(yīng)用繪制到屏幕的速度快一幀(16.7ms),其實(shí)就是抵消之前提到的慢的那一幀,需要framework層和app層一起配合改動(dòng),目前已申請(qǐng)了專利但代碼還沒進(jìn),將來有時(shí)間了應(yīng)該會(huì)進(jìn)到MIUI。感興趣的可以看看專利:滑動(dòng)操作響應(yīng)方法、裝置及終端設(shè)備。
最后我來用專業(yè)技術(shù)分析一下微博app在Android里掉幀的原因。非編程人員可以不看下去了。(這個(gè)過程我使用的是小米3高通版+最新版微博app。)
最初,我認(rèn)為這種現(xiàn)象很像GC(垃圾回收)導(dǎo)致的,于是打開logcat觀察每次卡頓的時(shí)候有沒有GC發(fā)生。結(jié)果發(fā)現(xiàn)并沒有。停下來的時(shí)候才會(huì)有GC,這說明微博app在滑動(dòng)過程中控制得不錯(cuò),在停下來的一刻才去分配內(nèi)存,使GC不影響幀率。
然后我打開“開發(fā)者選項(xiàng)”->“GPU呈現(xiàn)模式分析”->“在屏幕上顯示為條形圖”(好像是4.4才有這個(gè)選項(xiàng),之前的只能在dumpsys里看),這會(huì)在屏幕上直觀的顯示每幀繪制花費(fèi)的時(shí)間。屏幕上有條基準(zhǔn)線大概是16ms,如果超過這條線則很有可能掉幀了。如果下面的藍(lán)色部分很長則說明是軟件draw的部分太費(fèi)時(shí),那么可以通過traceview來繼續(xù)分析draw的java代碼。(我假定了現(xiàn)在的app都是硬件加速的方式。)如果中間紅色部分很長則說明是OpenGL ES繪制過程太費(fèi)時(shí)??梢杂胓ltrace來分析OpenGL ES的調(diào)用過程。 打開選項(xiàng)后使用微博app,發(fā)現(xiàn)雖然偶爾有超基準(zhǔn)線,但并不嚴(yán)重,并且卡頓的時(shí)候并沒有明顯異常。
于是我開始使用systrace,這下就很明顯了:

可以發(fā)現(xiàn)每隔幾幀,就會(huì)有一次大間隙,圖中我圈了紅圈圈的兩個(gè)地方都明顯超過正常的耗時(shí)時(shí)間。現(xiàn)在問題是,這些地方的代碼在干什么呢?是什么花費(fèi)了這么長時(shí)間?

放大后發(fā)現(xiàn)都是這兩個(gè):obtainView和setupListItem。它們都是在draw之前調(diào)用的,這也解釋了通過上一步的方法(“GPU呈現(xiàn)模式分析”)為什么沒有顯示出來,因?yàn)槟莻€(gè)方法只展示draw里面的時(shí)間消耗。 通過在源碼里搜索obtainView和setupListItem,可以發(fā)現(xiàn)是AbsListView的obtainView方法和ListView的setupChild方法打下的這個(gè)log。
接下來,我們用traceview來看看這兩個(gè)方法里面究竟調(diào)用了什么代碼導(dǎo)致耗時(shí)過長。 跟蹤obtainView最終到了這里:

代碼混淆了,看不太出來了,也懶得再跟下去了。但隨便點(diǎn)了點(diǎn)然后看到:

TextView.setText用了挺多的時(shí)間。感覺Android團(tuán)隊(duì)?wèi)?yīng)該優(yōu)化優(yōu)化這個(gè)方法。 然后再來跟蹤setupChild方法:

都是measure占用的時(shí)間,并且調(diào)用次數(shù)比較多。這應(yīng)該說明了View的數(shù)量不少。用hierarchyviewer看了下,的確不少,一條微博有50多個(gè)View:
 結(jié)論:我們最終大致找到了耗時(shí)的代碼及部分原因。這兩個(gè)耗時(shí)方法應(yīng)該都是有可能減下來的,但應(yīng)該不那么容易。 在Android上實(shí)現(xiàn)功能比較容易,但如果默認(rèn)實(shí)現(xiàn)有了性能問題,要想解決就不好說了,有時(shí)候要繞很遠(yuǎn)。
最后,列一些相關(guān)鏈接: Android圖形架構(gòu)(繪圖慢一幀的事情這里有說):http://source./devices/graphics/architecture.html Android Performance Case Study:http://www./docs/android-performance-case-study-1.html
|