1. 程式人生 > >App效能優化之卡頓終極原因研究及總結

App效能優化之卡頓終極原因研究及總結

640?wx_fmt=gif

熱文導讀 | 點選標題閱讀

來源:CoorChice

https://www.jianshu.com/p/df4d5ec779c8

有什麼料?

從這篇文章中你能獲得這些內容:

知道setContentView()之後發生了什麼?
知道Android究竟是如何在螢幕上顯示我們期望的畫面的?
對Android的檢視架構有整體把握。
學會從根源處分析畫面卡頓的原因。
掌握如何編寫一個流暢的App的技巧。
從原始碼中學習Android的細想。
收穫兩張自制圖,幫助你理解Android的檢視架構。

640?wx_fmt=png

從setContentView()說起

public class AnalyzeViewFrameworkActivity extends Activity
 
{
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_analyze_view_framwork);
  }
}

上面這段程式碼想必Androider們大都已經不能再熟悉的更多了。但是你知道這樣寫了之後發生什麼了嗎?這個佈局到底被新增到哪了?我的天,知識點來了!

可能很多同學也知道這個佈局是被放到了一個叫做DecorView的父佈局裡,但是我還是要再說一遍。且看下圖。

640?wx_fmt=png

這個圖可能和夥伴們在書上或者網上常見的不太一樣,為什麼不太一樣呢?因為是我自己畫的,哈哈哈…
下面就來看著圖捋一捋Android最基本的檢視框架。

PhoneWindow

估計很多同學都知道,每一個Activity都擁有一個Window物件的例項。這個例項實際是PhoneWindow型別的。那麼PhoneWindow從名字很容易看出,它應該是Window的兒子(即子類)!

知識點:每一個Activity都有一個PhoneWindow物件。

那麼,PhoneWindow有什麼用呢?它在Activity充當什麼角色呢?下面我就姑且把PhoneWindow等同於Window來稱呼吧。

Window從字面看它是一個視窗,意思和PC上的視窗概念有點像。但也不是那麼準確。看圖說。可以看到,我們要顯示的佈局是被放到它的屬性mDecor中的,這個mDecor就是DecorView的一個例項。下面會專門擼DecorView,現在先把關注點放到Window上。Window還有一個比較重要的屬性mWindowManager,它是WindowManager(這是個介面)的一個實現類的一個例項。我們平時通過getWindowManager()方法獲得的東西就是這個mWindowManager。顧名思義,它是Window的管理者,負責管理著視窗及其中顯示的內容。它的實際實現類是WindowManagerImpl。可能童鞋們現在正在PhoneWindow中尋找著這個mWindowManager是在哪裡例項化的,是不是上下來回滾動著這個類都找不見?STOP!mWindowManager是在它爹那裡就例項化好的。下面程式碼是在Window.java中的。

public void setWindowManager(WindowManager wm, 
    IBinder appToken, 
    String appName, 
    boolean hardwareAccelerated)
 
{
        ...
        if (wm == null) {
            wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
            //獲取了一個WindowManager
        }
        mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
        //通過這裡我們可以知道,上面獲取到的wm實際是WindowManagerImpl型別的。
    }

通過上面的介紹,我們已經知道了Window中有負責承載佈局的DecorView,有負責管理的WindowManager(事實上它只是個代理,後面會講它代理的是誰)。

DecorView

前面提到過,在Activity的onCreate()中通過setContentView()設定的佈局實際是被放到DecorView中的。我們在圖中找到DecorView。

從圖中可以看到,DecorView繼承了FrameLayout,並且一般情況下,它會在先新增一個預設的佈局。比如DecorCaptionView,它是從上到下放置自己的子佈局的,相當於一個LinearLayout。通常它會有一個標題欄,然後有一個容納內容的mContentRoot,這個佈局的型別視情況而定。我們希望顯示的佈局就是放到了mContentRoot中。

知識點:通過setContentView()設定的佈局是被放到DecorView中,DecorView是檢視樹的最頂層。

WindowManager

前面已經提到過,WindowManager在Window中具有很重要的作用。我們先在圖中找到它。這裡需要先說明一點,在PhoneWindow中的mWindowManager實際是WindowManagerImpl型別的。WindowManagerImpl自然就是介面WindowManager的一個實現類嘍。這一點是我沒有在圖中反映的。

WindowManager是在Activity執行attach()時被建立的,attach()方法是在onCreate()之前被呼叫的。
Activity.java

