Activity 的生命周期和啟動模式
Tips
- 新Activity是透明主題時,舊Activity不會走onStop;
- Activity切換時,舊Activity的onPause會先執(zhí)行,然后才會啟動新的Activity;
- Activity在異常情況下被回收時,onSaveInstanceState方法會被回調(diào),回調(diào)時機(jī)是在onStop之前,當(dāng)Activity被重新創(chuàng)建的時 候,onRestoreInstanceState方法會被回調(diào),時序在onStart之后;
- 標(biāo)識Activity任務(wù)棧名稱的屬性:TaskAffinity,默認(rèn)為應(yīng)用包名。
Activity的LaunchMode
- standard 系統(tǒng)默認(rèn)。每次啟動會重新創(chuàng)建新的實例,誰啟動了這個Activity,這個Activity就在誰的棧里。
- singleTop 棧頂復(fù)用模式。該Activity的onNewIntent方法會被回調(diào),onCreate和onStart并不會被調(diào)用。
- singleTask 棧內(nèi)復(fù)用模式。只要該Activity在一個棧中存在,都不會重新創(chuàng)建,onNewIntent會被回調(diào)。如果不存在,系統(tǒng)會先尋找是否存在需要的棧,如果不存在該棧,就創(chuàng)建一個任務(wù)棧,然后把這個Activity放進(jìn)去;如果存在,就會創(chuàng)建到已經(jīng)存在的這個棧中。
- singleInstance。具有此種模式的Activity只能單獨存在于一個任務(wù)棧。
IntentFilter匹配規(guī)則。
- action匹配規(guī)則:要求intent中的action 存在 且 必須和過濾規(guī)則中的其中一個相同 區(qū)分大小寫;
- category匹配規(guī)則:系統(tǒng)會默認(rèn)加上一個android.intent.category.DEAFAULT,所以intent中可以不存在category,但如果存在就必須匹配其中一個;
- data匹配規(guī)則:data由兩部分組成,mimeType和URI,要求和action相似。如果沒有指定URI,URI但默認(rèn)值為content和file(schema)
IPC 機(jī)制
使用android:process會帶來的問題
- 靜態(tài)成員和單例模式完全失效;
- SharedPreferences可靠性下降;
- Application會多次創(chuàng)建;
Binder的工作機(jī)制
Android中的IPC方式
Bundle
文件共享(不建議使用系統(tǒng)的SharedPreferences)
Messenger(輕量級IPC,底層依然是AIDL)工作原理(Page70 & 代碼)
AIDL
4.1. AIDL支持的數(shù)據(jù)類型:基本數(shù)據(jù)類型;String和CharSequence;List只支持ArrayList,里面每個元素都必須被AIDL支持;Map只支持HashMap,里面每個元素都必須被AIDL支持(包括key和value);Parcelable;AIDL接口本身;
4.2. 服務(wù)端可以使用CopyOnWriteArrayList和ConcurrentHashMap來進(jìn)行自動線程同步,客戶端拿到的依然是ArrayList和HashMap;
4.3. 服務(wù)端和客戶端之間做監(jiān)聽器,服務(wù)端需要使用RemoteCallbackList,否則客戶端的監(jiān)聽器無法收到通知(因為服務(wù)端實質(zhì)還是一份新的序列化后的監(jiān)聽器實例,并不是客戶端那份);
dd. 客戶端調(diào)用遠(yuǎn)程服務(wù)方法時,因為遠(yuǎn)程方法運行在服務(wù)端的binder線程池中,同時客戶端線程會被掛起,所以如果該方法過于耗時,而客戶端又是UI線程,會導(dǎo)致ANR,所以當(dāng)確認(rèn)該遠(yuǎn)程方法是耗時操作時,應(yīng)避免客戶端在UI線程中調(diào)用該方法。同理,當(dāng)服務(wù)器調(diào)用客戶端的listener方法時,該方法也運行在客戶端的binder線程池中,所以如果該方法也是耗時操作,請確認(rèn)運行在服務(wù)端的非UI線程中。另外,因為客戶端的回調(diào)listener運行在binder線程池中,所以更新UI需要用到handler。
4.4. 客戶端通過IBinder.DeathRecipient來監(jiān)聽Binder死亡,也可以在onServiceDisconnected中監(jiān)聽并重連服務(wù)端。區(qū)別在于前者是在binder線程池中,訪問UI需要用Handler,后者則是UI線程。
4.5. 可通過自定義權(quán)限在onBind或者onTransact中進(jìn)行權(quán)限驗證。ContentProvider
Socket 一般用于網(wǎng)絡(luò)通信,AIDL用這種方式會過于繁瑣,不建議。
Binder連接池,通過BinderPool的方式將Binder的控制與Service本身解耦,同時只需要維護(hù)一份Service即可。這里用到了CountDownLatch,大概解釋下用意:線程在await后等待,直到CountDownLatch的計數(shù)為0,BinderPool里使用它的目的是為了保證Activity獲取BinderPool的時候Service已確定bind完成~
View 的事件體系
View的定義
可以把View理解成組合模式里的葉子結(jié)點和有枝節(jié)點的關(guān)系,本質(zhì)都是Composite,而這里本質(zhì)ViewGroup和View都是View,組合模式最大的好處就是遍歷的時候不用關(guān)注是怎樣的結(jié)點,因為抽象都是一樣的。
View的位置參數(shù)
- View的寬高和坐標(biāo)關(guān)系:width = right - left,height = top - bottom。
- View在平移過程中,top和left表示的是原始左上角的位置信息,其值不會改變,發(fā)生改變的是x、y、translationX、translationY這四個參數(shù),x是View左上角的坐標(biāo),translation是view移動后相對于父容器(這里其實就是剛才說的左上角)的偏移量,所以有x = left + translationX。y的原理相同。
MotionEvent典型事件:ACTION_DOWN, ACTION_MOVE, ACTION_UP。
TouchSlop:系統(tǒng)所能識別的被認(rèn)為是滑動的最小距離,我們可以用這個常量來判斷用戶的滑動是否達(dá)到閾值,提升用戶體驗。獲取方法:ViewConfiguration.get(getContext()).getScaledTouchSlop()。
VelocityTracker加速度追蹤:使用很簡單,看書就好。經(jīng)過測試一般建議類似ViewPager這樣的控件,將時間間隔設(shè)置為1000(也就是1秒)時,加速度閾值設(shè)為1000-2000左右體驗較好,各位可自行測試。
View的滑動
- ScrollBy和ScrollTo,簡單歸納下: ScrollBy的0點在一般情況下均為可見的top那條線,有一種特殊情況就是(書中未提及)當(dāng)某個ViewGroup在內(nèi)部的layout的時候設(shè)置margin為負(fù)值的View時,0點會在可見top上方的高度(或?qū)挾龋閙argin的地方,這個鬼東西實在是太繞口,具體的參考blog,參考headerView的設(shè)置。豎向滑動時,上滑ScrollY不斷增加(所以應(yīng)該傳正值),下滑時ScrollY不斷減少(所以應(yīng)該傳負(fù)值);同理,橫向滑動時,左滑ScrollX不斷增加,右滑不斷減少。 ScrollTo可以理解為把View滑動到ScrollX或ScrollY為指定值的位置
- 動畫:注意View動畫的View移動只是位置移動,其本身還是在原來位置,會導(dǎo)致一些bug。
- 通過LayoutParams
Scoller
- TouchEvent的ACTION_UP事件中,用戶滑動速度很快,但是滑動距離又不足以“翻頁”的時候,通過scroller來幫助用戶scrollBy掉滑動一頁還需要的dx或dy。
- 當(dāng)用戶滑動到最上端或最下端時,我們?nèi)匀辉试S用戶繼續(xù)滑動,但是一旦松手,就把頁面彈回到最上端和最下端的位置,用IOS的用戶都知道IOS幾乎所有頁面都有這個彈性效果,用戶體驗非常好,其實我們用scroller也能輕松實現(xiàn)。
- 第三種場景和第一種類似,大家依然可以參考上面發(fā)的那篇仿網(wǎng)易的blog,現(xiàn)在不是翻頁,當(dāng)用戶滑動的加速度很大的時候,我們認(rèn)為用戶需要滑動的距離肯定是不只他手從放下到松開的那段距離的,所以這種情況下我們需要通過scroller幫助用戶去多滑一段,這個距離具體設(shè)置一般需要交互給出,設(shè)置的是滑加速度的十分之一,感覺還是太少,各位可以自行設(shè)置。
View事件的分發(fā)機(jī)制
-
三大方法關(guān)系的偽代碼
public boolean dispatchTouchEvent(MotionEvent ev) { boolean consume = false; if(onInterceptTouchEvent(ev)) { consume = onTouchEvent(ev); } else { consume = child.dispatchTouchEvent(ev); } return consume; }
關(guān)系很清楚了,如果當(dāng)前View攔截事件,就交給自己的onTouchEvent去處理,否則就丟給子View繼續(xù)走相同的流程。
- onTouchListener優(yōu)先級高于onTouchEvent。
- 事件傳遞順序:Activity -> Window -> View,如果View都不處理,最終將由Activity的onTouchEvent處理。
- 一些結(jié)論:攔截的一定是事件序列;不消耗ACTION_DOWN,則事件序列都會由其父元素處理;只消耗ACTION_DOWN事件,該事件會消失,消失的事件最終會交給Activity來處理;requestDisallowInterceptTouchEvent方法可以在子元素中干預(yù)父元素的事件分發(fā)過程,除了ACTION_DOWN;
事件分發(fā)源碼解析
Window的實現(xiàn)類為PhoneWindow。
獲取Activity的contentView的方法
((ViewGroup)getWindow().getDecorView().findViewById(android.R.id.content)).getChildAt(0);View的滑動沖突處理,普通需求基本直接復(fù)用代碼就能搞定。但是如果想要深刻理解,只有自己多寫多測多讀代碼,才能很好的掌握,對于自定義View來說,掌握這個專題將對你的功力有大幅的提升。
View 的工作原理
ViewRoot和DecorView
- ViewRoot對應(yīng)ViewRootImpl類,它是連接WindowManager和DecorView的紐帶,View的三大流程均通過ViewRoot來完成。
- ActivityThread中,Activity創(chuàng)建完成后,會將DecorView添加到Window中,同時創(chuàng)建ViewRootImpl對象,并建立兩者的關(guān)聯(lián)。
- View的繪制流程從ViewRoot的performTraversals方法開始,經(jīng)過measure、layout和draw三大流程。
MeasureSpec
- 復(fù)習(xí)下位運算…… a) <<左移,>>右移,所以源碼中EXACTLY = 1 << MODE_SHIFT就相當(dāng)于010000……..0000(30個0),其他可類推; b) &運算 0011 & 1100 = 0000,按位與;|或運算 0011 | 1100 = 1111,按位或; c)~取反運算,~0000 = 1111,所以(size & ~MODE_MASK) | (mode & MODE_MASK) 就好理解了;
- 三類specMode:UNSPECIFIED,基本無視;EXACTLY,精確大小或match_parent;AT_MOST,warp_content;
- 子View和父容器的MeasureSpec關(guān)系歸納:
a. 子View為精確寬高,無論父容器的MeasureSpec,子View的MeasureSpec都為精確值且遵循LayoutParams中的值。
b. 子View為match_parent時,如果父容器是精確模式,則子View也為精確模式且為父容器的剩余空間大小;如果父容器是wrap_content,則子View也是wrap_content且不會超過父容器的剩余空間。
c. 子View為wrap_content時,無論父View是精確還是wrap_content,子View的模式總是wrap_content,且不會超過父容器的剩余空間。
View的工作流程:
onMeasure, onLayout, onDraw
- getSuggestedMinimumWidth的邏輯:View如果沒有背景,那么返回android:minWidth這個屬性指定的值,這個值可以為0;如果設(shè)置了背景,則返回背景的最小寬度和minWidth中的較大值。
- 一個比較好的習(xí)慣是在onLayout方法中去獲取View的測量寬高或最終寬高。
- 如何在Activity初始化時獲取View的寬高:
a. Activity或者View的onWindowFocusChanged方法(注意該方法會在Activity Pause和resume時被多次調(diào)用)。
b. view.post(new Runnable( {@Overiddepublic void run(){})});在run方法中獲取。
c. ViewTreeObserver中的onGlobalLayoutListener中。
d. view.measure手動獲取: match_parent:無法測量; 精確值:int wMeasureSpec = MeasureSpec.makeMeasureSpec(exactlyValue, MeasureSpec.EXACTLY); wrap_content:int wMeasureSpec = MeasureSpec.makeMeasureSpec((1 << 30) - 1, MeasureSpec.AT_MOST); PS一下,還不懂(1<<30) - 1的,再多一句嘴,其實就是1111….11111(30個) - layout中可能會使view的大小大于測量的大小
- draw的過程:繪制背景(background.draw(canvas)),繪制自己(onDraw()),繪制chidren(dispatchDraw),繪制裝飾(onDrawScrollBars)
- setWillNotDraw方法用于在一個View不需要繪制時的優(yōu)化(設(shè)置為true時)。
自定義View:
- 直接繼承View或ViewGroup的需要自己處理wrap_content。
- View要在onDraw方法中要處理padding,而ViewGroup要在onMeasure和onLayout中處理padding和margin。
- View中的post方法可以取代handler。
- 在View的onDetachedFromWindow中停止動畫,線程或回收其他資源。
- 滑動沖突處理。
理解RemoteViews
Notification
Notification的自定義View只能使用setTextViewText, setImageViewResource, setOnClickPendingIntent等固定方法來設(shè)置View,不能像操作普通View的方式來操作。
AppWidgetProvider
- 本質(zhì)是一個廣播,配置步驟:定義界面xml,定義配置信息xml,定義實現(xiàn)類(繼承AppWidgetProvider),AndroidManifest中聲明。
- 重要回調(diào):onEnable,第一次被添加時調(diào)用,只有一次;onUpdate,添加或更新時回調(diào);onDelete,每次刪除時回調(diào);onDisable,最后一次刪除時回調(diào);onReceive,接收廣播的action。
PendingIntent
- 典型的使用場景就是和RemoteViews的點擊事件配合使用;
- 支持三種待定Intent:Activity,Service和Broadcast
- PendingIntent相同的定義:內(nèi)部的Intent和requestCode都相同。Intent相同的定義:兩個Intent的componentName和intent-filter相同(不包括extras)
- flag定義:FLAG_NO_CREATE,基本不使用;FLAG_ONE_SHOT,以第一個為準(zhǔn),后續(xù)的會全部和第一條保持一致,任意一條被觸發(fā),其他的都cancel;FLAG_CANCEL_CURRENT,前面的相同的PendingIntent都會被cancel,只有最新的可用;FLAG_UDPATE_CURRENT,前面的PendingIntent都會被更新(它們Intent中的extras都會被更新)
RemoteViews的內(nèi)部機(jī)制
- 支持的View類型:Layout:FrameLayout, LinearLayout, RelativeLayout, GridLayout; View: AnalogClock, Button, Chronometer, ImageButton, ImageView, ProgressBar, TextView, ViewFlipper, ListView, GridView, StackView, AdapterViewFlipper, ViewStub。
- 簡單歸納,客戶端的remoteViews通過action對象,由binder機(jī)制來更新服務(wù)端的remoteViews,所以RemoteViews本身也實現(xiàn)了Parcelable接口(參考圖Page233)
- RemoteViews中真正操作View的方法apply和reapply,前者會加載布局并更新界面,后者則只更新界面。
跨進(jìn)程的RemoteViews傳遞
模擬通知欄的實現(xiàn)。注意不同應(yīng)用間RemoteViews的id不同,需要約定名稱然后重新通過Resource.getIdentifier來獲取。
Android 的 Drawable
Android 動畫深入分析
理解 Window 和 WindowManager
一些基礎(chǔ)知識:
- Window的實現(xiàn)類是PhoneWindow。
- Window的具體實現(xiàn)位于WindowManagerService中,WindowManager和WindowManagerService的交互是一個IPC過程。
- Window實際是View的直接管理者。
常用的WindowManager.LayoutParams的Flag和Type
- FLAG: FLAG_NOT_FOCUSABLE,當(dāng)前Window不獲取焦點,也不接收各種輸入事件,會同時啟用FLAG_NOT_TOUCH_MODAL,事件會傳遞給下層具有焦點的Window。 FLAG_NOT_TOUCH_MODAL,當(dāng)前Window區(qū)域外的單擊事件傳遞給底層,區(qū)域內(nèi)的單擊事件自己處理,一般都需要開啟。 FLAG_SHOW_WHEN_LOCKED,可以讓W(xué)indow顯示在鎖屏界面上。
- Type: 應(yīng)用Window,一般對應(yīng)一個Activity。層級范圍1~99。 子Window,不能單獨存在,需要特定的父Window,比如一般的Dialog。層級范圍1000~1999。 系統(tǒng)Window,需要權(quán)限聲明,比如Toast。層級范圍2000~2999。一般可以選用WindowManager.LayoutParams.TYPE_SYSTEM_ERROR,同時聲明權(quán)限。
- WindowManager提供的功能:addView,updateViewLayout,removeView
Window的內(nèi)部機(jī)制
Window并不實際存在,以View的形式存在。每個Window對應(yīng)著一個View和ViewRootImpl,Window和View通過ViewRootImpl建立聯(lián)系。所以在實際使用中其實我們并不能訪問到真正的Window,而只能通過WindowManager。
-
幾個重要的window類的關(guān)系 這里寫圖片描述
Window的添加過程
a. WindowManagerGlobal中的addView:
b. 檢查參數(shù)是否合法;
c. 如果子Window還需要調(diào)節(jié)布局參數(shù);
d. 創(chuàng)建ViewRootImpl并將View添加到列表中;
e. 通過ViewRootImpl的setView來更新界面并完成Window的添加過程:
requestLayout中的scheduleTraversals是View繪制的入口,最終通過WindowSession來完成Window的添加過程,注意其實這里是個IPC過程,最終會通過WindowManagerService的addWindow方法來實現(xiàn)Window的添加。Window的刪除過程
a. WinodwManagerGlobal中的removeView;
b. findViewLocked來查找待刪除待View的索引,再調(diào)用removeViewLocked來做進(jìn)一步刪除;
c. removeViewLocked通過ViewRootImpl的die方法來完成刪除操作,包括同步和異步兩種方式,同步方式可能會導(dǎo)致意外的錯誤,不推薦,一般使用異步的方式,其實就是通過handler發(fā)送了一個刪除請求,將View添加到mDyingViews中;
d. die方法本質(zhì)調(diào)用了doDie方法,真正刪除View的邏輯在該方法的dispatchDetachedFromWindow方法中,主要做了四件事:垃圾回收,通過Session的remove方法刪除Window,調(diào)用View的dispatchDetachedFromWindow方法同時會回調(diào)View的onDetachedFromWindow以及onDetachedFromWindowInternal,調(diào)用WindowManagerGlobal的doRemoveView刷新數(shù)據(jù)。Window的更新過程
a. WindowManagerGlobal的updateViewLayout;
b. 更新View的LayoutParams;
c. 更新ViewImple的LayoutParams,實現(xiàn)對View的重新測量,布局,重繪;
d. 通過WindowSession更新Window的視圖,WindowManagerService.relayoutWindow()。
Window的創(chuàng)建過程
- Activity
a. Activity的attach方法中,系統(tǒng)會創(chuàng)建Activity所屬的Window并為其設(shè)置回調(diào);
b. Window對象的創(chuàng)建通過PolicyManager的makeNewWindow方法;
c. Window的具體實現(xiàn)是PhoneWindow類;
d. Window創(chuàng)建好之后,通過PhoneWindow的setContentView將Activity與Window進(jìn)行關(guān)聯(lián),這個方法大致步驟:
d.1. 如果沒有DecorView就創(chuàng)建,id是android.R.id.content;
d.2. 將Activity設(shè)置的ContentView設(shè)置到DecorView的mContentParent中;
d.3. 回調(diào)Activity的onContentChanged方法通知Activity視圖已經(jīng)發(fā)生改變;
d.4. Activity onResume的時候會調(diào)用Activity的makeVisible方法真正完成DecorView的添加和顯示。 - Dialog
a. 通過PolicyManager的makeNewWindow方法創(chuàng)建Window;
b. 初始化DecorView,和Activity類似;
c. Dialog的show方法中,通過WindowManager將DecorView添加到Window中;
d. Dialog關(guān)閉時,會通過WindowManager來移除DecorView,方法為removeViewImmediate(mDecor);
e. 想要創(chuàng)建一個使用application context的Dialog可按照本章2-2的方法設(shè)置,dialog.getWindow.setType(WindowManager.LayoutParams.TYPE_SYSTEM_ERROR),記得在manifest中設(shè)置權(quán)限。 - Toast
a. Toast內(nèi)部有兩類IPC:Toast訪問NotificationManagerService;NotificationManagerService(下文簡稱NMS)訪問Toast的TN接口;
b. Toast屬于系統(tǒng)Window,內(nèi)部視圖mNextView一種為系統(tǒng)默認(rèn)樣式,另一種通過setView方法來指定一個自定義View。
c. TN是一個Binder類,NMS處理Toast的顯示隱藏請求時會跨進(jìn)程回調(diào)TN中的方法,所以TN運行在Binder線程池中,所以需要handler切換到當(dāng)前發(fā)送Toast請求的線程中,也就是說沒有Looper的線程是無法彈出Toast的。
d. Toast的show方法調(diào)用了NMS的enqueueToast方法,該方法先將Toast請求封裝成ToastRecord并丟入mToastQueue隊列中(非系統(tǒng)應(yīng)用最多塞50個)。
e. NMS通過showNextToastLocked方法來顯示當(dāng)前View,Toast顯示由ToastRecord的callback方法中的show方法完成,callback其實就是TN對象的遠(yuǎn)程Binder,所以最終調(diào)用的是TN中的方法,并運行在發(fā)起Toast請求應(yīng)用的Binder線程池中。
f. 顯示以后,NMS通過scheduleTimeoutLocked方法發(fā)送延時消息,延時后NMS通過cancelToastLocked方法來隱藏Toast并從隊列中移除,隱藏依然通過ToastRecord的callback中的hide方法實現(xiàn)。
g. callback回調(diào)TN的show和hide方法后,會通過handler發(fā)送兩個Runnable,里面的handleShow和handleHide方法是真正完成顯示和隱藏Toast的地方。handleShow方法中將Toast的視圖添加到Window中,handleHide方法將Toast視圖從Window中移除。
四大組件的工作過程
四大組件概述:
- Activity的主要作用是展示一個界面并和用戶交互,它扮演的是一種前臺界面的角色。
- Service是一種計算型組件,用于在后臺執(zhí)行一系列計算任務(wù),但因為其本身還是運行在主線程中的,因此耗時的后臺計算仍然需要在單獨的線程中去完成。
- BroadcastReceiver是一種消息型組件,用于在不同的組件乃至不同的應(yīng)用之間傳遞消息。廣播注冊有兩種方式,動態(tài)注冊通過Context.registerReceiver()來實現(xiàn),必須要應(yīng)用啟動才能注冊;靜態(tài)注冊則在AndroidManifest文件中進(jìn)行,應(yīng)用安裝時會被系統(tǒng)解析,不需要啟動應(yīng)用就可接收廣播。
- ContentProvider是一種共享型組件,用于向其他組件乃至其他應(yīng)用共享數(shù)據(jù)。
Android 的消息機(jī)制
三大件
Hanlder,MessageQueue,Looper。
- MessageQueue內(nèi)部的數(shù)據(jù)結(jié)構(gòu)并非隊列,而是單鏈表,它只是用來存儲數(shù)據(jù)。
- Looper是真正的數(shù)據(jù)處理者,線程默認(rèn)沒有Looper,使用Handler必須為線程創(chuàng)建Looper,UI線程也就是ActivityThread創(chuàng)建時會初始化Looper,所以主線程中默認(rèn)可以直接使用Handler。
UI線程檢查當(dāng)前線程的操作在ViewRootImpl的checkThread方法中,我們常見的不能在子線程中訪問view的異常就是在這里拋出的。
不允許子線程訪問主線程的原因是UI控件不是線程安全的,而加鎖又會導(dǎo)致UI的操作過于復(fù)雜。 - Handler的工作過程,圖(page374),單概括一下就是:假設(shè)創(chuàng)建Handler的線程是A,耗時操作的線程是B,B線程中拿到handler實例發(fā)送消息或者post一個Runnable,實際是調(diào)用了MessageQueue的enqueueMessage方法,進(jìn)入了消息隊列,線程A中的Looper發(fā)現(xiàn)了這個消息,就會處理這個消息,也就是消息中的Runnable或者h(yuǎn)andler的handleMessage方法會被調(diào)用,這樣handler中的業(yè)務(wù)邏輯就被切換到線程A中去了。
- ThreadLocal看上去只new了一份,但在每個不同的線程中卻可以擁有不同數(shù)據(jù)副本的神奇類。其本質(zhì)是ThreadLocal中的Values類維護(hù)了一個Object[],而每個Thread類中有一個ThreadLocal.Values成員,當(dāng)調(diào)用ThreadLocal的set方法時,其實是根據(jù)一定規(guī)則把這個線程中對應(yīng)的ThreadLocal值塞進(jìn)了Values的Object[]數(shù)組中的某個index里。這個index總是為ThreadLocal的reference字段所標(biāo)識的對象的下一個位置。
三大件原理
- MessageQueue的工作原理:主要方法為enqueueMessage和next。
a. enqueueMessag主要就是一個單鏈表的插入操作,
b. next方法是一個無限循環(huán),如果消息隊列中沒有消息,next方法就阻塞,有新消息到來時,next方法會返回這條消息并將其從單鏈表中刪除。
2。 Looper的工作原理:
a. prepare方法,為當(dāng)前沒有Looper的線程創(chuàng)建Looper。
b. prepareMainLooper和getMainLooper方法用于創(chuàng)建和獲取ActivityThread的Looper。
c. quit和quitSafely方法,前者立即退出,后者只是設(shè)定一個標(biāo)記,當(dāng)消息隊列中的所有消息處理完畢后會才安全退出。子線程中創(chuàng)建的Looper建議不需要的時候都要手動終止。 d. loop方法,死循環(huán),阻塞獲取msg并丟給msg.target.dispatchMessage方法去處理,這里的target就是handler。 -
Handler的工作原理:
a. 無論sendMessage還是post最終都是調(diào)用的sendMessageAtTime方法。
b. 發(fā)送消息其實就是把一條消息通過MessageQueue的enqueueMessage方法加入消息隊列,Looper收到消息就會調(diào)用handler的dispatchMessage方法。它的處理過程參考書page388的流程圖,一看就懂~
c. 這里我補充一個東西,當(dāng)我們直接Handler h = new Handler()時,本質(zhì)調(diào)用的是Handler(Callback callback, Boolean async)構(gòu)造方法,這個方法里會調(diào)用Looper.myLooper()方法,這個方法其實就是返回的ThreadLocal里保存的當(dāng)前線程的Looper,這也就解釋了為什么我們在主線程中這樣new沒有問題,子線程中如果不先Looper.prepare會拋出異常的原因,前面多次說了,因為ActivityThread會在初始化的時候創(chuàng)建自己的Looper。
主線程的消息循環(huán):
這里寫圖片描述