導航:首頁 > 源碼編譯 > androidview源碼

androidview源碼

發布時間:2023-03-27 05:51:31

android - View 繪制流程

我們知道,在 Android 中,View 繪制主要包含 3 大流程:

Android 中,主要有兩種視圖: View 和 ViewGroup ,其中:

雖然 ViewGroup 繼承於 View ,但是在 View 繪制三大流程中,某些流程需要區分 View 和 ViewGroup ,它們之間的操作並不完全相同,比如:

對 View 進行測量,主要包含兩個步驟:

對於第一個步驟,即求取 View 的 MeasureSpec ,首先我們來看下 MeasureSpec 的源碼定義:

MeasureSpec 是 View 的一個公有靜態內部類,它是一個 32 位的 int 值,高 2 位表示 SpecMode(測量模式),低 30 位表示 SpecSize(測量尺寸/測量大小)。
MeasureSpec 將兩個數據打包到一個 int 值上,可以減少對象內存分配,並且其提供了相應的工具方法可以很方便地讓我們從一個 int 值中抽取出 View 的 SpecMode 和 SpecSize。

一個 MeasureSpec 表達的是:該 View 在該種測量模式(SpecMode)下對應的測量尺寸(SpecSize)。其中,SpecMode 有三種類型:

對 View 進行測量,最關鍵的一步就是計算得到 View 的 MeasureSpec ,子View 在創建時,可以指定不同的 LayoutParams (布局參數), LayoutParams 的源碼主要內容如下所示:

其中:

LayoutParams 會受到父容器的 MeasureSpec 的影響,測量過程會依據兩者之間的相互約束最終生成子View 的 MeasureSpec ,完成 View 的測量規格。

簡而言之,View 的 MeasureSpec 受自身的 LayoutParams 和父容器的 MeasureSpec 共同決定( DecorView 的 MeasureSpec 是由自身的 LayoutParams 和屏幕尺寸共同決定,參考後文)。也因此,如果要求取子View 的 MeasureSpec ,那麼首先就需要知道父容器的 MeasureSpec ,層層逆推而上,即最終就是需要知道頂層View(即 DecorView )的 MeasureSpec ,這樣才能一層層傳遞下來,這整個過程需要結合 Activity 的啟動過程進行分析。

我們知道,在 Android 中, Activity 是作為視圖組件存在,主要就是在手機上顯示視圖界面,可以供用戶操作, Activity 就是 Andorid 中與用戶直接交互最多的系統組件。

Activity 的基本視圖層次結構如下所示:

Activity 中,實際承載視圖的組件是 Window (更具體來說為 PhoneWindow ),頂層View 是 DecorView ,它是一個 FrameLayout , DecorView 內部是一個 LinearLayout ,該 LinearLayout 由兩部分組成(不同 Android 版本或主題稍有差異): TitleView 和 ContentView ,其中, TitleView 就是標題欄,也就是我們常說的 TitleBar 或 ActionBar , ContentView 就是內容欄,它也是一個 FrameLayout ,主要用於承載我們的自定義根布局,即當我們調用 setContentView(...) 時,其實就是把我們自定義的布局設置到該 ContentView 中。

當 Activity 啟動完成後,最終就會渲染出上述層次結構的視圖。

因此,如果我們要求取得到子View 的 MeasureSpec ,那麼第一步就是求取得到頂層View(即 DecorView )的 MeasureSpec 。大致過程如下所示:

經過上述步驟求取得到 View 的 MeasureSpec 後,接下來就可以真正對 View 進行測量,求取 View 的最終測量寬/高:

Android 內部對視圖進行測量的過程是由 View#measure(int, int) 方法負責的,但是對於 View 和 ViewGroup ,其具體測量過程有所差異。

因此,對於測量過程,我們分別對 View 和 ViewGroup 進行分析:

綜上,無論是對 View 的測量還是 ViewGroup 的測量,都是由 View#measure(int widthMeasureSpec, int heightMeasureSpec) 方法負責,然後真正執行 View 測量的是 View 的 onMeasure(int widthMeasureSpec, int heightMeasureSpec) 方法。

具體來說, View 直接在 onMeasure(...) 中測量並設置自己的最終測量寬/高。在默認測量情況下, View 的測量寬/高由其父容器的 MeasureSpec 和自身的 LayoutParams 共同決定,當 View 自身的測量模式為 LayoutParams.UNSPECIFIED 時,其測量寬/高為 android:minWidth / android:minHeight 和其背景寬/高之間的較大值,其餘情況皆為自身 MeasureSpec 指定的測量尺寸。

而對於 ViewGroup 來說,由於布局特性的豐富性,只能自己手動覆寫 onMeasure(...) 方法,實現自定義測量過程,但是總的思想都是先測量 子View 大小,最終才能確定自己的測量大小。

當確定了 View 的測量大小後,接下來就可以來確定 View 的布局位置了,也即將 View 放置到屏幕具體哪個位置。

View 的布局過程由 View#layout(...) 負責,其源碼如下:

View#layout(...) 主要就做了兩件事:

ViewGroup 的布局流程由 ViewGroup#layout(...) 負責,其源碼如下:

可以看到, ViewGroup#layout(...) 最終也是通過 View#layout(...) 完成自身的布局過程,一個注意的點是, ViewGroup#layout(...) 是一個 final 方法,因此子類無法覆寫該方法,主要是 ViewGroup#layout(...) 方法內部對子視圖動畫效果進行了相關設置。

由於 ViewGroup#layout(...) 內部最終調用的還是 View#layout(...) ,因此, ViewGroup#onLayout(...) 就會得到回調,用於處理 子View 的布局放置,其源碼如下:

