Android從啓動到程序運行整個過程的整理

1Android是基於Linux的一個操做系統,它能夠分爲五層,下面是它的層次架構圖,能夠記一下,由於後面應該會總結到SystemServer這些Application Framework層的東西java

Android的五層架構從上到下依次是:應用層,應用框架層,庫層,運行時層,Linux內核層。linux

而在Linux中,它的啓動能夠歸爲一下幾個流程: 
Boot Loader——>初始化內核——>。。。。。。 
當初始化內核以後,就會啓動一個至關重要的祖先進程,也就是init進程,在Linux中全部的進程都是由init進程直接或間接fork出來的。
android

而對於Android來講,前面的流程都是同樣的,而當init進程建立以後,會fork出一個Zygote進程,這個進程是全部Java進程的父進程。咱們知道,Linux是基於C的,而Android是基於Java的(固然底層也是C)。因此這裏就會fork出一個Zygote Java進程用來fork出其餘的進程。【斷點1】程序員

總結到了這裏就提一下以後會談到的幾個很是重要的對象以及一個很重要的概念。算法

  • ActivityManagerServices(AMS):它是一個服務端對象,負責全部的Activity的生命週期,ActivityThread會經過Binder與之交互,而AMS與Zygote之間進行交互則是經過Socket通訊(IPC通訊在以後會總結到)
  • ActivityThread:它也就是咱們俗稱的UI線程/主線程,它裏面存在一個main()方法,這也是APP的真正入口,當APP啓動時,就會啓動ActivityThread中的main方法,它會初始化一些對象,而後開啓消息循環隊列(以後總結),以後就會Looper.loop死循環,若是有消息就執行,沒有就等着,也就是事件驅動模型(edt)的原理。
  • ApplicationThread:它實現了IBinder接口,是Activity整個框架中客戶端和服務端AMS之間通訊的接口,同時也是ActivityThread的內部類。這樣就有效的把ActivityThread和AMS綁定在一塊兒了。
  • Instrumentation:這個東西我把它理解爲ActivityThread的一個工具類,也算是一個勞動者吧,對於生命週期的全部操做例如onCreate最終都是直接由它來執行的。

Android系統中的客戶端和服務器的概念 
在Android系統中其實也存在着服務器和客戶端的概念,服務器端指的就是全部App共用的系統服務,好比上面的AMS,PackageManagerService等等,這些系統服務是被全部的App共用的,當某個App想要實現某個操做的時候,就會通知這些系統服務。
設計模式

繼續斷點1數組

當Zygote被初始化的時候,會fork出System Server進程,這個進程在整個的Android進程中是很是重要的一個,地位和Zygote等同,它是屬於Application Framework層的,Android中的全部服務,例如AMS, WindowsManager, PackageManagerService等等都是由這個SystemServer fork出來的。因此它的地位可見一斑。緩存

而當System Server進程開啓的時候,就會初始化AMS,同時,會加載本地系統的服務庫,建立系統上下文,建立ActivityThread及開啓各類服務等等。而在這以後,就會開啓系統的Launcher程序,完成系統界面的加載與顯示。【斷點2】安全

Context總結服務器

