自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

用兩張圖告訴你,為什么你的App會卡頓?

移動開發(fā) Android
Window從字面看它是一個窗口,意思和PC上的窗口概念有點像。但也不是那么準確??磮D說??梢钥吹剑覀円@示的布局是被放到它的屬性mDecor中的,這個mDecor就是DecorView的一個實例。下面會專門擼DecorView,現(xiàn)在先把關(guān)注點放到Window上。Window還有一個比較重要的屬性mWindowManager,它是WindowManager(這是個接口)的一個實現(xiàn)類的一個實例。我們平時通過getWindowManager()方法獲得的東西就是這個mWindowManager。

[[192710]]

有什么料?

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

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

[[192711]] 

從setContentView()說起

  1. public class AnalyzeViewFrameworkActivity extends Activity { 
  2.   @Override 
  3.   protected void onCreate(Bundle savedInstanceState) { 
  4.     super.onCreate(savedInstanceState); 
  5.     setContentView(R.layout.activity_analyze_view_framwork); 
  6.   } 
  7.  

上面這段代碼想必Androider們大都已經(jīng)不能再熟悉的更多了。但是你知道這樣寫了之后發(fā)生什么了嗎?這個布局到底被添加到哪了?我的天,知識點來了!

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

 

這個圖可能和伙伴們在書上或者網(wǎng)上常見的不太一樣,為什么不太一樣呢?因為是我自己畫的,哈哈哈...

下面就來看著圖捋一捋Android最基本的視圖框架。

PhoneWindow

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

知識點:每一個Activity都有一個PhoneWindow對象。

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

[[192712]] 

Window從字面看它是一個窗口,意思和PC上的窗口概念有點像。但也不是那么準確??磮D說。可以看到,我們要顯示的布局是被放到它的屬性mDecor中的,這個mDecor就是DecorView的一個實例。下面會專門擼DecorView,現(xiàn)在先把關(guān)注點放到Window上。Window還有一個比較重要的屬性mWindowManager,它是WindowManager(這是個接口)的一個實現(xiàn)類的一個實例。我們平時通過getWindowManager()方法獲得的東西就是這個mWindowManager。顧名思義,它是Window的管理者,負責管理著窗口及其中顯示的內(nèi)容。它的實際實現(xiàn)類是WindowManagerImpl??赡芡瑐儸F(xiàn)在正在PhoneWindow中尋找著這個mWindowManager是在哪里實例化的,是不是上下來回滾動著這個類都找不見?STOP!mWindowManager是在它爹那里就實例化好的。下面代碼是在Window.java中的。

  1. public void setWindowManager(WindowManager wm,  
  2.     IBinder appToken,  
  3.     String appName,  
  4.     boolean hardwareAccelerated) { 
  5.         ... 
  6.         if (wm == null) { 
  7.             wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE); 
  8.             //獲取了一個WindowManager 
  9.         } 
  10.         mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this); 
  11.         //通過這里我們可以知道,上面獲取到的wm實際是WindowManagerImpl類型的。 
  12.     }  

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

DecorView

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

從圖中可以看到,DecorView繼承了FrameLayout,并且一般情況下,它會在先添加一個預設的布局。比如DecorCaptionView,它是從上到下放置自己的子布局的,相當于一個LinearLayout。通常它會有一個標題欄,然后有一個容納內(nèi)容的mContentRoot,這個布局的類型視情況而定。我們希望顯示的布局就是放到了mContentRoot中。

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

WindowManager

前面已經(jīng)提到過,WindowManager在Window中具有很重要的作用。我們先在圖中找到它。這里需要先說明一點,在PhoneWindow中的mWindowManager實際是WindowManagerImpl類型的。WindowManagerImpl自然就是接口WindowManager的一個實現(xiàn)類嘍。這一點是我沒有在圖中反映的。

WindowManager是在Activity執(zhí)行attach()時被創(chuàng)建的,attach()方法是在onCreate()之前被調(diào)用的。關(guān)于Activity的創(chuàng)建可以看看我的這篇:【可能是史上最簡單的!一張圖3分鐘讓你明白Activity啟動流程,不看后悔!http://www.jianshu.com/p/9ecea420eb52】。

