Android UI優化全解析

點擊上方藍字關注我,天天一見,給你力量前端


前言

往期優化文章目錄:java

啓動優化python


今天繼續優化方面的內容——UI(佈局)優化。android

UI優化知識點主要分爲三部分ios

  • 第一部分,系統爲咱們作的優化。因爲前端中UI展現的特殊性和重要性,Android團隊也是在不斷想辦法提升UI方面的渲染速度,因此也是更新了不少系統優化方案,好比:

硬件加速、黃油計劃、RenderThread。c++

  • 第二部分,咱們能夠 具體實施的優化方案。主要包括:

java代碼佈局、View重用、異步建立View、xml佈局優化、異步佈局框架Litho、屏幕適配、Flutter、Jetpack Composegit

  • 第三部分,工具使用,主要包括:

Choreographer、monitor、Systracegithub

系統作的優化

硬件加速

以前咱們說過,一個圖形的繪製是CPU,GPU和屏幕三方合做的結果。web

Android3.0以前,尚未硬件加速,都是經過CPU進行數據計算,而後經過Skia庫進行軟件繪製,可是CPU對於圖形處理並不高效。面試

因而從3.0開始,Android支持了硬件加速,到Android4.0默認開啓硬件加速。

開啓硬件加速後,就是由CPU進行圖形緩存數據的繪製。這樣CPU和GPU就能比較好的分工,各司其職了。CPU用於控制複雜繪製邏輯、構建或更新DisplayList(基礎元素)GPU用於完成圖形計算、渲染DisplayList(基礎元素)

這裏也找了一張各類場景下,硬件加速先後的流程與加速效果(Android6.0背景):

可是硬件加速也是有缺點的:

  • 啓用硬件加速須要更多資源,所以應用會佔用更多內存。
  • 比較低的版本,因爲有些 Canvas API尚未支持,因此使用硬件加速可能會有問題。那麼咱們就能夠手動關閉某個view的硬件加速:
    myView.setLayerType(View.LAYER_TYPE_SOFTWARE, null)

Project Butter

黃油計劃,你有可能沒怎麼據說,可是其實以前兩章內容都提到過,Android4.1以後,Google提出了黃油計劃,主要包括兩個內容:

  • VSYNC
  • Triple Buffering(三重緩存)

這些都熟悉了吧,上兩節都說過的,這裏再簡單提一下:

  • VSYNC

垂直同步信號,每當收到這個信號後,CPU就開始準備Buffer數據,並在16ms以內和GPU把屏幕須要的緩存數據準備好。

  • Triple Buffering(三重緩存)

Android4.1以前,是雙緩存機制,大部分是沒問題的。可是當CPU/GPU繪製過程較長,超過一個vsync信號週期,通常是16ms,就會致使丟幀,CPU沒法使用被GPU或者屏幕佔用的緩存區。若是下一幀繪製若是又超時,那麼又會丟幀。

因此再加上一個緩存區,這樣,CPU、GPU、Display三者都有各自的緩存區,互不影響,就能保證時間的最大化利用,也就能減小上述的狀況了。

RenderThread

RenderThread是在Android5.0提出的,從這個名字就能知道,它是一個線程,一個專門執行GL命令的線程,也就是一部分的繪製工做。

有了它以後,當CPU處理數據給GPU後,就不須要等GPU渲染完畢了,而是將一些繪製任務交給RenderThread,這樣就能減小主線程的工做,保證畫面的流暢。同時還提供了RenderNode,用來作view的屬性封裝,渲染幀的信息等等。

優化方案

java代碼佈局

咱們通常都是用XML文件進行佈局,可是XML解析也是很耗時的,並在這個解析過程在主線程進行。

因此咱們有的時候也許能夠經過Java代碼或者kotlin進行View的建立?

理論中,這樣確實能減小布局加載的消耗時間,可是Java代碼建立View太麻煩了,並且沒法可視化。

固然,也有一些庫能夠幫助咱們將xml代碼轉換成java代碼,好比X2C(https://github.com/iReaderAndroid/X2C ),可是它並不支持全部的狀況,好比merge標籤,appCompat兼容控件等等。

因此咱們須要在這之中找到平衡點,有的時候,UI簡單而且要求高性能的前提下,咱們能夠試試用這樣的方法,即用java代碼代替XmL代碼。

View重用

參照Recyclerview的作法,咱們也能夠將一些經常使用的view保存到緩存池中,這樣在不一樣的界面中就能複用緩存池裏面的view。

異步建立view

這是Google提出的一個方案——AsyncLayoutInflater。它能夠異步加載佈局文件,而且回調給主線程,從而減小主線程耗時。簡單貼下主要代碼:

new AsyncLayoutInflater(MainActivity.this).inflate(R.layout.activity_main, nullnew AsyncLayoutInflater.OnInflateFinishedListener() {
        @Override
        public void onInflateFinished(@NonNull View view, int i, @Nullable ViewGroup viewGroup) {
        //回調給主線程
            setContentView(view);
    }
});

xml佈局優化

在寫xml佈局文件的時候,咱們要作的也有不少,好比:

  • 減小布局嵌套。多使用ViewStub、Merge、ConstraintLayout來代替。
  • 優化開銷。RelativeLayout和 使用weight的LinearLayout 開銷比較大,建議使用ConstraintLayout,LinearLayout代替。

異步佈局框架Litho

Litho是Facebook開源的一款在Android上高效創建UI的聲明式框架。

主要有如下特色:

1)聲明式:它使用了聲明式的API來定義UI組件。