Context是一個抽象類,下面是它的註釋信息,摘自源碼。

  1. /**
  2. * Interface to global information about an application environment. This is
  3. * an abstract class whose implementation is provided by
  4. * the Android system. It
  5. * allows access to application-specific resources and classes, as well as
  6. * up-calls for application-level operations such as launching activities,
  7. * broadcasting and receiving intents, etc.
  8. */
  9. public abstract class Context {
複製代碼

從上面的這段話能夠簡單理解一下,Context是一個關於應用程序環境的全局變量接口,經過它能夠容許去得到資源或者類,例如啓動Activity,廣播,intent等等。

個人理解:Context的具體實現是Application, Activity,Service,經過Context可以有權限去作一些事情,其實我以爲就是一個運行環境的問題。

須要注意的地方 
Android開發中因爲不少地方都包含了Context的使用,所以就必需要注意到內存泄露或者是一些可能會引發的問題。

例如在Toast中,它的Context就最好設置爲Application Context,由於若是Toast在顯示東西的時候Activity關閉了,可是因爲Toast仍然持有Activity的引用,那麼這個Activity就不會被回收掉,也就形成了內存泄露。

Toast的相關總結

上面舉例的時候舉到了Toast,其實Toast也是頗有意思的一個東西,它的show方法其實並非顯示一個東西這麼簡單。 
Toast其實是一個隊列,會經過show方法把新的任務加入到隊列當中去,列隊中只要存在消息就會彈出來使用,而隊列的長度聽說默認是40個(這是網上搜出來的,我在源碼中沒找到對應的設置,感受也沒啥必要就沒找了)。 
因此這裏就要注意一下show這個操做了,它並非顯示內容,而是把內容入隊列。

  1. /**
  2. * Show the view for the specified duration.
  3. */
  4. public void show() {
  5. if (mNextView == null) {
  6. throw new RuntimeException("setView must have been called");
  7. }
  8. INotificationManager service = getService();
  9. String pkg = mContext.getOpPackageName();
  10. TN tn = mTN;
  11. tn.mNextView = mNextView;
  12. try {
  13. service.enqueueToast(pkg, tn, mDuration);
  14. } catch (RemoteException e) {
  15. // Empty
  16. }
  17. }
複製代碼

Handler的內存泄露

對於Handler來講,若是咱們直接在AndroidStudio中建立一個非靜態內部類Handler,那麼Handler這一大片的區域會被AS標記爲黃色,這個應該不少人都遇到過吧。其實是由於這樣設置會形成內存泄露,由於每個非靜態內部類都會持有一個外部類的引用,那麼這裏也就產生了一個內存泄露的可能點,若是當Activity被銷燬時沒有與Handler解除,那麼Handler仍然會持有對該Activity的引用,那麼就形成了內存泄露。

解決方案 
使用static修飾Handler,這樣也就成了一個靜態內部類,那麼就不會持有對外部類的引用了。而這個時候就能夠在Handler中建立一個WeakReference(弱引用)來持有外部的對象。只要外部解除了與該引用的綁定,那麼垃圾回收器就會在發現該弱引用的時候馬上回收掉它。

垃圾回收

關於垃圾回收的相關總結看我以前的博客,傳送門:JVM原理及底層探索

四種引用方式

上面扯到了弱引用,就再BB一下四種引用方式吧。

  • 強引用:垃圾回收器打死都不會回收掉一個強引用的,那怕是出現OOM也不會回收掉強引用,全部new出來的都是強引用。
  • 軟引用:垃圾回收器會在內存不足的狀況下回收掉軟引用,若是內存充足的話不會理它
  • 弱引用:它跟軟引用相似,可是它更脆弱,只要垃圾回收器一發現它,就會馬上回收掉它。好比一個對象持有一個強引用和弱引用,當強引用被取消時,那麼只要GC發現了它,就會馬上回收掉。只是GC發現它的這個過程是不肯定的,有可能不會立刻發生,因此它可能還會多活一會,中間存在一個優先級。
  • 虛引用:它跟上面3種方式都不一樣。我對虛引用的理解就是若是一個對象持有虛引用,那麼就能夠在被GC回收前進行一些設定好的工做等等。由於虛引用有個機制,由於虛引用必須和引用隊列聯合使用,當垃圾回收器準備回收一個對象時,若是發現它還有虛引用,就回在回收對象的內存前,把這個虛引用加入到與之關聯的引用隊列中。而程序若是判斷到引用隊列中已經加入了虛引用,那麼就能夠了解到被引用的對象立刻就要被垃圾回收了,這個時候就能夠作些被回收以前的事情啦。

ClassLoader

類加載器按層次從頂層到下依次爲Boorsrtap ClassLoader(啓動類加載器),Extension ClassLoader(拓展類加載器),ApplicationClassLoader(應用程序類加載器)

判斷兩個類是不是同一個類就是看它們是不是由同一個類加載器加載而來。

這裏就須要介紹一下雙親委派模式了: 
雙親委派模式的意思就是:除了啓動類加載器以外,其他的加載器都須要指定一個父類的加載器,當須要加載的時候會先讓父類去試着加載,若是父類沒法加載也就是找不到這個類的話就會讓子類去加載

好處:防止內存中出現多份一樣的字節碼

好比類A和類B都要加載system類,若是不是委託的話,類A就會加載一份,B也會加載一份,那麼就會出現兩份SYstem字節碼 
若是使用委託機制,會遞歸的向父類查找,也就是首選用Bootstrap嘗試加載,若是找不到再向下,若是A用這個已經加載了的話會直接返回內存中的system而不須要從新加載。那麼就只會存在一份

延遲加載的應用:單例模式

對於Java來講,類是須要使用到時纔會加載,這裏也就出現了一個延遲加載的效果。而在延遲加載的時候,會默認保持同步。這也就產生了一種單例模式的方式,具體的看我以前的博客:設計模式_單例模式

我以爲在android全部的建立單例模式方法中裏延遲加載方式是最好吧,雖然枚舉比延遲加載更好,effiective java中也很推薦,可是並不怎麼適用於Android,Android裏枚舉的消耗是static的兩倍,延遲加載的話只要咱們在使用延遲加載方式時作好反序列化的返回值readResolve()準備就行了。

繼續斷點2

上面BB了太多其餘的,如今有點緩不過來,下次本身看本身博客的時候會不會都被本身的思路帶得亂七八糟的。

上面的時候咱們就已經完成了整個Android系統的開機以及初始化。接下來就能夠B一下從點擊APP圖標開始到APP內部程序運行起來的流程了。

當咱們點擊屏幕時,觸摸屏的兩層電極會鏈接在一塊兒,也就產生了一個電壓(具體的我忘了,書上有,圖找不到了),當產生電壓的時候,就能夠經過對應的驅動把當前按壓點的XY座標傳給上層,這裏也就是操做系統。操做系統在獲取到XY值的時候,就會對按壓點的範圍進行一個判斷,若是肯定按壓點處於一個APP圖標或者是Button等等的範圍中時,操做系統也就會認爲用戶當前已經點擊了這個東西,啓動對應的監聽。

而當系統判斷咱們點擊的是APP圖標時,該App就由Launcher開始啓動了【斷點3】

Launcher

Launcher是一個繼承自Activity,同時實現了點擊事件,長按事件等等的一個應用程序。

  1. public final class Launcher extends Activity
  2. implements View.OnClickListener,OnLongClickListener, LauncherModel.Callbacks,View.OnTouchListener
複製代碼


當咱們點擊一個APP的圖標時,會調用Launcher內部的startActivitySafely()方法,而這個方法則會進行兩件事,一個是啓動目標activity,另外一個功能就是捕獲異常ActivityNotFoundException,也就是常見的「找不到activity,是否已經在androidmenifest文件中註冊?」。而在startActivity方法中,通過一系列的轉換最終會調用到startActivityForResult這個方法。

  1. @Override
  2. public void startActivity(Intent intent, @Nullable Bundle options) {
  3. if (options != null) {
  4. startActivityForResult(intent, -1, options);
  5. } else {
  6. // Note we want to go through this call for compatibility with
  7. // applications that may have overridden the method.
  8. startActivityForResult(intent, -1);
  9. }
  10. }
複製代碼

因此實際上,我對整個Android的界面是這樣理解的: 
當系統完成初始化以及各類服務的建立以後,就會啓動Launcher這個應用程序(它也是繼承自Activity的,包含本身對應的xml佈局文件),而後再把各類圖標按照一個正常APP佈局的方式放在上面,當咱們點擊APP圖標時,也就至關於在Launcher這個APP應用程序中經過startActivity(在底層最後會轉爲startActivityForResult)來啓動這個APP。簡單的講,我以爲就是一個主要的APP(Launcher)裏面啓動了其餘的功能APP,例如QQ、微信這些。【我的理解,若是之後發現不對再修改】

Android中點擊事件的處理

當咱們手指按下時,Android是如何處理點擊事件的呢?如何肯定是讓哪個控件來處理呢? 
簡單一句話:層層傳遞-冒泡的方式處理 
舉個例子:如今公司來了個小項目,老闆一看分配給經理作,經理一看分配給小組長,小組長一看好簡單,分配給組員。若是在這個傳遞過程當中(也就是還爲分配到最底部時),某一層以爲我來負責這個比較好的話就會攔截掉這個消息,而後把它處理了,下面的就收不到有消息的這個通知。若是一直到了底層的話,組員若是能完成,就完成它。若是不能完成,那麼就報告給組長,說組長我作不來,邊學邊作要影響進度。組長一看我也作不來,就給經理,經理一看我也不會,就給老闆。這樣也就一層層的傳遞了。 
總結一下就是消息從上到下依次傳遞,若是在傳遞的過程當中被攔截了就中止下傳。若是沒有被攔截,就一直傳遞到底部,若是底部不可以消耗該消息,那麼就又一層層的返回來,返給上層,直到被消耗或者是到達最頂層。

在Android中,存在三個重要的方法:

  • dispathTouchEvent(MotionEvent ev)
  • onInterceptTouchEvent(MotionEvent ev)
  • onTouchEvent(MotionEvent ev)

第一個方法負責事件的分發,它的返回值就是表示是否消耗當前事件。 
第二個方法是用於判斷是否攔截該消息,若是當前View攔截了某個時間,那麼在同一個事件序列中,此方法不會被再次調用。返回結果表示是否攔截當前事件 
第三個方法就是處理事件。返回結果表示是否消耗當前事件,若是不小號,則在同一時間序列中,當前View沒法再次接收到事件。

對於一個根ViewGroup來講,點擊事件產生後,首先會傳遞給它,調用它的dispath方法。若是這個ViewGroup的onIntercept方法返回true就表示它要攔截當前事件,false就表示不攔截,這個時候事件就會繼續傳遞給子元素,接着調用子元素的dispath方法,直到被處理。

滑動衝突

順帶總結一下滑動衝突的解決吧 
View的滑動衝突通常能夠分爲三種:

  • 外部滑動和內部滑動方向不一致
  • 外部滑動方向和內部滑動方向一致
  • 嵌套上面兩種狀況

好比說一個常見的,外部一個ListView,裏面一個ScrollView。這個時候該怎麼解決呢?其實這裏想到了ViewPager,它裏面其實是解決了滑動衝突的,能夠借鑑一下它的。

滑動處理規則 
通常來講,咱們能夠根據用戶手指滑動的方向以及角度來判斷用戶是要朝着哪一個方向去滑動。而不少時候還能夠根據項目的需求來指定一套合適的滑動方案。

外部攔截法 
這種方法就是指全部的點擊時間都通過父容器的攔截處理,若是父容器須要此時間就攔截,若是不須要此事件就不攔截。經過重寫父容器的onInterceptTouchEvent方法:

  1. case MotionEvent.ACTION_DOWN:
  2. intercepted = false;
  3. break;
  4. case MotionEvent.ACTION_MOVE:
  5. if(父類容器須要) {
  6. intercepted = true;
  7. } else {
  8. intercepted = false;
  9. }
  10. break;
  11. case MotionEvent.ACTION_UP:
  12. intercepted = false;
  13. break;
  14. return intercepted;
複製代碼

這裏有一點須要注意,ACTION_DOWN事件父類容器就必須返回false,由於若是父類容器攔截了的話,後面的Move等全部事件都會直接由父類容器處理,就沒法傳給子元素了。UP事件也要返回false,由於它自己來講沒有太多的意義,可是對於子元素就不一樣了,若是攔截了,那麼子元素的onClick事件就沒法觸發。

內部攔截法 
這種方法指的是父容器不攔截任什麼時候間,全部的事件都傳遞給子元素,若是子元素須要此事件就直接消耗掉,不然就交給父容器進行處理。它須要配合requestDisallowInterceptTouchEvent方法才能正常工做。咱們須要重寫子元素的dispatch方法

  1. case MotionEvent.ACTION_DOWN:
  2. parent.requestDisallowInterceptTouchEvent(true);
  3. break;
  4. MotionEvent.ACTION_MOVE:
  5. if(父容器須要此類點擊事件) {
  6. parent.requestDisallowInterceptTouchEvent(false);
  7. }
  8. break;
  9. return super.dispatchTouchEvent(event);
複製代碼

這種方法的話父類容器須要默認攔截除了ACTION_DOWN之外的其餘時間,這樣當子元素調用request方法的時候父元素才能繼續攔截所需的事件。

其餘的 
若是以爲上面兩個方式太複雜,看暈了,其實也能夠本身根據項目的實際須要來指定本身的策略實現。例如根據你手指按的點的位置來判斷你當前觸碰的是哪一個控件,以此來猜想用戶是不是要對這個控件進行操做。若是點擊的是空白的地方,就操做外部控件便可。

【等有時間了就把ViewPager的處理總結一下,挺重要的】

繼續斷點3

  • 當咱們點擊桌面的APP圖標時,Launcher進程會採用Binder的方式向AMS發出startActivity請求
  • AMS在接收到請求以後,就會經過Socket向Zygote進程發送建立進程的請求
  • Zygote進程會fork出新的子進程(APP進程)
  • 以後APP進程會再向AMS發起一次請求,AMS收到以後通過一系列的準備工做再回傳請求。
  • APP進程收到AMS返回的請求後,會利用Handler向主線程發送LAUNCH_ACTIVITY消息
  • 主線程在收到消息以後,就建立目標Activity,並回調onCreate()/onStart()/onResume()等方法,UI渲染結束後即可以看到App主界面 
    【斷點4】

Handler/Looper/Message Queue/ThreadLocal機制

Android的消息機制主要是指Handler的運行機制,Handler的運行須要底層的MessageQueue和Looper的支撐

雖然MessageQueue叫作消息隊列,可是實際上它內部的存儲結構是單鏈表的方式。因爲Message只是一個消息的存儲單元,它不能去處理消息,這個時候Looper就彌補了這個功能,Looper會以無限循環的形式去查找是否有新消息,若是有的話就處理消息,不然就一直等待(機制等會介紹)。而對於Looper來講,存在着另外的一個很重要的概念,就是ThreadLocal。

ThreadLocal

ThreadLocal它並非一個線程,而是一個能夠在每一個線程中存儲數據的數據存儲類,經過它能夠在指定的線程中存儲數據,數據存儲以後,只有在指定線程中能夠獲取到存儲的數據,對於其餘線程來講則沒法獲取到該線程的數據。 
舉個例子,多個線程經過同一個ThreadLocal獲取到的東西是不同的,就算有的時候出現的結果是同樣的(偶然性,兩個線程裏分別存了兩份相同的東西),但他們獲取的本質是不一樣的。

那爲何有這種區別呢?爲何要這樣設計呢? 
先來研究一下爲何會出現這個結果。 
在ThreadLocal中存在着兩個很重要的方法,get和set方法,一個讀取一個設置。

  1. /**
  2. * Returns the value of this variable for the current thread. If an entry
  3. * doesn't yet exist for this variable on this thread, this method will
  4. * create an entry, populating the value with the result of
  5. * {@link #initialValue()}.
  6. *
  7. * @return the current value of the variable for the calling thread.
  8. */
  9. @SuppressWarnings("unchecked")
  10. public T get() {
  11. // Optimized for the fast path.
  12. Thread currentThread = Thread.currentThread();
  13. Values values = values(currentThread);
  14. if (values != null) {
  15. Object[] table = values.table;
  16. int index = hash & values.mask;
  17. if (this.reference == table[index]) {
  18. return (T) table[index + 1];
  19. }
  20. } else {
  21. values = initializeValues(currentThread);
  22. }
  23. return (T) values.getAfterMiss(this);
  24. }
  25. /**
  26. * Sets the value of this variable for the current thread. If set to
  27. * {@code null}, the value will be set to null and the underlying entry will
  28. * still be present.
  29. *
  30. * @param value the new value of the variable for the caller thread.
  31. */
  32. public void set(T value) {
  33. Thread currentThread = Thread.currentThread();
  34. Values values = values(currentThread);
  35. if (values == null) {
  36. values = initializeValues(currentThread);
  37. }
  38. values.put(this, value);
  39. }
複製代碼

摘自源碼 
首先研究它的get方法吧,從註釋上能夠看出,get方法會返回一個當前線程的變量值,若是數組不存在就會建立一個新的。 
這裏有幾個很重要的詞,就是「當前線程」和「數組」。 
這裏提到的數組對於每一個線程來講都是不一樣的,values.table,而values是經過當前線程獲取到的一個Values對象,所以這個數組是每一個線程惟一的,不能共用,而下面的幾句話也更直接了,獲取一個索引,再返回經過這個索引找到數組中對應的值。這也就解釋了爲何多個線程經過同一個ThreadLocal返回的是不一樣的東西。

那這裏爲何要這麼設置呢?翻了一下書,搜了一下資料:

  • ThreadLocal在平常開發中使用到的地方較少,可是在某些特殊的場景下,經過ThreadLocal能夠輕鬆實現一些看起來很複雜的功能。通常來講,當某些數據是以線程爲做用域而且不一樣線程具備不一樣的數據副本的時候,就能夠考慮使用ThreadLocal。例如在Handler和Looper中。對於Handler來講,它須要獲取當前線程的Looper,很顯然Looper的做用域就是線程而且不一樣的線程具備不一樣的Looper,這個時候經過ThreadLocal就能夠輕鬆的實現Looper在線程中的存取。若是不採用ThreadLocal,那麼系統就必須提供一個全局的哈希表供Handler查找指定的Looper,這樣就比較麻煩了,還須要一個管理類。
  • ThreadLocal的另外一個使用場景是複雜邏輯下的對象傳遞,好比監聽器的傳遞,有些時候一個線程中的任務過於複雜,就可能表現爲函數調用棧比較深以及代碼入口的多樣性,這種狀況下,咱們又須要監聽器可以貫穿整個線程的執行過程。這個時候就可使用到ThreadLocal,經過ThreadLocal可讓監聽器做爲線程內的全局對象存在,在線程內經過get方法就能夠獲取到監聽器。若是不採用的話,可使用參數傳遞,可是這種方式在設計上不是特別好,當調用棧很深的時候,經過參數來傳遞監聽器這個設計太糟糕。而另一種方式就是使用static靜態變量的方式,可是這種方式存在必定的侷限性,拓展性並非特別的強。好比有10個線程在執行,就須要提供10個監聽器對象。

消息機制

上面提到了Handler/Looper/Message Queue,它們其實是一個總體,只不過咱們在開發中接觸更多的是Handler而已,Handler的主要做用是將一個任務切換到某個指定的線程中去執行,而Android之因此提供這個機制是由於Android規定UI只能在主線程中進程,若是在子線程中訪問UI就會拋出異常。

爲何Android不容許在子線程訪問UI 
其實這一點不只僅是對於Android,對於其餘的全部圖形界面如今都採用的是單線程模式。 
由於對於一個多線程來講,若是子線程更改了UI,那麼它的相關操做就必須對其餘子線程可見,也就是Java併發中很重要的一個概念,線程可見性,Happen-before原則【下篇博客總結一下本身對Java併發的理解吧,挺重要的,總結完後再把傳送門貼過來】而通常來講,對於這種併發訪問,通常都是採用加鎖的機制,可是加鎖的機制存在很明顯的問題:讓UI訪問間的邏輯變得複雜,同時效率也會下降。甚至有的時候還會形成死鎖的狀況,這個時候就麻煩了。 
而至於究竟能不可以實現這種UI界面的多線程呢?SUN公司的某個大牛(忘了是誰,好久以前看的,好像是前副總裁)說:「行確定是沒問題,可是很是考技術,由於必需要考慮到不少種狀況,這個時候就須要技術專家來設計。而這種設計出來的東西對於廣大普通程序員來講又是異常頭疼的,就算是實現了多線程,普通人用起來也是怨聲載道的。因此建議仍是單線程」。

死鎖

順帶着BB一下死鎖。

死鎖的四個必要條件

  • 互斥條件:資源不能被共享,只能被同一個進程使用
  • 請求與保持條件:已經獲得資源的進程能夠申請新的資源
  • 非剝奪條件:已經分配的資源不能從相應的進程中被強制剝奪
  • 循環等待條件:系統中若干進程組成環路,該環路中每一個進程都在等待相鄰進程佔用的資源

舉個常見的死鎖例子:進程A中包含資源A,進程B中包含資源B,A的下一步須要資源B,B的下一步須要資源A,因此它們就互相等待對方佔有的資源釋放,因此也就產生了一個循環等待死鎖。

處理死鎖的方法

  • 忽略該問題,也就是鴕鳥算法。當發生了什麼問題時,無論他,直接跳過,無視它。
  • 檢測死鎖並恢復
  • 資源進行動態分配
  • 破除上面的四種死鎖條件之一

繼續消息機制

MessageQueue主要包含兩個操做:插入和讀取,讀取操做自己會伴隨着刪除操做,插入和讀取對應的方法分別爲enqueueMessage和next,其中enqueueMessage的做用是往消息隊列中插入一條消息,而next的做用是從消息隊列中取出一條消息並將其從消息隊列中移除。這也就是爲何使用的是一個單鏈表的數據結構來維護消息列表,由於它在插入和刪除上比較有優點(把下一個鏈接的點切換一下就完成了)。

而對於MessageQueue的插入操做來講,沒什麼能夠看的,也就這樣吧,主要須要注意的是它的讀取方法next。

  1. Message next() {
  2. // Return here if the message loop has already quit and been disposed.
  3. // This can happen if the application tries to restart a looper after quit
  4. // which is not supported.
  5. final long ptr = mPtr;
  6. if (ptr == 0) {
  7. return null;
  8. }
  9. int pendingIdleHandlerCount = -1; // -1 only during first iteration
  10. int nextPollTimeoutMillis = 0;
  11. for (;;) {
  12. if (nextPollTimeoutMillis != 0) {
  13. Binder.flushPendingCommands();
  14. }
  15. nativePollOnce(ptr, nextPollTimeoutMillis);
  16. synchronized (this) {
  17. // Try to retrieve the next message. Return if found.
  18. final long now = SystemClock.uptimeMillis();
  19. Message prevMsg = null;
  20. Message msg = mMessages;
  21. if (msg != null && msg.target == null) {
  22. // Stalled by a barrier. Find the next asynchronous message in the queue.
  23. do {
  24. prevMsg = msg;
  25. msg = msg.next;
  26. } while (msg != null && !msg.isAsynchronous());
  27. }
  28. if (msg != null) {
  29. if (now < msg.when) {
  30. // Next message is not ready. Set a timeout to wake up when it is ready.
  31. nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
  32. } else {
  33. // Got a message.
  34. mBlocked = false;
  35. if (prevMsg != null) {
  36. prevMsg.next = msg.next;
  37. } else {
  38. mMessages = msg.next;
  39. }
  40. msg.next = null;
  41. if (DEBUG) Log.v(TAG, "Returning message: " + msg);
  42. msg.markInUse();
  43. return msg;
  44. }
  45. } else {
  46. // No more messages.
  47. nextPollTimeoutMillis = -1;
  48. }
  49. // Process the quit message now that all pending messages have been handled.
  50. if (mQuitting) {
  51. dispose();
  52. return null;
  53. }
  54. // If first time idle, then get the number of idlers to run.
  55. // Idle handles only run if the queue is empty or if the first message
  56. // in the queue (possibly a barrier) is due to be handled in the future.
  57. if (pendingIdleHandlerCount < 0
  58. && (mMessages == null || now < mMessages.when)) {
  59. pendingIdleHandlerCount = mIdleHandlers.size();
  60. }
  61. if (pendingIdleHandlerCount <= 0) {
  62. // No idle handlers to run. Loop and wait some more.
  63. mBlocked = true;
  64. continue;
  65. }
  66. if (mPendingIdleHandlers == null) {
  67. mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
  68. }
  69. mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
  70. }
  71. // Run the idle handlers.
  72. // We only ever reach this code block during the first iteration.
  73. for (int i = 0; i < pendingIdleHandlerCount; i++) {
  74. final IdleHandler idler = mPendingIdleHandlers[i];
  75. mPendingIdleHandlers[i] = null; // release the reference to the handler
  76. boolean keep = false;
  77. try {
  78. keep = idler.queueIdle();
  79. } catch (Throwable t) {
  80. Log.wtf(TAG, "IdleHandler threw exception", t);
  81. }
  82. if (!keep) {
  83. synchronized (this) {
  84. mIdleHandlers.remove(idler);
  85. }
  86. }
  87. }
  88. // Reset the idle handler count to 0 so we do not run them again.
  89. pendingIdleHandlerCount = 0;
  90. // While calling an idle handler, a new message could have been delivered
  91. // so go back and look again for a pending message without waiting.
  92. nextPollTimeoutMillis = 0;
  93. }
  94. }
複製代碼

源碼有點長,總結一下就是: 
next方法它是一個死循環,若是消息隊列中沒有消息,那麼next方法就會一直阻塞在這裏,當有新的消息來的時候,next方法就會返回這條信息並將其從單鏈表中移除。

而這個時候勒Looper就等着的,它也是一直循環循環,不停地從MessageQueue中查看是否有新消息,若是有新消息就會馬上處理,不然就會一直阻塞在那裏。而對於Looper來講,它是隻能建立一個的,這個要歸功與它的prepare方法。

  1. /** Initialize the current thread as a looper.
  2. * This gives you a chance to create handlers that then reference
  3. * this looper, before actually starting the loop. Be sure to call
  4. * {@link #loop()} after calling this method, and end it by calling
  5. * {@link #quit()}.
  6. */
  7. public static void prepare() {
  8. prepare(true);
  9. }
  10. private static void prepare(boolean quitAllowed) {
  11. if (sThreadLocal.get() != null) {
  12. throw new RuntimeException("Only one Looper may be created per thread");
  13. }
  14. sThreadLocal.set(new Looper(quitAllowed));
  15. }
複製代碼


從這裏咱們就能夠看出該prepare方法會首先檢測是否已經存在looper了,若是不存在,就建立一個新的;若是存在,就拋出異常。 
而以後使用Looper.loop()就能夠開啓消息循環了。

  1. /**
  2. * Run the message queue in this thread. Be sure to call
  3. * {@link #quit()} to end the loop.
  4. */
  5. public static void loop() {
  6. final Looper me = myLooper();
  7. if (me == null) {
  8. throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
  9. }
  10. final MessageQueue queue = me.mQueue;
  11. // Make sure the identity of this thread is that of the local process,
  12. // and keep track of what that identity token actually is.
  13. Binder.clearCallingIdentity();
  14. final long ident = Binder.clearCallingIdentity();
  15. for (;;) {
  16. Message msg = queue.next(); // might block
  17. if (msg == null) {
  18. // No message indicates that the message queue is quitting.
  19. return;
  20. }
  21. // This must be in a local variable, in case a UI event sets the logger
  22. Printer logging = me.mLogging;
  23. if (logging != null) {
  24. logging.println(">>>>> Dispatching to " + msg.target + " " +
  25. msg.callback + ": " + msg.what);
  26. }
  27. msg.target.dispatchMessage(msg);
  28. if (logging != null) {
  29. logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
  30. }
  31. // Make sure that during the course of dispatching the
  32. // identity of the thread wasn't corrupted.
  33. final long newIdent = Binder.clearCallingIdentity();
  34. if (ident != newIdent) {
  35. Log.wtf(TAG, "Thread identity changed from 0x"
  36. + Long.toHexString(ident) + " to 0x"
  37. + Long.toHexString(newIdent) + " while dispatching to "
  38. + msg.target.getClass().getName() + " "
  39. + msg.callback + " what=" + msg.what);
  40. }
  41. msg.recycleUnchecked();
  42. }
  43. }
複製代碼

從這裏面咱們能夠看到它也是個死循環,會不停的調用queue.next()方法來獲取信息,若是沒有,就return,若是有就處理。

注意 
固然了,這裏有一個很重要的點,通常可能會忘,那就是在子線程中若是手動爲其建立了Looper,那麼在全部的事情完成之後應該調用quit方法來終止消息循環,不然這個子線程就會一直處於等待狀態,而若是退出Looper以後,這個線程就會馬上終止,因此建議不須要使用的時候終止Looper。

Handler 
上面總結了Looper和MessageQueue,這裏就對Handler進行一個總結吧。它的工做主要包含消息的發送和接受過程,消息的發送能夠經過post的一系列方法以及send的一系列方法來實現,post的一系列方法最終是經過send的一系列方法來實現的。 
實際上它發送消息的過程僅僅是向消息隊列中插入了一條消息,MessageQueue的next方法就會返回這條消息給Looper,Looper在收到消息以後就會開始處理了。最後由Looper交給Handler處理(handleMessage()方法)。

IPC通訊

上面總結完了Android的消息處理機制,那麼就順帶總結一下IPC通訊吧,畢竟上面提到過那麼屢次Binder和Socket。

資料:爲何Android要採用Binder做爲IPC機制? 
知乎上面的回答至關的好,這個博主對系統底層也是很有鑽研,學習。

這裏就結合上面的知乎回答以及加上《Linux程序設計》還有一本Linux內核剖析(書名忘了可是講得真的很是好),摻雜一些我的的理解。

進程的定義 
UNIX標準把進程定義爲:「一個其中運行着一個或多個進程的地址控件和這些線程所須要的系統資源」。目前,能夠簡單的把進程看作正在運行的程序。

進程都會被分配一個惟一的數字編號,咱們成爲PID(也就是進程標識符),它一般是一個取值範圍從2到32768的正整數。當進程被啓動時,系統將按順序選擇下一個未被使用的數字做爲PID,當數字已經迴繞一圈時,新的PID從新從2開始,數字1通常是爲init保留的。在進程中,存在一個本身的棧空間,用於保存函數中的局部變量和控制函數的調用與返回。進程還有本身的環境空間,包含專門爲這個進程創建的環境變量,同時還必需要維護本身的程序計數器,這個計數器用來記錄它執行到的位置,即在執行線程中的位置。 
在Linux中能夠經過system函數來啓動一個進程

守護進程 
這裏就須要提到一個守護進程了,這個在全部的底層中常常都會被提到。 
在linux或者unix操做系統中在系統引導的時候會開啓不少服務,這些服務就叫作守護進程。爲了增長靈活性,root能夠選擇系統開啓的模式,這些模式叫作運行級別,每一種運行級別以必定的方式配置系統。 守護進程是脫離於終端而且在後臺運行的進程。守護進程脫離於終端是爲了不進程在執行過程當中的信息在任何終端上顯示而且進程也不會被任何終端所產生的終端信息所打斷。 
守護進程經常在系統引導裝入時啓動,在系統關閉時終止。若是想要某個進程不由於用戶或終端或其餘的變化而受到影響,那麼就必須把這個進程變成一個守護進程

防止手機服務後臺被殺死 
是否是在手機的設置界面看當前正在運行的服務時會發現有的APP不止存在一個服務?有的APP後臺存在兩個,有的存在三個?有的流氓軟件也會這麼設置,這樣的話就能夠一直運行在後臺,用戶你關也關不了(倒不是說全部這麼設置的都是流氓軟件,由於有的軟件須要保持一個長期的後臺在線,這是由功能決定的)。

這裏有兩種方法(可能還有更多,這裏只總結我瞭解的):

  • 第一種方法就是利用android中service的特性來設置,防止手機服務後臺被殺死。經過更改onStartCommand方法的返回值,將service設置爲粘性service,那麼當service被kill的時候就會將服務的狀態返回到最開始啓動的狀態,也就是運行的狀態,因此這個時候也就會再次從新運行。可是須要注意一點,這個時候的intent值就爲空了,獲取的話須要注意一下這一點。
  • 第二種就是fork出一個C的進程,由於在Linux中,子類進程在父類被殺死銷燬的時候不會隨之殺死,它會被init進程領養。因此也就可使用這一個方法,利用主進程fork出一個C進程在後臺運行,一旦檢測到服務被殺死(檢測的方式多種,可以使用觀察者模式,廣播,輪詢等等),就重啓服務便可

IPC通訊 
上面總結了進程的相關基礎,這裏就開始總結一下進程間通訊(IPC 
)的問題了。 
如今Linux現有的全部IPC方式:

  • 管道:在建立時分配一個page大小的內存,緩存區大小有限
  • 消息隊列:信息複製兩次,額外的cpu消耗,不適合頻繁或信息量大的通訊
  • 共享內存:無需複製,共享緩衝區直接附加到進程虛擬地址控件,速度是在全部IPC通訊中最快的。可是進程間的同步問題操做系統沒法實現,必須由各進程利用同步工具解決。
  • Socket:做爲更通用的接口,傳輸效率低,主要用於不通機器或跨網絡的通訊
  • 信號量:常做爲一種鎖機制。
  • 信號:不適用於信息交換,更適用於進程件中斷控制,例如kill process

到了這裏,就有了問題,爲何在Linux已經存在這麼多優良的IPC方案時,Android還要採起一種新的Binder機制呢? 
猜想:我以爲Android採用這種新的方式(固然也大面積的同時使用Linux的IPC通訊方式),最多兩個緣由:

  • 推廣時手機廠商自定義ROM底層的保密性或者公司之間的關係。
  • 在某些狀況下更適合手機這種低配置,對效率要求極高,用戶體驗極其重要的設備

資料

對於Binder來講,存在着如下的優點:

  • 性能角度:Binder的數據拷貝只須要一次,而管道、消息隊列、Socket都須要2次,而共享內存是一次都不須要拷貝,所以Binder的性能僅次於共享內存
  • 穩定性來講:Binder是基於C/S架構的,也就是Client和Server組成的架構,Client端有什麼需求,直接發送給Server端去完成,架構清晰,分工明確。而共享內存的實現方式複雜,須要充分考慮訪問臨界資源的併發同步問題,不然可能會出現死鎖等問題。從穩定性來講,Binder的架構優於共享內存。
  • 從安全的角度:Linux的傳統IPC方式的接收方沒法得到對方進程可靠的UID(用戶身份證實)/PID(進程身份證實),從而沒法鑑別對方身份,而Android是一個對安全性能要求特別高的操做系統,在系統層面須要對每個APP的權限進行管控或者監視,對於普通用戶來講,絕對不但願從App商店下載偷窺隱射數據、後臺形成手機耗電等問題。傳統的Linux IPC無任何保護措施,徹底由上層協議來確保。而在Android中,操做系統爲每一個安裝好的應用程序分配了本身的UID,經過這個UID能夠鑑別進程身份。同時Android系統對外只暴露Client端,Client端將任務發送給Server端,Server端會根據權限控制策略判斷UID/PID是否知足訪問權限。也就是說Binder機制對於通訊雙方的身份是內核進行校驗支持的。例如Socket方式只須要指導地址就能夠鏈接,他們的安全機制須要上層協議來假設
  • 從語言角度:Linux是基於C的,而Android是基於Java的,而Binder是符合面向對象思想的。它的實體位於一個進程中,而它的引用遍及與系統的各個進程之中,它是一個跨進程引用的對象,模糊了進程邊界,淡化了進程通訊的過程,整個系統彷彿運行於同一個面向對象的程序之中。
  • 從公司角度:Linux內核是開源的,GPL協議保護,受它保護的Linux Kernel是運行在內核控件,對於上層的任何類庫、服務等只要進行系統調用,調用到底層Kernel,那麼也必須遵循GPL協議。而對於Android來講,Google巧妙地將GPL協議控制在內核控件,將用戶控件的協議採用Apache-2.0協議(容許基於Android的開發商不向社區反饋源碼)。

反射

剛纔談到Binder的時候提了一下效率的問題,那這裏就不得不講到反射了。

反射它容許一個類在運行過程當中得到任意類的任意方法,這個是Java語言的一個很重要的特性。它方便了程序員的編寫,可是下降了效率。

實際上,對於只要不是特別大的項目(非Android),反射對於效率的影響微乎其微,而與之對比的開發成原本說就更划算了。 
可是,Android是一個用於手機的,它的硬件設施有限,咱們必需要考慮到它的這個因素,用戶體驗是最重要的。之前看到過國外的一項統計。在一個APP中的Splash中使用了反射,結果運行時間增長了一秒,這個已經算是很嚴重的效率影響了。

爲何反射影響效率呢 
這裏就須要提到一個東西,JIT編譯器。JIT編譯器它能夠把字節碼文件轉換爲機器碼,這個是能夠直接讓處理器使用的,通過它處理的字節碼效率提高很是大,可是它有一個缺點,就是把字節碼轉換成機器碼的過程很慢,有的時候甚至還超過了不轉換的代碼效率(轉換以後存在一個複用的問題,對於轉換了的機器碼,使用的次數越多就越值的)。所以,在JVM虛擬機中,也就產生了一個機制,把經常使用的、使用頻率高的字節碼經過JIT編譯器轉換,而頻率低的就無論它。而反射的話則是直接越過了JIT編譯器,無論是經常使用的仍是很是用的字節碼一概沒有通過JIT編譯器的轉化,因此效率就會低。 
而在Android裏面,5.0以前使用的是Davlik虛擬機,它就是上面的機制,而在Android5.0以後Google使用了一個全新的ART虛擬機全面代替Davlik虛擬機。 
ART虛擬機會在程序安裝時直接把全部的字節碼所有轉化爲機器碼,雖然這樣會致使安裝時間邊長,可是程序運行的效率提高很是大。 
【疑問:那在Android5.0以後的系統上,反射會不會沒影響了?因爲如今作項目的時候更多考慮的是向下兼容,單獨考慮5.0的狀況尚未,等之後有需求或者是有機會的時候再深刻了解一下,之後更新】

繼續斷點4

剛纔總結了Android的消息處理機制和IPC通訊,那麼咱們主線程的消息處理機制是何時開始的呢?由於咱們知道在主線程中咱們是不須要手動調用Looper.prepare()和Looper.loop()的。

Android的主線程就是ActivityThread,主線程的入口方法是main方法,在main方法中系統會經過Looper.prepareMainLooper()來建立主線程的Looper以及MessageQueue,並經過Looper.loop來開啓消息循環,因此這一步其實是系統已經爲咱們作了,咱們就再也不須要本身來作。 
ActivityThread經過AppplicationThread和AMS進行進程件通訊,AMS以進程間通訊的方式完成ActivityThread的請求後會回調ApplicationThread中的Binder方法,而後ApplicationThread會向Handler發送消息,Handler收到消息後會將ApplicationThread中的邏輯切換到主線程中去執行,這個過程就是主線程的消息循環模型。

上面總結到了APP開始運行,依次調用onCreate/onStart/onResume等方法,那麼在onCreate方法中咱們常用的setContentView和findViewById作了什麼事呢?

Activity界面顯示

首先,就考慮到第一個問題,也就是setContentView這個東西作了什麼事,這裏就要對你當前繼承的Activity分類了,若是是繼承的Activity,那麼setContentView源碼是這樣的:

  1. /**
  2. * Set the activity content from a layout resource. The resource will be
  3. * inflated, adding all top-level views to the activity.
  4. *
  5. * @param layoutResID Resource ID to be inflated.
  6. *
  7. * @see #setContentView(android.view.View)
  8. * @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)
  9. */
  10. public void setContentView(@LayoutRes int layoutResID) {
  11. getWindow().setContentView(layoutResID);
  12. initWindowDecorActionBar();
  13. }
  14. /**
  15. * Set the activity content to an explicit view. This view is placed
  16. * directly into the activity's view hierarchy. It can itself be a complex
  17. * view hierarchy. When calling this method, the layout parameters of the
  18. * specified view are ignored. Both the width and the height of the view are
  19. * set by default to {@link ViewGroup.LayoutParams#MATCH_PARENT}. To use
  20. * your own layout parameters, invoke
  21. * {@link #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)}
  22. * instead.
  23. *
  24. * @param view The desired content to display.
  25. *
  26. * @see #setContentView(int)
  27. * @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)
  28. */
  29. public void setContentView(View view) {
  30. getWindow().setContentView(view);
  31. initWindowDecorActionBar();
  32. }
  33. /**
  34. * Set the activity content to an explicit view. This view is placed
  35. * directly into the activity's view hierarchy. It can itself be a complex
  36. * view hierarchy.
  37. *
  38. * @param view The desired content to display.
  39. * @param params Layout parameters for the view.
  40. *
  41. * @see #setContentView(android.view.View)
  42. * @see #setContentView(int)
  43. */
  44. public void setContentView(View view, ViewGroup.LayoutParams params) {
  45. getWindow().setContentView(view, params);
  46. initWindowDecorActionBar();
  47. }
複製代碼


這裏面存在着3個重載函數,而無論你調用哪個,最後都會調用到initWindowDecorActionBar()這個方法。 
而對於新的一個AppcompatActivity,這個Activity裏面包含了一些新特性,如今我作的項目裏基本都是使用AppcompatActivity代替掉原來的Activity,固然也並非必定的,仍是要根據項目的實際狀況來選擇。 
在AppcompatActivity中,setContentView是這樣的:

  1. @Override
  2. public void setContentView(@LayoutRes int layoutResID) {
  3. getDelegate().setContentView(layoutResID);
  4. }
  5. @Override
  6. public void setContentView(View view) {
  7. getDelegate().setContentView(view);
  8. }
  9. @Override
  10. public void setContentView(View view, ViewGroup.LayoutParams params) {
  11. getDelegate().setContentView(view, params);
  12. }
複製代碼


同樣的3個重載函數,只是裏面沒有了上面的那個init方法,取而代之的是一個getDelegate().setContentView,這個delegate從字面上能夠了解到它是一個委託的對象,源碼是這樣的:

  1. /**
  2. * @return The {@link AppCompatDelegate} being used by this Activity.
  3. */
  4. @NonNull
  5. public AppCompatDelegate getDelegate() {
  6. if (mDelegate == null) {
  7. mDelegate = AppCompatDelegate.create(this, this);
  8. }
  9. return mDelegate;
  10. }
  11. 而在AppCompatDelegate.Create方法中,則會返回一個頗有意思的東西:
  12. /**
  13. * Create a {@link android.support.v7.app.AppCompatDelegate} to use with {@code activity}.
  14. *
  15. * @param callback An optional callback for AppCompat specific events
  16. */
  17. public static AppCompatDelegate create(Activity activity, AppCompatCallback callback) {
  18. return create(activity, activity.getWindow(), callback);
  19. }
  20. private static AppCompatDelegate create(Context context, Window window,
  21. AppCompatCallback callback) {
  22. final int sdk = Build.VERSION.SDK_INT;
  23. if (sdk >= 23) {
  24. return new AppCompatDelegateImplV23(context, window, callback);
  25. } else if (sdk >= 14) {
  26. return new AppCompatDelegateImplV14(context, window, callback);
  27. } else if (sdk >= 11) {
  28. return new AppCompatDelegateImplV11(context, window, callback);
  29. } else {
  30. return new AppCompatDelegateImplV7(context, window, callback);
  31. }
  32. }
複製代碼

這裏會根據SDK的等級來返回不一樣的東西,這樣的話就不深究了,底層的話我撇了一下,應該原理和Activity是同樣的,可能存在一些區別。這裏就用Activity來談談它的setContentView方法作了什麼事。

在setContentView上面有段註釋:

Set the activity content from a layout resource. The resource will be inflated, adding all top-level views to the activity.

這裏就介紹了它的功能,它會按照一個佈局資源去設置Activity的內容,而這個佈局資源將會被引入而後添加全部頂級的Views到這個Activity當中。 
這是個啥意思勒。 
下面從網上扒了一張圖: 
 
這裏是整個Activity的層級,最外面一層是咱們的Activity,它包含裏面的全部東西。 
再上一層是一個PhoneWindow,這個PhoneWindow是由Window類派生出來的,每個PhoneWindow中都含有一個DecorView對象,Window是一個抽象類。 
再上面一層就是一個DecorView,我理解這個DecorView就是一個ViewGroup,就是裝View的。 
而在DecoreView中,最上面的View就是咱們的TitleActionBar,下面就是咱們要設置的content。因此在上面的initWindowDecorActionBar就能猜到是什麼意思了吧。

而在initWindowDecorActionBar方法中,有一段代碼:

  1. /**
  2. * Creates a new ActionBar, locates the inflated ActionBarView,
  3. * initializes the ActionBar with the view, and sets mActionBar.
  4. */
  5. private void initWindowDecorActionBar() {
  6. Window window = getWindow();
  7. // Initializing the window decor can change window feature flags.
  8. // Make sure that we have the correct set before performing the test below.
  9. window.getDecorView();
  10. if (isChild() || !window.hasFeature(Window.FEATURE_ACTION_BAR) || mActionBar != null) {
  11. return;
  12. }
  13. mActionBar = new WindowDecorActionBar(this);
  14. mActionBar.setDefaultDisplayHomeAsUpEnabled(mEnableDefaultActionBarUp);
  15. mWindow.setDefaultIcon(mActivityInfo.getIconResource());
  16. mWindow.setDefaultLogo(mActivityInfo.getLogoResource());
  17. }
複製代碼




注意上面的window.getDecoreView()方法的註釋,該方法會設置一些window的標誌位,而當這個方法執行完以後,就不再能更改了,這也就是爲何不少第三方SDK設置window的標誌位時必定要求要在setContentView方法前調用。

findViewById

咱們經過一個findViewById方法能夠實現對象的綁定,那它底層到底是怎麼實現的呢?

findViewById根據繼承的Activity類型的不一樣也存在着區別,老規矩,仍是以Activity的來。

  1. /**
  2. * Finds a view that was identified by the id attribute from the XML that
  3. * was processed in {@link #onCreate}.
  4. *
  5. * @return The view if found or null otherwise.
  6. */
  7. @Nullable
  8. public View findViewById(@IdRes int id) {
  9. return getWindow().findViewById(id);
  10. }
複製代碼




從源碼來看,findViewById也是通過了一層層的調用,它的功能如同它上面的註釋同樣,經過一個view的id屬性查找view,這裏也能夠看到一個熟悉的getWindow方法,說明findViewById()實際上Activity把它也是交給了本身的window來作

  1. /**
  2. * Finds a view that was identified by the id attribute from the XML that
  3. * was processed in {@link android.app.Activity#onCreate}. This will
  4. * implicitly call {@link #getDecorView} for you, with all of the
  5. * associated side-effects.
  6. *
  7. * @return The view if found or null otherwise.
  8. */
  9. @Nullable
  10. public View findViewById(@IdRes int id) {
  11. return getDecorView().findViewById(id);
  12. }
複製代碼




而在這裏面,又調用了getDecorView的findViewById()方法,這也至關因而一個層層傳遞的過程,由於DecorView我理解爲就是一個ViewGroup,而當運行getDecorView().findViewById()方法時,就會運行View裏面的findViewById方法。它會使用這個被給予的id匹配子View的Id,若是匹配,就返回這個View,完成View的綁定

  1. /**
  2. * Look for a child view with the given id. If this view has the given
  3. * id, return this view.
  4. *
  5. * @param id The id to search for.
  6. * @return The view that has the given id in the hierarchy or null
  7. */
  8. @Nullable
  9. public final View findViewById(@IdRes int id) {
  10. if (id < 0) {
  11. return null;
  12. }
  13. return findViewTraversal(id);
  14. }
  15. /**
  16. * {@hide}
  17. * @param id the id of the view to be found
  18. * @return the view of the specified id, null if cannot be found
  19. */
  20. protected View findViewTraversal(@IdRes int id) {
  21. if (id == mID) {
  22. return this;
  23. }
  24. return null;
  25. }
複製代碼

最後總結一下(Activity中),findViewById的過程是這樣的: Activity -> Window -> DecorView -> View

相關文章
相關標籤/搜索