Activity.java

  1. final void attach(Context context, ActivityThread aThread, 
  2.     Instrumentation instr, IBinder token, int ident, 
  3.     Application application, Intent intent, ActivityInfo info, 
  4.     CharSequence title, Activity parent, String id, 
  5.     NonConfigurationInstances lastNonConfigurationInstances, 
  6.     Configuration config, String referrer, IVoiceInteractor voiceInteractor, 
  7.     Window window){ 
  8.         ... 
  9.         mWindow = new PhoneWindow(this, window); 
  10.         //創(chuàng)建Window 
  11.         ... 
  12.         mWindow.setWindowManager( 
  13.          (WindowManager)context.getSystemService(Context.WINDOW_SERVICE), 
  14.          mToken, mComponent.flattenToString(), 
  15.          (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0); 
  16.         //注意!這里就是在創(chuàng)建WindowManager。 
  17.         //這個方法在前面已經(jīng)說過了。 
  18.         if (mParent != null) { 
  19.            mWindow.setContainer(mParent.getWindow()); 
  20.         } 
  21.         mWindowManager = mWindow.getWindowManager(); 
  22.             }  

繼續(xù)看圖。WindowManagerImpl持有了PhoneWindow的引用,因此它可以對PhoneWindow進行管理。同時它還持有一個非常重要的引用mGlobal。這個mGlobal指向一個WindowManagerGlobal類型的單例對象,這個單例每個應用程序只有唯一的一個。在圖中,我說明了WindowManagerGlobal維護了本應用程序內(nèi)所有Window的DecorView,以及與每一個DecorView對應關(guān)聯(lián)的ViewRootImpl。這也就是為什么我前面提到過,WindowManager只是一個代理,實際的管理功能是通過WindowManagerGlobal實現(xiàn)的。我們來看個源碼的例子就比較清晰了。開始啦!

[[192713]] 

WimdowManagerImpl.java

  1. public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) { 
  2.     ... 
  3.     mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow); 
  4.     //實際是通過WindowManagerGlobal實現(xiàn)的。 
  5.  

從上面的代碼可以看出,WindowManagerImpl確實只是WindowManagerGlobal的一個代理而已。同時,上面這個方法在整個Android的視圖框架流程中十分的重要。我們知道,在Activity執(zhí)行onResume()后界面就要開始渲染了。原因是在onResume()時,會調(diào)用WindowManager的addView()方法(實際最后調(diào)用的是WindowManagerGlobal的addView()方法),把視圖添加到窗口上。結(jié)合我的這篇【可能是史上最簡單的!一張圖3分鐘讓你明白Activity啟動流程,不看后悔!http://www.jianshu.com/p/9ecea420eb52】看,可以幫助你更好的理解Android的視圖框架。