final void attach(Context context, ActivityThread aThread,
    Instrumentation instr, IBinder token, int ident,
    Application application, Intent intent, ActivityInfo info,
    CharSequence title, Activity parent, String id,
    NonConfigurationInstances lastNonConfigurationInstances,
    Configuration config, String referrer, IVoiceInteractor voiceInteractor,
    Window window){
        ...
        mWindow = new PhoneWindow(thiswindow);
        //建立Window
        ...
        mWindow.setWindowManager(
         (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
         mToken, mComponent.flattenToString(),
         (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
        //注意!這裡就是在建立WindowManager。
        //這個方法在前面已經說過了。
        if (mParent != null) {
           mWindow.setContainer(mParent.getWindow());
        }
        mWindowManager = mWindow.getWindowManager();
            }

繼續看圖。WindowManagerImpl持有了PhoneWindow的引用,因此它可以對PhoneWindow進行管理。同時它還持有一個非常重要的引用mGlobal。這個mGlobal指向一個WindowManagerGlobal型別的單例物件,這個單例每個應用程式只有唯一的一個。在圖中,我說明了WindowManagerGlobal維護了本應用程式內所有Window的DecorView,以及與每一個DecorView對應關聯的ViewRootImpl。這也就是為什麼我前面提到過,WindowManager只是一個代理,實際的管理功能是通過WindowManagerGlobal實現的。我們來看個原始碼的例子就比較清晰了。開始啦!

WimdowManagerImpl.java

public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params{
    ...
    mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
    //實際是通過WindowManagerGlobal實現的。
}

從上面的程式碼可以看出,WindowManagerImpl確實只是WindowManagerGlobal的一個代理而已。同時,上面這個方法在整個Android的檢視框架流程中十分的重要。我們知道,在Activity執行onResume()後介面就要開始渲染了。原因是在onResume()時,會呼叫WindowManager的addView()方法(實際最後呼叫的是WindowManagerGlobal的addView()方法),把檢視新增到視窗上。
ActivityThread.java

final void handleResumeActivity(IBinder token,
    boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason)
 
{
    ...
    ViewManager wm = a.getWindowManager();
    //獲得WindowManager,實際是WindowManagerImpl
    ...
    wm.addView(decor, l);
    //新增檢視
    ...
    wm.updateViewLayout(decor, l);
    //需要重新整理的時候會走這裡
    ...
}

從上面可以看到,當Activity執行onResume()的時候就會新增檢視,或者重新整理檢視。需要解釋一點:WindowManager實現了ViewManager介面。

如圖中所說,WindowManagerGlobal呼叫addView()的時候會把DecorView新增到它維護的陣列中去,並且會建立另一個關鍵且極其重要的ViewRootImpl(這個必須要專門講一下)型別的物件,並且也會把它存到一個數組中維護。
WindowManagerGlobal.java

public void addView(View view, ViewGroup.LayoutParams params,
    Display display, Window parentWindow
{
    ...
    root = new ViewRootImpl(view.getContext(), display);
    //重要角色登場
    view.setLayoutParams(wparams);
    mViews.add(view);
    mRoots.add(root);
    //儲存起來維護
    mParams.add(wparams);
    ...
    root.setView(view, wparams, panelParentView);
    //設定必要屬性view是DecorView,panelParentView是PhoneWindow
    ...
}

可以看出ViewRootImpl是在Activity執行onResume()的時候才被建立的,並且此時才把DecorView傳進去讓它管理。

知識點:WindowManager是在onCreate()時被建立。它對視窗的管理能力實際是通過WindowManagerGlobal實現的。在onResume()是檢視才通過WindowManager被新增到視窗上。

ViewRootImpl

ViewRootImpl能夠和系統的WindowManagerService進行互動,並且管理著DecorView的繪製和視窗狀態。非常的重要。趕緊在圖中找到對應位置吧!

ViewRootImpl並不是一個View,而是負責管理檢視的。它配合系統來完成對一個Window內的檢視樹的管理。從圖中也可以看到,它持有了DecorView的引用,並且檢視樹它是檢視樹繪製的起點。因此,ViewRootImpl會稍微複雜一點,需要我們更深入的去了解,在圖中我標出了它比較重要的組成Surface和Choreographer等都會在後面提到。

到此,我們已經一起把第一張圖擼了一遍了,現在童鞋們因該對Android檢視框架有了大致的瞭解。下面將更進一步的去了解Android的繪製機制。

App總是卡頓到底是什麼原因?

下面將會詳細的講解為什麼我們設定的檢視能夠被繪製到螢幕上?這中間究竟隱藏著怎樣的離奇?看完之後,你自然就能夠從根源知道為什麼你的App會那麼卡,以及開始有思路著手解決這些卡頓。

640?wx_fmt=png

同樣用一張圖來展示這個過程。由於Android繪製機制確實有點複雜,所以第一眼看到的時候你的內心中可能蹦騰了一萬隻草泥馬