由於不同的 ViewGroup ,其布局特性不同,因此 ViewGroup#onLayout(...) 是一個抽象方法,交由 ViewGroup 子類依據自己的布局特性,擺放其 子View 的位置。

當 View 的測量大小,布局位置都確定後,就可以最終將該 View 繪制到屏幕上了。

View 的繪制過程由 View#draw(...) 方法負責,其源碼如下:

其實注釋已經寫的很清楚了, View#draw(...) 主要做了以下 6 件事:

我們知道,在 Activity 啟動過程中,會調用到 ActivityThread.handleResumeActivity(...) ,該方法就是 View 視圖繪制的起始之處:

可以看到, ActivityThread.handleResumeActivity(...) 主要就是獲取到當前 Activity 綁定的 ViewManager ,最後調用 ViewManager.addView(...) 方法將 DecorView 設置到 PhoneWindow 上,也即設置到當前 Activity 上。 ViewManager 是一個介面, WindowManager 繼承 ViewManager ,而 WindowManagerImpl 實現了介面 WindowManager ,此處的 ViewManager.addView(...) 實際上調用的是 WindowManagerImpl.addView(...) ,源碼如下所示:

WindowManagerImpl.addView(...) 內部轉發到 WindowManagerGlobal.addView(...) :

在 WindowManagerGlobal.addView(...) 內部,會創建一個 ViewRootImpl 實例,然後調用 ViewRootImpl.setView(...) 將 ViewRootImpl 與 DecorView 關聯到一起:

ViewRootImpl.setView(...) 內部首先關聯了傳遞過來的 DecorView (通過屬性 mView 指向 DecorView 即可建立關聯),然後最終調用 requestLayout() ,而 requestLayout() 內部又會調用方法 scheleTraversals() :

ViewRootImpl.scheleTraversals() 內部主要做了兩件事:

Choreographer.postCallback(...) 會申請一次 VSYNC 中斷信號,當 VSYNC 信號到達時,便會回調 Choreographer.doFrame(...) 方法,內部會觸發已經添加的回調任務, Choreographer 的回調任務有以下四種類型:

因此, ViewRootImpl.scheleTraversals(...) 內部通過 mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null) 發送的非同步視圖渲染消息就會得到回調,即回調 mTra

Ⅱ Android 重學系列 View的繪制流程(六) 硬體渲染(上)

本文開始聊聊Android中的硬體渲染。如果跟著我的文章順序,從SF進程到App進程的繪制流程一直閱讀,我們到這里已經有了一定的基礎,可以試著進行橫向比對如Chrome瀏覽器渲染流程,看看軟體渲染,硬體渲染,SF合成都做了什麼程度的優化。

先讓我們回顧一下負責硬體渲染的主體對象ThreadedRenderer在整個繪制流程中做了哪幾個步驟。

在硬體渲染的過程中,有一個很核心的對象RenderNode,作為每一個View繪制的節點對象。

當每一次進行准備進行繪制的時候,都會雷打不動執行如下三個步驟:

如果遇到什麼問題歡迎來到 https://www.jianshu.com/p/c84bfa909810 下進行討論

實際上整個硬體渲染的設計還是比較龐大。因此本文先聊聊ThreadedRender整個體系中主要對象的構造以及相關的原理。

首先來認識下面幾個重要的對象有一個大體的印象。

java層中面向Framework中,只有這么多,下面是一一映射的簡圖。

能看到實際上RenderNode也會跟著View 樹的構建同時一起構建整個顯示層級。也是因此ThreadedRender也能以RenderNode為線索構建出一套和軟體渲染一樣的渲染流程。

僅僅這樣?如果只是這么簡單,知道我習慣的都知道,我喜歡把相關總結寫在最後。如果把總攬寫在正文開頭是因為設計比較繁多。因為我們如果以流水線的形式進行剖析容易造成迷失細節的困境。

讓我繼續介紹一下,在硬體渲染中native層的核心對象。

如下是一個思維導圖:

有這么一個大體印象後,就不容易迷失在源碼中。我們先來把這些對象的實例化以及上面列舉的ThreadedRenderer在ViewRootImpl中執行行為的順序和大家來聊聊其原理,先來看看ThreadedRenderer的實例化。

當發現mSurfaceHolder為空的時候會調用如下函數:

而這個方法則調用如下的方法對ThreadedRenderer進行創建:

文件:/ frameworks / base / core / java / android / view / ThreadedRenderer.java

能不能創建的了ThreadedRenderer則決定於全局配置。如果ro.kernel.qemu的配置為0,說明支持OpenGL 則可以直接返回true。如果qemu.gles為-1說明不支持OpenGL es返回false,只能使用軟體渲染。如果設置了qemu.gles並大於0,才能打開硬體渲染。

我們能看到ThreadedRenderer在初始化,做了三件事情:

關鍵是看1-3點中ThreadRenderer都做了什麼。

文件:/ frameworks / base / core / jni / android_view_ThreadedRenderer.cpp

能看到這里是直接實例化一個RootRenderNode對象,並把指針的地址直接返回。

能看到RootRenderNode繼承了RenderNode對象,並且保存一個JavaVM也就是我們所說的Java虛擬機對象,一個java進程全局只有一個。同時通過getForThread方法,獲取ThreadLocal中的Looper對象。這里實際上拿的就是UI線程的Looper。

在這個構造函數有一個mDisplayList十分重要,記住之後會頻繁出現。接著來看看RenderNode的頭文件:
文件:/ frameworks / base / libs / hwui / RenderNode.h

實際上我把幾個重要的對象留下來:

文件:/ frameworks / base / core / java / android / view / RenderNode.java

能看到很簡單,就是包裹一個native層的RenderNode返回一個Java層對應的對象開放Java層的操作API。