ActivityThread.java

  1. final void handleResumeActivity(IBinder token, 
  2.     boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) { 
  3.     ... 
  4.     ViewManager wm = a.getWindowManager(); 
  5.     //獲得WindowManager,實際是WindowManagerImpl 
  6.     ... 
  7.     wm.addView(decor, l); 
  8.     //添加視圖 
  9.     ... 
  10.     wm.updateViewLayout(decor, l); 
  11.     //需要刷新的時候會走這里 
  12.     ... 
  13.  

從上面可以看到,當Activity執(zhí)行onResume()的時候就會添加視圖,或者刷新視圖。需要解釋一點:WindowManager實現(xiàn)了ViewManager接口。

如圖中所說,WindowManagerGlobal調(diào)用addView()的時候會把DecorView添加到它維護的數(shù)組中去,并且會創(chuàng)建另一個關(guān)鍵且極其重要的ViewRootImpl(這個必須要專門講一下)類型的對象,并且也會把它存到一個數(shù)組中維護。

WindowManagerGlobal.java

  1. public void addView(View view, ViewGroup.LayoutParams params, 
  2.     Display display, Window parentWindow) { 
  3.     ... 
  4.     root = new ViewRootImpl(view.getContext(), display); 
  5.     //重要角色登場 
  6.     view.setLayoutParams(wparams); 
  7.     mViews.add(view); 
  8.     mRoots.add(root); 
  9.     //保存起來維護 
  10.     mParams.add(wparams); 
  11.     ... 
  12.     root.setView(view, wparams, panelParentView); 
  13.     //設置必要屬性view是DecorView,panelParentView是PhoneWindow 
  14.     ... 
  15.  

可以看出ViewRootImpl是在Activity執(zhí)行onResume()的時候才被創(chuàng)建的,并且此時才把DecorView傳進去讓它管理。

知識點:WindowManager是在onCreate()時被創(chuàng)建。它對窗口的管理能力實際是通過WindowManagerGlobal實現(xiàn)的。在onResume()是視圖才通過WindowManager被添加到窗口上。

ViewRootImpl

ViewRootImpl能夠和系統(tǒng)的WindowManagerService進行交互,并且管理著DecorView的繪制和窗口狀態(tài)。非常的重要。趕緊在圖中找到對應位置吧!

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

到此,我們已經(jīng)一起把第一張圖擼了一遍了,現(xiàn)在童鞋們因該對Android視圖框架有了大致的了解。下面將更進一步的去了解Android的繪制機制。

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

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

 

同樣用一張圖來展示這個過程。由于Android繪制機制確實有點復雜,所以第一眼看到的時候你的內(nèi)心中可能蹦騰了一萬只草泥馬😂。不要怕!我們從源頭開始,一點一點的梳理這個看似復雜的繪制機制。為什么說看似復雜呢?因為這個過程只需要幾分鐘。Just Do It!

CPU、GPU是搞什么鬼的?

整天聽到CPU、GPU的,你知道他們是干什么的嗎?這里簡單的提一下,幫助理解后面的內(nèi)容。

在Android的繪制架構(gòu)中,CPU主要負責了視圖的測量、布局、記錄、把內(nèi)容計算成Polygons多邊形或者Texture紋理,而GPU主要負責把Polygons或者Textture進行Rasterization柵格化,這樣才能在屏幕上成像。在使用硬件加速后,GPU會分擔CPU的計算任務,而CPU會專注處理邏輯,這樣減輕CPU的負擔,使得整個系統(tǒng)效率更高。

 

RefreshRate刷新率和FrameRate幀率

RefreshRate刷新率是屏幕每秒刷新的次數(shù),是一個與硬件有關(guān)的固定值。在Android平臺上,這個值一般為60HZ,即屏幕每秒刷新60次。

FrameRate幀率是每秒繪制的幀數(shù)。通常只要幀數(shù)和刷新率保持一致,就能夠看到流暢的畫面。在Android平臺,我們應該盡量維持60FPS的幀率。但有時候由于視圖的復雜,它們可能就會出現(xiàn)不一致的情況。

 

 

如圖,當幀率小于刷新率時,比如圖中的30FPS < 60HZ,就會出現(xiàn)相鄰兩幀看到的是同一個畫面,這就造成了卡頓。這就是為什么我們總會說,要盡量保證一幀畫面能夠在16ms內(nèi)繪制完成,就是為了和屏幕的刷新率保持同步。

下面將會介紹Android是如何來確保刷新率和幀率保持同步的。

Vsync(垂直同步)是什么?

你可能在游戲的設置中見過Vsync,開啟它通常能夠提高游戲性能。在Android中,同樣使用Vsync垂直同步來提高顯示性能。它能夠使幀率FrameRate和硬件的RefreshRate刷新強制保持一致。

HWComposer與Vsync不得不說的事

看圖啦看圖啦。首先在最左邊我們看到有個叫HWComposer的類,這是一個c++編寫的類。它Android系統(tǒng)初始化時就被創(chuàng)建,然后開始配合硬件產(chǎn)生Vsync信號,也就是圖中的HW_Vsync信號。當然它不是一直不停的在產(chǎn)生,這樣會導致Vsync信號的接收者不停的接收到繪制、渲染命令,即使它們并不需要,這樣會帶來嚴重的性能損耗,因為進行了很多無用的繪制。所以它被設計設計成能夠喚醒和睡眠的。這使得HWComposer在需要時才產(chǎn)生Vsync信號(比如當屏幕上的內(nèi)容需要改變時),不需要時進入睡眠狀態(tài)(比如當屏幕上的內(nèi)容保持不變時,此時屏幕每次刷新都是顯示緩沖區(qū)里沒發(fā)生變化的內(nèi)容)。

如圖,Vsync的兩個接收者,一個是SurfaceFlinger(負責合成各個Surface),一個是Choreographer(負責控制視圖的繪制)。我們稍后再介紹,現(xiàn)在先知道它們是干什么的就行了。

Vsync offset機制

為了提高效率,盡量減少卡頓,在Android 4.1時引入了Vsync機制,并在隨后的4.4版本中加入Vsync offset偏移機制。

 

圖1. 為4.1時期的Vsync機制。可以看到,當一個Vsync信號到來時,SurfaceFlinger和UI繪制進程會同時啟動,導致它們競爭CPU資源,而CPU分配資源會耗費時間,著降低系統(tǒng)性能。同時當收到一個Vsync信號時,第N幀開始繪制。等再收到一個Vsync信號時,第N幀才被SurfaceFlinger合成。而需要顯示到屏幕上,需要等都第三個Vsync信號。這是比較低效率。于是才有了圖2. 4.4版本加入的Vsync offset機制。

圖2. Google加入Vsync offset機制后,原本的HW_Vsync信號會經(jīng)過DispSync會分成Vsync和SF_Vsync兩個虛擬化的Vsync信號。其中Vsync信號會發(fā)送到Choreographer中,而SF_Vsync會發(fā)送到SurfaceFlinger中。理論上只要phase_app和phase_sf這兩個偏移參數(shù)設置合理,在繪制階段消耗的時間控制好,那么畫面就會像圖2中的前幾幀那樣有序流暢的進行。理想總是美好的。實際上很難一直維持這種有序和流暢,比如frame_3是比較復雜的一幀,它的繪制完成的時間超過了SurfaceFlinger開始合成的時間,所以它必須要等到下一個Vsync信號到來時才能被合成。這樣便造成了一幀的丟失。但即使是這樣,如你所見,加入了Vsync offset機制后,繪制效率還是提高了很多。

從圖中可以看到,Vsync和SF_Vsync的偏移量分別由phase_app和phase_sf控制,這兩個值是可以調(diào)節(jié)的,默認為0,可為負值。你只需要找到BoardConfig.mk文件,就可以對這兩個值進行調(diào)節(jié)。

回到ViewRootImpl

前面介紹了幾個關(guān)鍵的概念,現(xiàn)在我們回到ViewRootImpl中去,在圖中找到ViewRootImpl的對應位置。

前面說過,ViewRootImpl控制著一個Window中的整個視圖樹的繪制。那它是如何進行控制的呢?一次繪制究竟是如何開始的呢?

 

[[192717]] 

在ViewRootImpl創(chuàng)建的時候,會獲取到前面提到過過的一個關(guān)鍵對象Choreographer。Choreographer在一個線程中僅存在一個實例,因此在UI線程只有一個Choreographer存在。也就說,通常情況下,它相當于一個應用中的單例。

在ViewRootImpl初始化時,會實現(xiàn)一個Choreographer.FrameCallback(這是一個Choreographer中的內(nèi)部類),并向Choreographer中post。顧名思義,F(xiàn)rameCallback會在每次接收到Vsync信號時被回調(diào)。

Choreographer.java

  1. public interface FrameCallback { 
  2.     public void doFrame(long frameTimeNanos); 
  3.     //一旦注冊到CallbackQueue中,那么 
  4.     //每次Choreographer接收到Vsync信號時都會回調(diào)。 
  5.     }  

FrameCallback一旦被注冊,那么每次收到Vsync信號時它都會被回調(diào)。利用它,我們可以實現(xiàn)會幀率的監(jiān)聽。

ViewRootImpl.java

  1. //這個方法只有在ViewRootImpl初始化時才會被調(diào)用 
  2. private void profileRendering(boolean enabled) { 
  3.     ... 
  4.     mRenderProfiler = new Choreographer.FrameCallback() { 
  5.     @Override 
  6.     public void doFrame(long frameTimeNanos) { 
  7.         ... 
  8.         scheduleTraversals(); 
  9.         //請求一個Vsync信號,后面還會提到這個方法 
  10.         mChoreographer.postFrameCallback(mRenderProfiler); 
  11.         //每次回調(diào)時,重新將FrameCallback post到Choreographer中 
  12.         ... 
  13.     } 
  14.     }; 
  15.     ... 
  16.     mChoreographer.postFrameCallback(mRenderProfiler); 
  17.     //將FrameCallback post到Choreographer中 
  18.     ... 
  19.  

上面代碼出現(xiàn)了一個重要方法scheduleTraversals()。下面我們看看它究竟為何重要。 ViewRootImpl.java

  1. void scheduleTraversals() { 
  2.     ... 
  3.     mChoreographer.postCallback( 
  4.         Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); 
  5.     //向Choreographer中post一個TraversalRunnable 
  6.     //這又是一個十分重要的對象 
  7.     ... 
  8.     }  

可以看出scheduleTraversals()每次調(diào)用時會向Choreographer中post一個TraversalRunnable,它會促使Choreographer去請求一個Vsync信號。所以這個方法的作用就是用來請求一次Vsync信號刷新界面的。事實上,你可以看到,在invalidate()、requestLayout()等操作中,都能夠看到它被調(diào)用。原因就是這些操作需要刷新界面,所以需要請求一個Vsync信號來出發(fā)新界面的繪制。

ViewRootImpl.java

  1. final class TraversalRunnable implements Runnable { 
  2.     @Override 
  3.     public void run() { 
  4.         doTraversal(); 
  5.         //開始遍歷視圖樹,這意味著開始繪制一幀內(nèi)容了 
  6.     } 
  7.  

從圖中可以看到,每當doTraversal()被調(diào)用時,一系列的測量、布局和繪制操作就開始了。在繪制時,會通過Surface來獲取一個Canvas內(nèi)存塊交給DecorView,用于視圖的繪制。整個View視圖的內(nèi)容都是被繪制到這個Canvas中。

Choreographer中的風起云涌

前面反復提到向Choreographer中post回調(diào),那么post過去發(fā)生了些什么呢?從圖中可以看到,所有的post操作最終都進入到postCallbackDelayedInternal()中。

[[192718]] 

Choreographer.java

  1. private void postCallbackDelayedInternal(int callbackType, 
  2.     Object action, Object token, long delayMillis) { 
  3.     ... 
  4.     synchronized (mLock) { 
  5.         final long now = SystemClock.uptimeMillis(); 
  6.         final long dueTime = now + delayMillis; 
  7.         mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token); 
  8.         //將Callback添加到CallbackQueue[]中 
  9.         if (dueTime <= now) { 
  10.             scheduleFrameLocked(now); 
  11.             //如果回調(diào)時間到了,請求一個Vsync信號 
  12.             //在接收到后會調(diào)用doFrame()回調(diào)這個Callback。 
  13.         } else { 
  14.             Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action); 
  15.             msg.arg1 = callbackType; 
  16.             msg.setAsynchronous(true); 
  17.             //異步消息,避免被攔截器攔截 
  18.             mHandler.sendMessageAtTime(msg, dueTime); 
  19.             //如果還沒到回調(diào)的時間,向FrameHandelr中發(fā)送 
  20.             //MSG_DO_SCHEDULE_CALLBACK消息 
  21.         } 
  22.     } 
  23.     ... 
  24.  

上面這段代碼會把post到Choreographer中的Callback添加到Callback[]中,并且當它因該被回調(diào)時,請求一個Vsync信號,在接收到下一個Vsync信號時回調(diào)這個Callback。如果沒有到回調(diào)的時間,則向FrameHandler中發(fā)送一個MSG_DO_SCHEDULE_CALLBACK消息,但最終還是會請求一個Vsync信號,然后回調(diào)這個Callback。

簡單提一下CallbackQueue:簡單說一下CallbackQueue。它和MessageQueue差不多,都是單鏈表結(jié)構(gòu)。在我的這篇【驚天秘密!從Thread開始,揭露Android線程通訊的詭計和主線程的陰謀http://www.jianshu.com/p/8862bd2b6a29】文章中,你能夠看到更多關(guān)于MessageQueue和Handler機制的內(nèi)容。不同的是它同時還是一個一維數(shù)組,下標表示Callback類型。事實上,算上每種類型的單鏈表結(jié)構(gòu),它更像是二維數(shù)組的樣子。簡單點描述,假設有一個MessageQueue[]數(shù)組,里面存了幾個MessageQueue。來看看它的創(chuàng)建你可能就明白,它是在Choreographer初始化時創(chuàng)建的。

  1. private Choreographer(Looper looper) { 
  2.     mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1]; 
  3.     //CALLBACK_LAST值為3。 
  4.     for (int i = 0; i <= CALLBACK_LAST; i++) { 
  5.         mCallbackQueues[i] = new CallbackQueue(); 
  6.     } 
  7.  

現(xiàn)在來看看前面代碼中調(diào)用的scheduleFrameLocked()是如何請求一個Vsync信號的。

  1. private void scheduleFrameLocked(long now) { 
  2.     ... 
  3.     //先判斷當前是不是在UI線程 
  4.     if (isRunningOnLooperThreadLocked()) { 
  5.         scheduleVsyncLocked(); 
  6.         //是UI線程就請求一個Vsync信號 
  7.     } else { 
  8.         Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC); 
  9.         msg.setAsynchronous(true); 
  10.         mHandler.sendMessageAtFrontOfQueue(msg); 
  11.         //不在UI線程向FrameHandler發(fā)送一個MSG_DO_SCHEDULE_VSYNC消息 
  12.         //來請求一個Vsync信號 
  13.     } 
  14.  
  15. private void scheduleVsyncLocked() { 
  16.     mDisplayEventReceiver.scheduleVsync(); 
  17.     //通過DisplayEventReceiver請求一個Vsync信號 
  18.     //這是個恨角色,待會兒會聊聊它。 
  19.     //MSG_DO_SCHEDULE_VSYNC消息也是通過調(diào)用這個方法請求Vsync信號的。 
  20.  

上面我們提到過,Choreographer在一個線程中只有一個。所以,如果在其它線程,需要通過Handler來切換到UI線程,然后再請求Vsync信號。

下面看看剛剛出場的mDisplayEventReceiver是個什么鬼?

[[192719]] 

 

  1. private final class FrameDisplayEventReceiver extends DisplayEventReceiver 
  2.     implements Runnable { 
  3.  
  4.     //這個方法用于接收Vsync信號 
  5.     public void onVsync(){ 
  6.         ... 
  7.         Message msg = Message.obtain(mHandler, this); 
  8.         msg.setAsynchronous(true); 
  9.         mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS); 
  10.         //這里并沒有設置消息的類型 
  11.         //其實就是默認為0,即MSG_DO_FRAME類型的消息 
  12.         //它其實就是通知Choreographer開始回調(diào)CallbackQueue[]中的Callback了 
  13.         //也就是開始繪制下一幀的內(nèi)容了 
  14.     } 
  15.  
  16.     //這個方法是在父類中的,寫在這方便看 
  17.     public void scheduleVsync() { 
  18.         ... 
  19.         nativeScheduleVsync(mReceiverPtr); 
  20.         //請求一個Vsync信號 
  21.     } 
  22.  

這給類功能比較明確,而且很重要! 

[[192720]] 

上面一直在說向FrameHandler中發(fā)消息,搞得神神秘秘的。接下來就來看看FrameHandler本尊。請在圖中找到對應位置哦。

  1. private final class FrameHandler extends Handler { 
  2.     public FrameHandler(Looper looper) { 
  3.         super(looper); 
  4.     } 
  5.  
  6.     @Override 
  7.     public void handleMessage(Message msg) { 
  8.         switch (msg.what) { 
  9.             case MSG_DO_FRAME: 
  10.                 //開始回調(diào)Callback,以開始繪制下一幀內(nèi)容 
  11.                 doFrame(System.nanoTime(), 0); 
  12.                 break; 
  13.             case MSG_DO_SCHEDULE_VSYNC: 
  14.                 //請求一個Vsync信號 
  15.                 doScheduleVsync(); 
  16.                 break; 
  17.             case MSG_DO_SCHEDULE_CALLBACK: 
  18.                 //實際也是請求一個Vsync信號 
  19.                 doScheduleCallback(msg.arg1); 
  20.                 break; 
  21.             } 
  22.         } 
  23.     }  

FrameHandler主要在UI線程處理3種類型的消息。

  • MSG_DO_FRAME:值為0。當接收到一個Vsync信號時會發(fā)送該種類型的消息,然后開始回調(diào)CallbackQueue[]中的Callback。比如上面說過,在ViewRootImpl有兩個重要的Callback,F(xiàn)rameCallback(請求Vsync并再次注冊回調(diào))和TraversalRunnable(執(zhí)行doTraversal()開始繪制界面)頻繁被注冊。
  • MSG_DO_SCHEDULE_VSYNC:值為1。當需要請求一個Vsync消息(即屏幕上的內(nèi)容需要更新時)會發(fā)送這個消息。接收到Vsync后,同上一步。
  • MSG_DO_SCHEDULE_CALLBACK:值為2。請求回調(diào)一個Callback。實際上會先請求一個Vsync信號,然后再發(fā)送MSG_DO_FRAME消息,然后再回調(diào)。

FrameHandler并不復雜,但在UI的繪制過程中具有重要的作用,所以一定要結(jié)合圖梳理下這個流程。

SurfaceFlinger和Surface簡單說

在介紹Vsync的時候,我們可能已經(jīng)看到了,現(xiàn)在Android系統(tǒng)會將HW_VSYNC虛擬化為兩個Vsync信號。一個是VSYNC,被發(fā)送給上面一直在講的Choreographer,用于觸發(fā)視圖樹的繪制渲染。另一個是SF_VSYNC,被發(fā)送給我接下來要講的SurfaceFlinger,用于觸發(fā)Surface的合成,即各個Window窗口畫面的合成。接下來我們就簡單的看下SurfaceFlinger和Surface。由于這部分基本是c++編寫的,我著重講原理。

隱藏在背后的Surface

平時同學們都知道,我們的視圖需要被繪制。那么它們被繪制到那了呢?也許很多童鞋腦海里立即浮現(xiàn)出一個詞:Canvas。但是,~沒錯!就是繪制到了Canvas上。那么Canvas又是怎么來的呢?是的,它可以New出來的。但是前面提到過,我們Window中的視圖樹都是被繪制到一個由Surface提供的Canvas上。忘了的童鞋面壁思過😄。 

[[192721]] 

Canvas實際代表了一塊內(nèi)存,用于儲存繪制出來的數(shù)據(jù)。在Canvas的構(gòu)造器中你可以看到:

  1. public Canvas() { 
  2.     ... 
  3.     mNativeCanvasWrapper = initRaster(null); 
  4.     //申請一塊內(nèi)存,并且返回該內(nèi)存的一個long類型的標記或者索引。 
  5.     ... 
  6.  

可以看到,Canvas實際主要就是持有了一塊用于繪制的內(nèi)存塊的索引long mNativeCanvasWrapper。每次繪制時就通過這個索引找到對應的內(nèi)存塊,然后將數(shù)據(jù)繪制到內(nèi)存中。比如:

  1. public void drawRect(@NonNull RectF rect, @NonNull Paint paint) { 
  2.     native_drawRect(mNativeCanvasWrapper, 
  3.         rect.left, rect.top, rect.right, rect.bottom, paint.getNativeInstance()); 
  4.     //在mNativeCanvasWrapper標記的內(nèi)存中繪制一個矩形。 
  5.     }  

簡單的說一下。Android繪制圖形是通過圖形庫Skia(主要針對2D)或OpenGL(主要針對3D)進行。圖形庫是個什么概念?就好比你在PC上用畫板畫圖,此時畫板就相當于Android中的圖形庫,它提供了一系列標準化的工具供我們畫圖使用。比如我們drawRect()實際就是操作圖形庫在內(nèi)存上寫入了一個矩形的數(shù)據(jù)。

扯多了,我們繼續(xù)回到Surface上。當ViewRootImpl執(zhí)行到draw()方法(即開始繪制圖形數(shù)據(jù)了),會根據(jù)是否開啟了硬件(從Android 4.0開始默認是開啟的)加速來決定是使用CPU軟繪制還是使用GPU硬繪制。如果使用軟繪制,圖形數(shù)據(jù)會繪制在Surface默認的CompatibleCanvas上(和普通Canvas的唯一區(qū)別就是對Matrix進行了處理,提高在不同設備上的兼容性)。如果使用了硬繪制,圖形數(shù)據(jù)會被繪制在DisplayListCanvas上。DisplayListCanvas會通過GPU使用openGL圖形庫進行繪制,因此具有更高的效率。

前面也簡單說了一下,每一個Window都會有一個自己的Surface,也就是說一個應用程序中會存在多個Surface。通過上面的講解,童鞋們也都知道了Surface的作用就是管理用于繪制視圖樹的Canvas的。這個Surface是和SurfaceFlinger共享,從它實現(xiàn)了Parcelable接口也可以才想到它會被序列化傳遞。事實上,Surface中的繪制數(shù)據(jù)是通過匿名共享內(nèi)存的方式和SurfaceFlinger共享的,這樣SurfaceFlinger可以根據(jù)不同的Surface,找到它所對應的內(nèi)存區(qū)域中的繪制數(shù)據(jù),然后進行合成。

合成師SurfaceFlinger

SurfaceFlinger是系統(tǒng)的一個服務。前面也一直在提到它專門負責把每個Surface中的內(nèi)容合成緩存,以待顯示到屏幕上。SurfaceFlinger在合成Surface時是根據(jù)Surface的Z-order順序一層一層進行。比如一個Dialog的Surface就會在Activity的Surface上面。然后這個東西不多提了。

終于可以說說你的App為什么這么卡的原因了

通過對Android繪制機制的了解,我們知道造成應用卡頓的根源就在于16ms內(nèi)不能完成繪制渲染合成過程,因為Android平臺的硬件刷新率為60HZ,大概就是16ms刷新一次。如果沒能在16ms內(nèi)完成這個過程,就會使屏幕重復顯示上一幀的內(nèi)容,即造成了卡頓。在這16ms內(nèi),需要完成視圖樹的所有測量、布局、繪制渲染及合成。而我們的優(yōu)化工作主要就是針對這個過程的。

復雜的視圖樹

如果視圖樹復雜,會使整個Traversal過程變長。因此,我們在開發(fā)過程中要控制視圖樹的復雜程度。減少不必要的層級嵌套。比如使用RelativeLayout可以減少復雜布局的嵌套。比如使用【震驚!這個控件絕對值得收藏。輕松實現(xiàn)圓角、文字描邊、狀態(tài)指示等效果http://www.jianshu.com/p/cfe18cbc6924】😄,這個控件可以減少既需要顯示文字,又需要圖片和特殊背景的需求的布局復雜程度,所有的東西由一個控件實現(xiàn)。

頻繁的requestlayout()

如果頻繁的觸發(fā)requestLayout(),就可能會導致在一幀的周期內(nèi),頻繁的發(fā)生布局計算,這也會導致整個Traversal過程變長。有的ViewGroup類型的控件,比如RelativeLayout,在一幀的周期內(nèi)會通過兩次layout()操作來計算確認子View的位置,這種少量的操作并不會引起能夠被注意到的性能問題。但是如果在一幀的周期內(nèi)頻繁的發(fā)生layout()計算,就會導致嚴重的性能,每次計算都是要消耗時間的!而requestLayout()操作,會向ViewRootImpl中一個名為mLayoutRequesters的List集合里添加需要重新Layout的View,這些View將在下一幀中全部重新layout()一遍。通常在一個控件加載之后,如果沒什么變化的話,它不會在每次的刷新中都重新layout()一次,因為這是一個費時的計算過程。所以,如果每一幀都有許多View需要進行l(wèi)ayout()操作,可想而知你的界面將會卡到爆!卡到爆!需要注意,setLayoutParams()最終也會調(diào)用requestLayout(),所以也不能爛用!同學們在寫代碼的過程中一定要謹慎注意那些可能引起requestLayout()的地方啊!

UI線程被阻塞

如果UI線程受到阻塞,顯而易見的是,我們的Traversal過程也將受阻塞!畫面卡頓是妥妥的發(fā)生啊。這就是為什么大家一直在強調(diào)不要在UI線程做耗時操作的原因。通常UI線程的阻塞和以下原因脫不了關(guān)系。

在UI線程中進行IO讀寫數(shù)據(jù)的操作。這是一個很費時的過程好嗎?千萬別這么干。如果不想獲得一個卡到爆的App的話,把IO操作統(tǒng)統(tǒng)放到子線程中去。

在UI線程中進行復雜的運算操作。運算本身是一個耗時的操作,當然簡單的運算幾乎瞬間完成,所以不會讓你感受到它在耗時。但是對于十分復雜的運算,對時間的消耗是十分辣眼睛的!如果不想獲得一個卡到爆的App的話,把復雜的運算操作放到子線程中去。

在UI線程中進行復雜的數(shù)據(jù)處理。我說的是比如數(shù)據(jù)的加密、解密、編碼等等。這些操作都需要進行復雜運算,特別是在數(shù)據(jù)比較復雜的時候。如果不想獲得一個卡到爆的App的話,把復雜數(shù)據(jù)的處理工作放到子線程中去。

頻繁的發(fā)生GC,導致UI線程被頻繁中斷。在Java中,發(fā)生GC(垃圾回收)意味著Stop-The-World,就是說其它線程全部會被暫停啊。好可怕!正常的GC導致偶然的畫面卡頓是可以接受的,但是頻繁發(fā)生就讓人很蛋疼了!頻繁GC的罪魁禍首是內(nèi)存抖動,這個時候就需要看下我的這篇【Android內(nèi)存基礎——內(nèi)存抖動http://www.jianshu.com/p/69e6f894c698】文章了。簡單的說就是在短時間內(nèi)頻繁的創(chuàng)建大量對象,導致達到GC的閥值,然后GC就發(fā)生了。如果不想獲得一個卡到爆的App的話,把內(nèi)存的管理做好,即使這是Java。

故意阻塞UI線程。好吧,相信沒人會這么干吧。比如sleep()一下?

總結(jié)

抽出空余時間寫文章分享需要動力,還請各位看官動動小手點個贊,鼓勵下嘍😄

我一直在不定期的創(chuàng)作新的干貨,想要上車只需進到我的個人主頁點個關(guān)注就好了哦。發(fā)車嘍~

整篇下來,相信童鞋對Android的繪制機制也有了一個比較全面的了解?,F(xiàn)在回過頭來再寫代碼時是不是有種知根知底的自信呢?😄

參考鏈接

  1. Implementing VSYNC:https://source.android.com/devices/graphics/implement-vsync
  2. SurfaceFlinger and Hardware Composer:https://source.android.com/devices/graphics/arch-sf-hwc
  3. Surface and SurfaceHolder:https://source.android.com/devices/graphics/arch-sh
  4. Implementing the Hardware Composer HAL:https://source.android.com/devices/graphics/implement-hwc
  5. 可能是史上最簡單的!一張圖3分鐘讓你明白Activity啟動流程,不看后悔!http://www.jianshu.com/p/9ecea420eb52
  6. 驚天秘密!從Thread開始,揭露Android線程通訊的詭計和主線程的陰謀http://www.jianshu.com/p/8862bd2b6a29
  7. 震驚!這個控件絕對值得收藏。輕松實現(xiàn)圓角、文字描邊、狀態(tài)指示等效果http://www.jianshu.com/p/cfe18cbc6924
  8. Android內(nèi)存基礎——內(nèi)存抖動http://www.jianshu.com/p/69e6f894c698
  9. Android性能優(yōu)化之渲染篇http://hukai.me/android-performance-render/
  10. Android硬件加速原理與實現(xiàn)簡介http://tech.meituan.com/hardware-accelerate.html
  11. Android SurfaceFlinger對VSync信號的處理過程分析http://blog.csdn.net/yangwen123/article/details/17001405
  12. Android Vsync 原理http://www.10tiao.com/html/431/201601/401709603/1.html
  13. Android Choreographer 源碼分析http://www.jianshu.com/p/996bca12eb1d?utm_campaign=hugo&utm_medium=reader_share&utm_content=note
  14. Android應用程序窗口(Activity)的視圖對象(View)的創(chuàng)建過程分析:http://blog.csdn.net/luoshengyang/article/details/8245546
  15. Android 4.4(KitKat)中VSync信號的虛擬化http://blog.csdn.net/jinzhuojun/article/details/17293325
  16. Understanding necessity of Android VSYNC signals:http://stackoverflow.com/questions/27947848/understanding-necessity-of-android-vsync-signals 

 

責任編輯:龐桂玉 來源: CoorChice的博客
相關(guān)推薦

2014-01-23 16:27:44

域名解析異常互聯(lián)網(wǎng)癱瘓DNS

2021-04-13 15:51:46

服務治理流量

2022-08-01 10:43:11

RocketMQZookeeper注冊中心

2021-02-27 10:38:56

Python結(jié)構(gòu)數(shù)據(jù)

2012-07-20 17:24:51

HTML5

2013-11-29 10:09:41

物聯(lián)網(wǎng)

2019-05-08 14:24:04

區(qū)塊鏈CosmosPolkadot

2018-05-28 21:17:57

大數(shù)據(jù)分析軟件

2019-07-16 08:57:15

kafka應用Broker

2022-09-26 10:43:13

RocketMQ保存消息

2021-04-13 18:16:07

多線程安全代碼

2012-03-14 20:59:32

iPad

2015-01-22 11:37:44

Android

2020-09-09 08:30:42

內(nèi)網(wǎng)隱蔽端口

2018-03-07 17:47:16

藍屏計算機死機

2021-03-18 14:34:34

達達集團京東云電商

2015-11-06 09:41:03

圖標可視化

2013-10-10 17:22:51

開源開源軟件

2017-07-18 13:09:20

互聯(lián)網(wǎng)

2012-07-03 16:56:12

Hadoop
點贊
收藏

51CTO技術(shù)棧公眾號