2)異步佈局:它把 measurelayout 都放到了後臺線程,只留下了必需要在主線程完成的 draw,這大大下降了 UI 線程的負載

3)視圖扁平化:因爲 Litho 使用了自有的佈局引擎(Yoga),在佈局階段就能夠檢測沒必要要的層級、減小 ViewGroups,來實現 UI 扁平化。

4)優化 RecyclerView:Litho 還優化了 RecyclerView 中 UI 組件的緩存和回收方法。

屏幕適配

關於屏幕適配問題,也是老生常談了。主要有如下幾種方案:

  • dp適配方案。

這是系統自帶的適配單位,dp是基於屏幕物理分辨率一個抽象的單位,用於說明與密度無關的尺寸和位置。因此它能在不一樣分辨率的手機上有相對大小的適配性。計算公式是:px=dp * (dpi/160)。可是dpi有可能會被人爲調整(好比幾部相同分辨率不一樣尺寸的手機的ppi可能分別是是430,440,450,那麼在Android系統中,可能dpi會所有指定爲480),因此仍是有可能在一些設備上出現適配問題。

  • 寬高限定符適配方案。

簡單地說,這個方案就是窮舉市面上全部的Android手機的寬高像素值。而後找到對應的文件夾使用下面的資源文件所對應的px值。

可是這方案有個缺陷,就是必須精確命中才行。好比1920x1080的手機就必定要找到1920x1080的限定符,不然就只能用統一的默認的dimens文件了。

因此容錯性過低,不推薦。

  • smallestWidth適配方案。

這個方案就是經過手機的寬度值來找到對應限定符文件夾下的資源文件,能夠看作寬高限定符屏幕適配方案的升級版。

假如咱們的設計圖寬爲360dp,那麼就建立values-sw360dp文件夾,並添加資源文件:

<?xml version="1.0" encoding="UTF-8"?>
<resources>
    <dimen name="dp_1">1dp</dimen>
    <dimen name="dp_2">2dp</dimen>
    <dimen name="dp_3">3dp</dimen>
    ...
    <dimen name="dp_359">359dp</dimen>
    <dimen name="dp_360">360dp</dimen>
</resources>

很簡單,分爲360份,而後咱們實際寫佈局文件的時候,直接引用對應的dimen值便可。

而後新建其餘設備寬度的文件夾,並在每一個文件夾裏添加對應的資源文件,這裏以400dp爲例:

├── src/main
│   ├── res
│   ├── ├──values
│   ├── ├──values-sw320dp
│   ├── ├──values-sw400dp
│   ├── ├──...
│   ├── ├──values-sw640dp

<?xml version="1.0" encoding="UTF-8"?>
<resources>
    <dimen name="dp_1">1.1111dp</dimen>
    <dimen name="dp_2">2.2222dp</dimen>
    <dimen name="dp_3">3.3333dp</dimen>
    <dimen name="dp_4">4.4444dp</dimen>
    ...
    <dimen name="dp_359">398.8889dp</dimen>
    <dimen name="dp_360">400.0000dp</dimen>
</resources>

也就是說,全部的設備都分爲360份了,這樣就能保證在不一樣寬度設備上都能有差很少的效果。

若是咱們的設備寬度爲400dp,那麼就會調用values-sw400dp對應的資源文件,若是找不到,就會向下查找。好比咱們寬度是402dp,找不到對應的,就會向上找到400dp對應的資源文件,因此也有比較好的容錯性。也是一個比較好的適配方案。

固然這種重複性工做確定不須要咱們本身手動去實現,有專門的插件能夠生成相應的文件和文件夾,這裏也推薦一個:https://github.com/ladingwu/dimens_sw

  • 今日頭條適配方案。

這個你們應該都很熟悉了,主要是經過動態修改density值來保證全部設備的屏幕寬度都是固定的dp值。用到的公式就是px = density * dp

好比設計圖寬爲360dp,咱們只要保證全部設備的寬度都是360dp就能適配了。而寬度的px值咱們是已知的,因此就是要修改這個 density 值來完成咱們的適配目的。具體代碼我就不貼了,網上不少。

這種方案侵入性低,使用方便,是個不錯的適配方案。

Flutter

Flutter是 Google 推出並開源的移動應用開發框架,開發者能夠經過 Dart 語言開發 App,一套代碼同時運行在 iOS 和 Android 平臺。