能看到這個過程生成了兩個對象:

這個對象實際上讓RenderProxy持有一個創建動畫上下文的工廠。RenderProxy可以通過ContextFactoryImpl為每一個RenderNode創建一個動畫執行對象的上下文AnimationContextBridge。

文件:/ frameworks / base / libs / hwui / renderthread / RenderProxy.cpp

在這里有幾個十分重要的對象被實例化,當然這幾個對象在聊TextureView有聊過( SurfaceView和TextureView 源碼淺析 ):

我們依次看看他們初始化都做了什麼。

文件:/ frameworks / base / libs / hwui / renderthread / RenderThread.cpp

能看到其實就是簡單的調用RenderThread的構造函數進行實例化,並且返回對象的指針。

RenderThread是一個線程對象。先來看看其頭文件繼承的對象:
文件:/ frameworks / base / libs / hwui / renderthread / RenderThread.h

其中RenderThread的中進行排隊處理的任務隊列實際上是來自ThreadBase的WorkQueue對象。

文件:/ frameworks / base / libs / hwui / thread / ThreadBase.h

ThreadBase則是繼承於Thread對象。當調用start方法時候其實就是調用Thread的run方法啟動線程。

另一個更加關鍵的對象,就是實例化一個Looper對象到WorkQueue中。而直接實例化Looper實際上就是新建一個Looper。但是這個Looper並沒有獲取當先線程的Looper,這個Looper做什麼的呢?下文就會揭曉。

WorkQueue把一個Looper的方法指針設置到其中,其作用可能是完成了某一件任務後喚醒Looper繼續工作。

而start方法會啟動Thread的run方法。而run方法最終會走到threadLoop方法中,至於是怎麼走進來的,之後有機會會解剖虛擬機的源碼線程篇章進行講解。

在threadloop中關鍵的步驟有如下四個:

在這個過程中創建了幾個核心對象:

另一個核心的方法就是,這個方法為WorkQueue的Looper注冊了監聽:

能看到在這個Looper中注冊了對DisplayEventReceiver的監聽,也就是Vsync信號的監聽,回調方法為displayEventReceiverCallback。

我們暫時先對RenderThread的方法探索到這里,我們稍後繼續看看回調後的邏輯。

文件:/ frameworks / base / libs / hwui / thread / ThreadBase.h

能看到這里的邏輯很簡單實際上就是調用Looper的pollOnce方法,阻塞Looper中的循環,直到Vsync的信號到來才會繼續往下執行。詳細的可以閱讀我寫的 Handler與相關系統調用的剖析 系列文章。

文件:/ frameworks / base / libs / hwui / thread / ThreadBase.h

實際上調用的是WorkQueue的process方法。

文件:/ frameworks / base / libs / hwui / thread / WorkQueue.h

能看到這個過程中很簡單,幾乎和Message的loop的邏輯一致。如果Looper的阻塞打開了,則首先找到預計執行時間比當前時刻都大的WorkItem。並且從mWorkQueue移除,最後添加到toProcess中,並且執行每一個WorkItem的work方法。而每一個WorkItem其實就是通過從某一個壓入方法添加到mWorkQueue中。

到這里,我們就明白了RenderThread中是如何消費渲染任務的。那麼這些渲染任務又是哪裡誕生呢?

上文聊到了在RenderThread中的Looper會監聽Vsync信號,當信號回調後將會執行下面的回調。

能看到這個方法的核心實際上就是調用drainDisplayEventQueue方法,對ui渲染任務隊列進行處理。

能到在這里mVsyncRequested設置為false,且mFrameCallbackTaskPending將會設置為true,並且調用queue的postAt的方法執行ui渲染方法。

還記得queue實際是是指WorkQueue,而WorkQueue的postAt方法實際實現如下:
/ frameworks / base / libs / hwui / thread / WorkQueue.h

情景帶入,當一個Vsync信號達到Looper的監聽者,此時就會通過WorkQueue的drainDisplayEventQueue 壓入一個任務到隊列中。

每一個默認的任務都是執行dispatchFrameCallback方法。這里的判斷mWorkQueue中是否存在比當前時間更遲的時刻,並返回這個WorkItem。如果這個對象在頭部needsWakeup為true,說明可以進行喚醒了。而mWakeFunc這個方法指針就是上面傳下來:

把阻塞的Looper喚醒。當喚醒後就繼續執行WorkQueue的process方法。也就是執行dispatchFrameCallbacks方法。

在這里執行了兩個事情:

先添加到集合中,在上面提到過的threadLoop中,會執行如下邏輯:

如果大小不為0,則的把中的IFrameCallback全部遷移到mFrameCallbacks中。

而這個方法什麼時候調用呢?稍後就會介紹。其實這部分的邏輯在TextureView的解析中提到過。

接下來將會初始化一個重要對象:

這個對象名字叫做畫布的上下文,具體是什麼上下文呢?我們現在就來看看其實例化方法。
文件:/ frameworks / base / libs / hwui / renderthread / CanvasContext.cpp

文件:/ device / generic / goldfish / init.ranchu.rc

在init.rc中默認是opengl,那麼我們就來看看下面的邏輯:

首先實例化一個OpenGLPipeline管道,接著OpenGLPipeline作為參數實例化CanvasContext。

文件:/ frameworks / base / libs / hwui / renderthread / OpenGLPipeline.cpp

能看到在OpenGLPipeline中,實際上就是存儲了RenderThread對象,以及RenderThread中的mEglManager。透過OpenGLPipeline來控制mEglManager進而進一步操作OpenGL。

做了如下操作:

文件:/ frameworks / base / libs / hwui / renderstate / RenderState.cpp

文件:/ frameworks / base / libs / hwui / renderthread / DrawFrameTask.cpp

實際上就是保存這三對象RenderThread;CanvasContext;RenderNode。

文件:/ frameworks / base / core / jni / android_view_ThreadedRenderer.cpp

能看到實際上就是調用RenderProxy的setName方法給當前硬體渲染對象設置名字。

文件:/ frameworks / base / libs / hwui / renderthread / RenderProxy.cpp

能看到在setName方法中,實際上就是調用RenderThread的WorkQueue,把一個任務隊列設置進去,並且調用runSync執行。

能看到這個方法實際上也是調用post執行排隊執行任務,不同的是,這里使用了線程的Future方式,阻塞了執行,等待CanvasContext的setName工作完畢。

Ⅲ Android TV 焦點原理源碼解析

相信很多剛接觸AndroidTV開發的開發者,都會被各種焦點問題給折磨的不行。不管是學技術還是學習其他知識,都要學習和理解其中原理,碰到問題我們才能得心應手。下面就來探一探Android的焦點分發的過程。

Android焦點事件的分發是從ViewRootImpl的processKeyEvent開始的,源碼如下:

源碼比較長,下面我就慢慢來講解一下具體的每一個細節。

dispatchKeyEvent方法返回true代表焦點事件被消費了。

ViewGroup的dispatchKeyEvent()方法的源碼如下:

(2)ViewGroup的dispatchKeyEvent執行流程

(3)下面再來瞧瞧view的dispatchKeyEvent方法的具體的執行過程

驚奇的發現執行了onKeyListener中的onKey方法,如果onKey方法返回true,那麼dispatchKeyEvent方法也會返回true

可以得出結論:如果想要修改ViewGroup焦點事件的分發,可以這么干:

注意:實際開發中,理論上所有焦點問題都可以通過給dispatchKeyEvent方法增加監聽來來攔截來控制。

(1)dispatchKeyEvent方法返回false後,先得到按鍵的方向direction值,這個值是一個int類型參數。這個direction值是後面來進行焦點查找的。

(2)接著會調用DecorView的findFocus()方法一層一層往下查找已經獲取焦點的子View。
ViewGroup的findFocus方法如下:

View的findFocus方法

說明:判斷view是否獲取焦點的isFocused()方法, (mPrivateFlags & PFLAG_FOCUSED) != 0 和view 的isFocused()方法是一致的。

其中isFocused()方法的作用是判斷view是否已經獲取焦點,如果viewGroup已經獲取到了焦點,那麼返回本身即可,否則通過mFocused的findFocus()方法來找焦點。mFocused其實就是ViewGroup中獲取焦點的子view,如果mView不是ViewGourp的話,findFocus其實就是判斷本身是否已經獲取焦點,如果已經獲取焦點了,返回本身。

(3)回到processKeyEvent方法中,如果findFocus方法返回的mFocused不為空,說明找到了當前獲取焦點的view(mFocused),接著focusSearch會把direction(遙控器按鍵按下的方向)作為參數,找到特定方向下一個將要獲取焦點的view,最後如果該view不為空,那麼就讓該view獲取焦點。

(4)focusSearch方法的具體實現。

focusSearch方法的源碼如下:

可以看出focusSearch其實是一層一層地網上調用父View的focusSearch方法,直到當前view是根布局(isRootNamespace()方法),通過注釋可以知道focusSearch最終會調用DecorView的focusSearch方法。而DecorView的focusSearch方法找到的焦點view是通過FocusFinder來找到的。

(5)FocusFinder是什麼?

它其實是一個實現 根據給定的按鍵方向,通過當前的獲取焦點的View,查找下一個獲取焦點的view這樣演算法的類。焦點沒有被攔截的情況下,Android框架焦點的查找最終都是通過FocusFinder類來實現的。

(6)FocusFinder是如何通過findNextFocus方法尋找焦點的。

下面就來看看FocusFinder類是如何通過findNextFocus來找焦點的。一層一層往下看,後面會執行findNextUserSpecifiedFocus()方法,這個方法會執行focused(即當前獲取焦點的View)的findUserSetNextFocus方法,如果該方法返回的View不為空,且isFocusable = true && isInTouchMode() = true的話,FocusFinder找到的焦點就是findNextUserSpecifiedFocus()返回的View。

(7)findNextFocus會優先根據XML里設置的下一個將獲取焦點的View ID值來尋找將要獲取焦點的View。

看看View的findUserSetNextFocus方法內部都幹了些什麼,OMG不就是通過我們xml布局裡設置的nextFocusLeft,nextFocusRight的viewId來找焦點嗎,如果按下Left鍵,那麼便會通過nextFocusLeft值里的View Id值去找下一個獲取焦點的View。

可以得出以下結論:

1. 如果一個View在XML布局中設置了focusable = true && isInTouchMode = true,那麼這個View會優先獲取焦點。

2. 通過設置nextFocusLeft,nextFocusRight,nextFocusUp,nextFocusDown值可以控制View的下一個焦點。

Android焦點的原理實現就這些。總結一下:

為了方便同志們學習,我這做了張導圖,方便大家理解~

Ⅳ Android源碼追蹤—android:onClick

之前對源碼的閱讀,總是用時一通亂七八糟的跳轉,以學會使用為目的;過了一段時間,就忘記了,因此打算將一些源碼的閱讀經歷記錄下來,也通過敲一遍的過程,加深理解。

最開始,用一個比較簡單的例子來小試牛刀吧

對於View(Button、TextView等)的點擊事件,常用的寫法是通過 findViewById 獲取View的實例,然後通過 setOnClickListener 設置監聽事件,比如我們有如下Button控制項。

設置點擊事件(假設在Activity中)

但是還有一種寫法是在xml布局中通過android:onClick屬性直接指定點擊執行的函數。