Flutter框架如今也是特別火,實際運用到不少的大廠項目,好比閒魚今日頭條。它相對於Android實際上是另一套APP架構了,它沒有基於系統自己的渲染引擎,而是app中自帶Skia引擎,虛擬機也是使用的Dart虛擬機。

主要有如下幾個特色:

  • 跨平臺:如今flutter至少能夠跨5種平臺,常見的平臺:MacOS,Windows ,Linux ,Android ,iOS ,到目前爲止,Flutter算是支持平臺最多的框架了。良好的跨平臺性,大大減小了開發成本。

  • 絲滑般的體驗:使用Flutter內置的Material Design(android風格)和Cupertino(ios風格)風格組件,以及豐富的motion API,平滑而天然的滑動效果和平臺感知,爲用戶帶來全新的體驗。

  • 響應式框架:使用一系列基礎組件和響應式框架,能夠輕鬆構建用戶界面。使用功能強大且靈活的API能夠實現複雜的界面效果。

  • 支持插件:使用插件能夠訪問平臺本地API,如相機,藍牙,WIFI等等。藉助現有的Java,swift ,object c , c++代碼實現對原生系統的調用。

  • 60fps超高性能:Flutter編寫的應用能夠達到60fps(每秒傳輸幀數)。Flutter採用GPU渲染技術,因此性能很好。徹底能夠勝任遊戲開發。

Jetpack Compose

Jetpack Compose 是用於構建原生 Android 界面的新工具包

原來咱們的佈局文件都是寫在xml文件中的,如今提供了一種新的view構建方式,也就是Compose

它是一種聲明式UI,再也不使用xml,而是使用kotlin進行UI佈局。其實就跟咱們以前提到的一點,用java代碼去構建view同樣的效果。這樣就減小了xml解析的時間,提升了效率。

聲明式UI。指的是隻須要把界面聲明出來,不須要手動更新。好比咱們這裏的Compose只須要寫一遍,後續的UI改變會隨着變量自動更新。而傳統的xml佈局方式就沒法作到這一點,屬於命令式UI,須要咱們手動命令紙牌屋UI的修改。

官方也是宣稱有如下幾點優點

更少更直觀的代碼,更強大的功能,能提升開發速度。

最後貼一段代碼,感覺下Compose的寫法:

class MainActivity : AppCompatActivity() {
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContent {
      Greeting("Android")
    }
  }
}

@Composable
fun Greeting(name: String) {
    Text (text = "Hello $name!")
}
  

工具篇

Choreographer

嘿嘿,沒想到吧,上期咱們說過的Choreographer其實也是一個監控應用幀率的工具。它主要有如下特性:

  • 能獲取總體的幀率。
  • 能在線上使用。
  • 獲取的幀率幾乎是實時的。

主要原理就是利用postFrameCallback計算兩次繪製的間隔時間,簡單貼下代碼:

private long mLastFrameTime;
Choreographer.getInstance().postFrameCallback(new Choreographer.FrameCallback() {
    @Override
    public void doFrame(long frameTimeNanos) {
        if (mLastFrameTime == 0) {
            mLastFrameTime = frameTimeNanos;
        }
        float diff = (frameTimeNanos - mLastFrameTime) / 1000000.0f;//獲得毫秒,正常是 16.66 ms
        if (diff > 500) {
            double fps = (((double) (mFrameCount * 1000L)) / diff);
            mFrameCount = 0;
            mLastFrameTime = 0;
            Log.d("doFrame""doFrame: " + fps);
        } else {
            ++mFrameCount;
        }
        Choreographer.getInstance().postFrameCallback(this);
    }
});

想細細研究的能夠看看這個庫(https://github.com/friendlyrobotnyc/TinyDancer )

LayoutInspector/Android Device Monitor

LayoutInspectorAndroidStudio種的一個佈局檢查器,能夠經過Tools > Layout Inspector找到,他能夠檢查應用中的某個界面的視圖結構,可是沒法查看非調式狀態的應用。

若是要看其餘應用的佈局狀況,可使用Android Device Monitor,在Android Studio 3.1 之後,須要單獨從文件夾打開了:

android-sdk/tools/monitor

Systrace

Systrace是分析Android性能問題的神器,獲取Systrace文件的方式有兩種:

  • 一是 AndroidSDK/tools目錄下,經過 monitor.batAndroid Device Monitor可視化工具獲得。
  • 二是經過 python腳本獲取。

具體怎麼分析這裏就不細說了,下次能夠專門一篇文章講Systrace性能分析。

參考

https://www.jianshu.com/p/a4b8e4c5d9b0 https://time.geekbang.org/column/article/81049 https://juejin.cn/post/6844904047355363341 https://juejin.cn/post/6844904048068395015 https://blog.csdn.net/u013425527/article/details/97046401


感謝你們的閱讀,有一塊兒學習的小夥伴能夠關注下公衆號—碼上積木❤️

每日三問知識點/面試題,聚沙成塔。



在看你最好看


本文分享自微信公衆號 - 碼上積木(Lzjimu)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索