【思考】

首先我們知道諸如 android:xxx 之類的屬性是會在某個attrs文件中定義的,此處的 android:onClick 是View的屬性,定義在如下文件中。

在View的構造函數中,會解析出此屬性的值。

看這里, 如果變數handlerName不為空,就會為此View設置點擊事件了 ,這個handlerName就是onClick屬性的值doSubmit,但這個點擊事件,並不是我們所熟悉的OnClickListener。

進一步看看這個 DeclaredOnClickListener 類

DeclaredOnClickListener 實現了 OnClickListener ,其中重點是參數 mResolvedMethod 和 mResolvedContext 。

在onClick事件中最終通過反射 mResolvedMethod.invoke(mResolvedContext, v); 執行了doSubmit方法。

doSubmit的訪問許可權是否可以設置為private呢?

答案:不可以,因為源碼中沒有調用 mMethod.setAccessible(true); 注入所有修飾符。

其實在onClick屬性的注釋中就已經說明了。

Ⅳ Android TV開發焦點移動源碼分析

點可以理解為選中態,在Android TV上起很重要的作用。一個視圖控制項只有在獲得焦點的狀態下,才能響應按鍵的Click事件。
相對於手機上用手指點擊屏幕產生的Click事件, 在TV中通過點擊遙控器的方向鍵來控制焦點的移動。當焦點移動到目標控制項上之後,按下遙控器的確定鍵,才會觸發一個Click事件,進而去做下一步的處理
在處理焦點的時候,有一些基礎的用法需要知道。
首先,一個控制項isFocusable()需要為true才有資格可以獲取到焦點。如果想要在觸摸模式下獲取焦點,需要通過setFocusableInTouchMode(boolean)來設置。也可以直接在xml布局文件中指定:

keyEvent 分發過程:

而當按下遙控器的按鍵時,會產生一個按鍵事件,就是KeyEvent,包含「上」,「下」,「左」,「右」,「返回」,「確定」等指令。焦點的處理就在KeyEvent的分發當中完成。
首先,KeyEvent會流轉到ViewRootImpl中開始進行處理,具體方法是內部類 ViewPostImeInputStage 中的 processKeyEvent :

接下來先看一下KeyEvent在view框架中的分發:

這里也是可以做焦點控制,最好是在 event.getAction() == KeyEvent.ACTION_DOWN 進行.
因為android 的 ViewRootlmpl 的 processKeyEvent 焦點搜索與請求的地方 進行了判斷if (event.getAction() == KeyEvent.ACTION_DOWN)

• 首先ViewGroup會一層一層往上執行父類的dispatchKeyEvent方法,如果返回true那麼父類的dispatchKeyEvent方法就會返回true,也就代表父類消費了該焦點事件,那麼焦點事件自然就不會往下進行分發。
• 然後ViewGroup會判斷mFocused這個view是否為空,如果為空就會return false,焦點繼續往下傳遞;如果不為空,那就會return mFocused的dispatchKeyEvent方法返回的結果。這個mFocused其實是ViewGroup中當前獲取焦點的子View

發現執行了onKeyListener中的onKey方法,如果onKey方法返回true,那麼dispatchKeyEvent方法也會返回true
如果想要修改ViewGroup焦點事件的分發
• 重寫view的dispatchKeyEvent方法
• 給某個子view設置onKeyListener監聽

下面再來看一下如果一個頁面第一次進入,系統是如何確定焦點是定位在哪個view上的

由於DecorView繼承自FrameLayout,這里調用的是ViewGroup的requestFocus

descendantFocusability:
• FOCUS_AFTER_DESCENDANTS:先分發給Child View進行處理,如果所有的Child View都沒有處理,則自己再處理
• FOCUS_BEFORE_DESCENDANTS:ViewGroup先對焦點進行處理,如果沒有處理則分發給child View進行處理
• FOCUS_BLOCK_DESCENDANTS:ViewGroup本身進行處理,不管是否處理成功,都不會分發給ChildView進行處理
因為 PhoneWindow 給 DecoreView 初始化時設置 了 setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS),所以這里默認是FOCUS_AFTER_DESCENDANTS

到此第一次請求焦點的過程基本告一個段落

焦點移動的時候,默認的情況下,會按照一種演算法去找在指定移動方向上最近的鄰居。在一些情況下,焦點的移動可能跟開發者的意圖不符,這時開發者可以在布局文件中使用下面這些XML屬性來指定下一個焦點對象:

在KeyEvent分發中已經知道如果分發過程中event沒有被消耗,就會根據方向搜索以及請求焦點View

流程一:查找用戶指定的下一個焦點

流程二:獲取搜索方向上所有可以獲取焦點的view,使用演算法查找下一個view
addFocusables() 獲取搜索方向上可獲得焦點的view

descendantFocusability屬性決定了ViewGroup和其子view的聚焦優先順序
• FOCUS_BLOCK_DESCENDANTS:viewgroup會覆蓋子類控制項而直接獲得焦點
• FOCUS_BEFORE_DESCENDANTS:viewgroup會覆蓋子類控制項而直接獲得焦點
• FOCUS_AFTER_DESCENDANTS:viewgroup只有當其子類控制項不需要獲取焦點時才獲取焦點
addFocusables 的第一個參數views是由root決定的。在ViewGroup的focusSearch方法中傳進來的root是DecorView,也可以主動調用FocusFinder的findNextFocus方法,在指定的ViewGroup中查找焦點。
FocusFinder.findNextFocus 查找焦點

Ⅵ Android源碼解析Window系列第(一)篇---Window的基本認識和Activity的載入流程

您可能聽說過View ,ViewManager,Window,PhoneWindow,WindowManager,WindowManagerService,可是你知道這幾個類是什麼關系,幹嘛用的。概括的來說,View是放在Window中的,Window是一個抽象類,它的具體實現是PhoneWindow,PhoneWindow還有個內部類DecorView,WindowManager是一個interface,繼承自ViewManager,它是外界訪問Window的入口,,提供了add/remove/updata的方法操作View,WindowManager與WindowManagerSerice是個跨進程的過程,WindowManagerService的職責是對系統中的所有窗口進行管理。如果您不太清楚,建議往下看,否則就不要看了。

Android系統的Window有很多種,大體上來說,Framework定義了三種窗口類型;

這就是Framework定義了三種窗口類型,這三種類型定義在WindowManager的內部類LayoutParams中,WindowManager講這三種類型 進行了細化,把每一種類型都用一個int常量來表示,這些常量代表窗口所在的層,WindowManagerService在進行窗口疊加的時候,會按照常量的大小分配不同的層,常量值越大,代表位置越靠上面, 所以我們可以猜想一下,應用程序Window的層值常量要小於子Window的層值常量,子Window的層值常量要小於系統Window的層值常量。 Window的層級關系如下所示。

上面說了Window分為三種,用Window的type區分,在搞清楚Window的創建之前,我們需要知道怎麼去描述一個Window,我們就把Window當做一個實體類,給我的感覺,它必須要下面幾個欄位。

實際上WindowManager.LayoutParams對Window有很詳細的定義。

提取幾個重要的參數

Window是一個是一個抽象的概念,千萬不要認為我們所看到的就是Window,我們平時所看到的是視圖,每一個Window都對應著一個View,View和Window通過ViewRootImpl來建立聯系。有了View,Window的存在意義在哪裡呢,因為View不能單獨存在,它必須依附著Window,所以有視圖的地方就有Window,比如Activity,一個Dialog,一個PopWindow,一個菜單,一個Toast等等。

通過上面我們知道視圖和Window的關系,那麼有一個問題,是先有視圖,還是先有Window。這個答案只有在源碼中找了。應用程序的入口類是ActivityThread,在ActivityThread中有performLaunchActivity來啟動Activity,這個performLaunchActivity方法內部會創建一個Activity。

如果activity不為null,就會調用attach,在attach方法中通過PolicyManager創建了Window對象,並且給Window設置了回調介面。

PolicyManager的實現類是Policy

這樣Window就創建出來了, 所以先有Window,後有視圖,視圖依賴Window存在 ,再說一說視圖(Activity)為Window設置的回調介面。

Activity實現了這個回調介面,當Window的狀態發生變化的時候,就會回調Activity中實現的這些介面,有些回調介面我們還是熟悉的,dispatchTouchEvent,onAttachedToWindow,onDetachedFromWindow等。

下面分析view是如何附屬到window上的,通過上面可以看到,在attach之後就要執行callActivityOnCreate,在onCreate中我們會調用setContentView方法。

getWindow獲取了Window對象,Window的具體實現類是PhoneWindow,所以要看PhoneWindow的setContentView方法。

這里涉及到一個mContentParent變數,他是一個DecorView的一部分,DecorView是PhoneWindow的一個內部類,我先介紹一下關於DecorView的知識。

DecorView是Activity的頂級VIew,DecorView繼承自FrameLayout,在DecorView中有上下兩個部分,上面是標題欄,下面是內容欄,我們通過PhoneWindow的setContentView所設置的布局文件是加到內容欄(mContentParent)裡面的,View層的事件都是先經過DecorView在傳遞給我們的View的。

OK在回到setContentView的源碼分析,我們可以得到Activity的Window創建需要三步。

- 1、 如果沒有DecorView,在installDecor中創建DecorView。

- 2、將View添加到decorview中的mContentParent中。

- 3、回調Activity的onContentChanged介面。

先看看第一步,installDecor的源碼

installDecor中調用了generateDecor,繼續看

直接給new一個DecorView,有了DecorView之後,就可以載入具體的布局文件到DecorView中了,具體的布局文件和系統和主題有關系。

在看第二步,將View添加到decorview中的mContentParent中。

直接將Activity視圖加到DecorView的mContentParent中,最後一步,回調Activity的onContentChanged介面。在Activity中尋找onContentChanged方法,它是個空實現,我們可以在子Activity中處理。

到此DecorView被創建完畢,我們一開始從Thread中的handleLaunchActivity方法開始分析,首先載入Activity的位元組碼文件,利用反射的方式創建一個Activity對象,調用Activity對象的attach方法,在attach方法中,創建系統需要的Window並為設置回調,這個回調定義在Window之中,由Activity實現,當Window的狀態發生變化的時候,就會回調Activity實現的這些回調方法。調用attach方法之後,Window被創建完成,這時候需要關聯我們的視圖,在handleLaunchActivity中的attach執行之後就要執行handleLaunchActivity中的callActivityOnCreate,在onCreate中我們會調用setContentView方法。通過setContentView,創建了Activity的頂級View---DecorView,DecorView的內容欄(mContentParent)用來顯示我們的布局。 這個是我們上面分析得到了一個大致流程,走到這里,這只是添加的過程,還要有一個顯示的過程,顯示的過程就要調用handleLaunchActivity中的handleResumeActivity方法了。最後會調用makeVisible方法。

這裡面首先拿到WindowManager對象,用tWindowManager 的父介面ViewManager接收,ViewManager可以
最後調用 mDecor.setVisibility(View.VISIBLE)設置mDecor可見。到此,我們終於明白一個Activity是怎麼顯示在我們的面前了。
參考鏈接:
http://blog.csdn.net/feiclear_up/article/details/49201357

Ⅶ Android ViewRootImpl

本文主要分析兩個問題:
1、為什麼View 的繪制流程是從 ViewRootImpl 的performTraversals()方法開始的?
2、View 的invalidate方法是怎麼觸發到ViewRootImpl 的performTraversals()方法的。
在閱讀本文前,最好先了解window的添加過程,Android消息處理機制 和 View 的繪制流程。推薦先閱讀以下文章:
Android Window和WindowManager
Android-消息機制
Android View 的繪制流程

android 源碼注釋的意思是:ViewRootImpl是視圖層次結構的頂部,實現 View 和 WindowManager 之間所需的協議。是 WindowManager Global 的內部實現中重要的組成部分。

View 的繪制流程是從 ViewRootImpl 的performTraversals()方法開始的,那到底是哪裡調用了performTraversals()方法呢,下面我們分析一下:

1.私有屬性的performTraversals()方法肯定是在內部調用起來的,經過搜索找到是doTraversal()方法調用了。

2.接著找到了,調用了doTraversal() 的TraversalRunnable 類

3.內部只有一個地方實例化了TraversalRunnable 的實例mTraversalRunnable ,查到到兩個方法內都調用了mTraversalRunnable ,明顯 scheleTraversals 是主動觸發這個 Runnable 。這就表明調用了scheleTraversals ()函數的地方都主動觸發了view的刷新。

4.接著我們看一下 mChoreographer.postCallback 做了什麼

可以看到,最後後走進了scheleVsyncLocked()方法內。

5.mDisplayEventReceiver 的類 是 FrameDisplayEventReceiver,繼承自
DisplayEventReceiver 。

最後走到這里就沒了,那麼這個方法是做了什麼呢,這個方法的注釋是這個意思:安排在下一個顯示幀開始時傳送單個垂腔察直同步脈沖。意思就是,調用了這個方法可以收到系統傳送過來的垂直同步脈沖信號。Android系統每隔16ms就會發送一個VSYNC信號(VSYNC:vertical synchronization 垂直同步,幀同步),觸發對UI進行渲染。這個垂直同步信對於應用來說了,只有了訂閱了監聽,才能收到。而且是訂閱一次,收到一次。

6.既然是在這個類裡面訂閱垂直同步信號的,那回調也應該在這里。於是找到了以下方法。

native code 調用到 onVsync,這個方法的注釋解釋如下:當接收到垂直同步脈沖時調用。接收者應該渲染一個幀,然後調用 {@link scheleVsync} 來安排下一個垂直同步脈沖。
這個方法的具體實現在前面分析到的FrameDisplayEventReceiver 類裡面。

這里可以看到,其實mHandler就是當前主線程的handler,當接收到onVsync信號的時候,將自己封裝到Message中,等到Looper處理,最後Looper處理消息基圓寬的時候就會調用run方法,這里是Handler的機制,不做解釋。

7.最後,如下圖調用所示,最終從mCallbackQueues取回之前添加的任務再執行run方法,也就是TraservalRunnable的run方法。

總結上面的分析,調用流程如下圖所示如下:

上面我們分析到只要調用了ViewRootImpl 的scheleTraversals ()方法,最終就能調用了ViewRootImpl 的performTraversals()來開始繪制。搏亮那肯定是我們常調用的view刷新的介面,經過一系列的方法調用,最終調用了ViewRootImpl 的scheleTraversals ()方法。下面我們分析一下常用的View 的 invalidate()介面是怎麼調用到了ViewRootImpl 的scheleTraversals ()方法。

可以看出,invalidate有多個重載方法,但最終都會調用invalidateInternal方法,在這個方法內部,進行了一系列的判斷,判斷View是否需要重繪,接著為該View設置標記位,然後把需要重繪的區域傳遞給父容器,即調用父容器的invalidateChild方法。
接著我們看ViewGroup#invalidateChild:

由於不斷向上調用父容器的方法,到最後會調用到ViewRootImpl的invalidateChildInParent方法,我們來看看它的源碼,ViewRootImpl#invalidateChildInParent:

最後調用了scheleTraversals方法,觸發View的工作流程。至此,我們已經完整地分析了一次調用View 的 invalidate()方法到觸發ViewRootImpl 的scheleTraversals()方法。

Ⅷ Carson帶你學Android:你真的了解view.post()嗎

為什麼view.post()能保證獲取到view的寬高?

View.post()的原理: 以Handler為基礎,View.post() 將傳入任務添加到 View繪制任務所在的消息隊列尾部,從而保證View.post() 任務的執行時機是在View 繪制任務完成之後的。 其中,幾個關鍵點:

所以:

具體源碼分析請看: Android:為什麼view.post()能保證獲取到view的寬高?

為什麼onCreate()使用view.post()無法立刻執行任務(如獲取寬高),需要在onResume()後才可獲取?

在onCreate()時,AttachInfo還沒被賦值(為null)(是在view.dispatchAttachedToWindow()才被賦值),所亮物猜以會走下述源碼的過程2;通過上面分析,此過程的作用僅是:保存了通過post()添加的任務,並沒執行。

若只是創建一個 View & 調用它的post(),那麼post的任務會不會被執行?

不會。主要原因是:
每個View中post() 需執行的任務,必須得添加到窗口視圖-執行繪制流程 - 任務才會被post到消息隊列里去等待執行,即依賴於dispatchAttachedToWindow ();

若View未添加到窗口視圖,那麼就不會走繪制流程,post() 添加的任務最終不會被post到消息隊列里,即得不到執行。(但會保存到HandlerAction數組里)

上述例子,因為它沒有被添加到窗口視圖,所以不會走繪制流程,所以該任務最終不會被post到消息隊列里 & 執行

此時只需要添加將View添加到窗口,那麼post()的任務即可螞租被執行

view.pos()傳入的任務被執行的有效期是多久?

在整個 Activity 的生命周期內都可以正常使用 View.post() 任務

任務被執行是構造AttachInfo,所敬型以任務釋放即時釋放AttachInfo (置為null)。而AttachInfo 的釋放操作(置為null)是在 Activity 生命周期 onDestory 方法之後

下面,我們將分析,什麼時候調用上述入口,即DecorView.dispatchDetachedFromWindow();

此時需從 將DecorView從WindowManager中移除 開始講起:移除 Window 窗口任務是通過 ActivityThread.handleDestoryActivity()完成。

View.post() 任務被執行的有效期是在 Activity 生命周期 onDestory()後。本質是追蹤AttachInfo的釋放過程(置為null)

AttachInfo的釋放過程是在 將DecorView從WindowManager中移除時:回調DecorView.dispatchDetachedFromWindow(),其具體行為是:

而上述過程是在ActivityThread.handleDestoryActivity()中回調 Activity.onDestory()之後。

至此,關於view.post()的四大常見疑問 (坑)內容講解完畢。

不定期分享關於 安卓開發 的干貨,追求 短、平、快 ,但 卻不缺深度

Ⅸ Android的View類是怎樣定義的源代碼是什麼

view的定義還真不是一兩句話能說清楚的。源碼里代碼2萬多行,最前面的注釋有500多行。

如果你用android studio,直接Ctrl 點擊View應該就能看到源碼。
當然也可以在網頁里查看源碼
http ://androidxref.com

Ⅹ Android-ViewPager源碼解析與性能優化

ViewPager高度設置為wrap_content或者具體的高度值無效,是因為ViewPager的onMeasure方法在度量寬高的時候,在方法體的最開始就直接調用了setMeasuredDimension()方法將自身的寬高度量,但是並沒有在其onMeasure()計算完其具體的子View的寬高之後,重新度量一次自身的寬高

從這里我們可以看到,ViewPager的寬高會受其父容器的寬高的限制,但是並不會因為自身子View的寬高而影響ViewPager的寬高。

看setMeasuredDimension的源碼調用可以看出,當父容器的高度確定時,ViewPager的寬高其實就是父容器的寬高,ViewPager就是在onMeasure方法一進來的時候就直接填充滿整個父容器的剩餘空間。在計算孩子節點之前,就已經計算好了ViewPager的寬高,在計算完孩子節點之後,並不會再去重返告新計算ViewPager的寬高。

自定義一個ViewPager,根據子View的寬高重新度量ViewPager的寬高。其實做法就是在自定義onMeasure的super.onMeasure(widthMeasureSpec, heightMeasureSpec);之前重新計算heightMeasureSpec,將原本ViewPager接收的父容器的限定的heightMeasureSpec替換成我們自定義的heightMeasureSpec。

但是這樣的做法,會有種問題,即在ViewPager的子View是採用LinearLayout作為根布局的時候,並且給LinearLayout設置了固定的高度值,那麼會出現ViewPager動態高度無效的問題
其實具體的做法,就是仿造measureChild的做法,自定義子View的heightMeasureSpec然後度量整個子View,其實子View的寬度也可以這樣做。

這里其實是源碼層做了限制,在setOffscreenPageLimit中設置了一個默認值,而這個默認值的大小為1

所以從這里可以看出,ViewPager的最小緩存的limit是1,而不能小於1,當小於1的時候就會被強制的設置為1。
而populate()函數就是用來處理ViewPager的緩存的。
populate()的生命周期是與Adapter的生命周期綁定的。
其實在setOffscreenPageLimit()的時候,調用的populate(),而populate()內部調用的

而pupulate(int newCurrentItem)方法在另一處調用的地方就是在setCurrentItem。
其實ViewPager緩存都是基於派世盯ItemInfo這個類來進行的,

看下ViewPager.addNewItem的源碼
其實ViewPager.addNewItem就是通過調用Adapter.instantiateItem來創建對應的View,並且將View保存到ItemInfo中的object屬性,並且判斷ViewPager緩存中是否已經有ItemInfo,如果沒有,則添加,如果有則做修改替換

從分析FragmentStatePagerAdapter來看,setUserVisibleHint方法會優先於Fragment的生命周期函數 執行。因為在FragmentStatePagerAdapter中提交事務,是在調用finishUpdate方法中進行的,只有提交事務的時候,才會去執行Fragment的生命周期。
FragmentStatePagerAdapter中的instantiateItem和destroyItem都實現了對fragment的事塵和務的添加和刪除,而finishUpdate實現了事務的提交,所以在實現FragmentStatePagerAdapter的時候,並不需要重寫instantiateItem和destroyItem

閱讀全文

與androidview源碼相關的資料

熱點內容
新電腦管家下載好怎麼解壓 瀏覽:524
php獲取介面數據 瀏覽:763
最後的命令 瀏覽:921
如何添加手機app桌面快捷圖標 瀏覽:427
ui設計師與程序員 瀏覽:417
壽司pdf 瀏覽:828
pythonbg是什麼 瀏覽:248
c數值演算法程序大全 瀏覽:785
android整點報時 瀏覽:221
稀土pdf 瀏覽:536
單片機電子鎖 瀏覽:596
通達信機智資金流指標公式源碼 瀏覽:216
php安裝xsl擴展 瀏覽:842
python如何使用help 瀏覽:367
上汽榮威app在哪裡查詢 瀏覽:903
冰櫃壓縮機溫度108 瀏覽:720
阿里雲郵smtp伺服器地址 瀏覽:252
解壓館認知理解 瀏覽:239
為什麼使用非官方伺服器會封號 瀏覽:9
佛山加密文檔軟體 瀏覽:813