第一章:Activity生命周期和啟動模式
- Activity關閉時會調用
onPause()
和onStop()
,如果下一個Activity是透明主題,則當前Activity不會調onStop()
。 - 啟動Activity的請求會由Instrumentation來處理,然后它通過Binder向AMS發請求,AMS內部維護著一個ActivityStatck并負責Activity的狀態同步,AMS通過ApplicationThread(Binder)回調通知ActivityThread再去同步Activity狀態從而完成主線程生命周期回調。
- 前一個Activity的
onPause()
先調用后才后啟動下一個Activity,因此不要在onPause()
里做太耗時的操作;后一個Activity啟動后,即onResume()
后前一個Activity再繼續調onSaveInstanceState()
和onStop()
。 -
onSaveInstanceState()
方法只有在系統異常終止時才會被調用,其在onStop()
之前,不確定與onPause()
的順序(Api11之前,在onPause之前;在Api11之后調整到了onPause之后onStop之前);onRestoreInstanceState()
在onStart()
之后,方法中可以直接獲取到Bundle值不用判空,而onCreate()
方法中的Bundle需要做判空。View層次的恢復過程為Activity->Window->DecorView
逐步向上委托,再由上到下通知子View來保存元素。 - 一般給Manifest清單中的configChanges配置
orientation|screenSize
可避免Activity重啟。 - Activity啟動模式:
? standard:標準模式,始終創建新實例,誰啟動Activity,被啟動的Activity就和誰在同一個任務棧;非Activity類型的Context不能啟動Activity,除非指定標記位為FLAG_ACTIVITY_NEW_TASK
。
? singleTop:棧頂復用模式,Activity如果已在棧頂則不會被新建且會調用onNewIntent()
方法,否則創建新實例與standard效果相同。以上兩個模式對于TaskAffinity屬性無影響,即使設置了不同的affinity,也不會啟動新的任務棧。
? singleTask:棧內復用模式,這個比較特殊,會先尋找是否已有所需任務棧存在,不存在則創建指定任務棧并放入,存在則將棧拉到前臺,沒有實例則創建Activity,已有則調用onNewIntent()
方法,自帶newTask和clearTop標志行為清空頂部其他Activity(這個其實就是對比TaskAffinity是否相同,默認同一個應用下是包名,相同則不啟動新的任務棧);TaskAffinity
參數可指定了任務棧名稱,默認是包名,新指定不能和包名相同,一般與allowTaskReparenting
屬性配對使用,allowTaskReparenting使得在應用A中啟動應用B中的某個Activity,這個Activity可以與應用A處于同一任務棧,如果再去啟動應用B,則這個Activity又被轉移到應用B的Activity棧中。
? singleInstance:單實例模式,Activity獨自存在于一個任務棧,后續不會再創建新的(除非這個棧被銷毀了),用于多應用共享Activity。 - Flags:
FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
標志位可以使被啟動的Activity不出現在歷史棧列表中,可以用于承接跳轉Activity使用。 - IntentFilter匹配規則:
? IntentFilter中的過濾信息有action、category、data等參數。
? 一個Activity中可以有多組intent-filter,一個intent只要匹配其中一組即可。
? action:intent中的action參數能匹配到filter中任何一個即可,區分大小寫;且清單中必須要有一個action參數,否則一定失敗。
? category:intent中可以沒有category參數(系統默認會加上一個default參數),要是有的話必須每個category都能和filter中的某一個匹配才行;清單filter中必須手動加一個DEFAUL
。
? data:由scheme://host:port/path/pathPrefix/pathPattern
組成;URI中scheme和host是必須的,scheme默認為content或file;intent中必須含有data數據且需要和filter中某一條匹配。
? data和type必須一起設置intent.setDataAndType()
才有效,否則會相互覆蓋。 - PackageManager或Intent的
resolveActivity()
方法和PackageManager的queryIntentActivities()
方法可以返回匹配的intent,flag參數傳MATCH_DEFAULT_ONLY
代表只找Activity,這樣可以事先判空避免找不到而崩潰。 - Activity被回收時棧恢復情況:
? 場景:如果是手動強行殺死進程,任何生命周期都不會被回調(考慮下要不要保留狀態什么的);如果是Activity棧在后臺由于內存不足被回收,則棧順序會被保存,優先恢復棧頂Activity(其余Activity還沒有創建),點擊back返回時,其余Activity再創建并顯示;如果是前臺Activity棧由于崩潰退出,恢復時只恢復到上一個Activity。
? 問題:棧恢復中,只能創建并恢復棧頂Activity,這時前面的Activity還沒有創建,所以如果被恢復的棧頂Activity使用了前面Activity的靜態引用或者依賴前面Activity的數據則會出現問題。
? 解決:可以在onSaveInstanceState()
和onRestoreInstanceState()
方法中做手腳來恢復數據;如果簡單一些的話,可以自己在內存維護一個標志,在BaseActivity中判斷,如果標志被置位則認為是進程重啟了,這時候重啟到LauncherActivity并殺死自己,需要將intent設置intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK)
。
第二章:IPC機制
- 線程是CPU調度的最小單元,線程是一種有限的系統資源;進程是一個執行單元,一般在PC或移動設備中指一個程序或應用;一個進程中可以包含多個線程,線程間數據共享,進程間數據獨享。
- 通過給組件設置
android:process=":remote"
可開啟多進程模式;":"代表在包名后面拼接進程名,也可直接指定進程全名。 - 進程名以":"開頭的屬于當前應用的私有進程,其他組件不能和它跑在一起,不以":"開頭的為全局進程,通過shareUID方式可以將不同組件跑在同一進程,但是簽名需要一致。
- 多進程模式中,不同進程組件會擁有獨立的虛擬機、Application以及內存空間,數據不會共享,因此會出現單例失效、同步機制失效、SP讀寫不安全(多進程文件讀寫存在并發)、多個Application等情況。
- 實現Serializable接口(Java提供)并定義serialVersionUID(用于判斷反序列化時對象是否一致用的)可實現序列化,使用簡單但開銷大,需要大量IO操作,適用于持久化到設備或網絡;Android特有實現Parcelable接口也可實現序列化,使用稍麻煩,效率高,適用于內存序列化,傳值等;注意靜態成員不參數序列化,transient關鍵字標記的成員變量不參與序列化。
- Binder是Android中的一個類,實現了IBinder接口,它是Android中的一種跨進程通信方式,是鏈接各個Manager和ManagerService的橋梁。
- Binder可以理解為一種進程間通信規范,因為實現了IBinder接口,我們平時用的Stub繼承自Binder,且實現了業務接口,提供兩個接口的相互轉換方法
asBinder()
和asInterface()
,供客戶端使用;創建.aidl文件只是為了方便系統自動生成規范的.java代碼而已。 - 客戶端向遠程發起請求后,客戶端線程將掛起,直到遠程回復,因此對客戶端來說遠程調用可能是耗時的,要在子線程中發起;對于服務端來說,Binder方法是運行在Binder線程池中的,因此同步調用即可。
- Binder提供了
linkToDeath()
和unlinkToDeath()
方法來給設置死亡代理,當遠程進程異常時IBinder.DeathRecipient類的binderDied()
方法會被回調,客戶端可以收到回調并重連遠程Service;Binder的方法isBinderAlive()
也可以判斷Binder是否死亡。 - Android中的幾種IPC方式:
①Intent:可以使用Bundle傳輸數據,它也實現了Parcelable接口,可傳輸基本數據類型和可序列化后的Object。
②文件共享:Linux沒有對文件并發讀寫做限制,因此要自己控制并發問題;SP文件也一樣,系統會在內存緩存一份SP文件,在多進程模式下,也是線程不安全的。
③Messenger:本質是對AIDL的封裝,底層也是通過AIDL實現;可通過Handler或IBinder構建Messenger,服務端通過Handler構建,返回msgner.getBinder()
,客戶端通過IBinder構建,使用msgner.send(msg)
發數據,可帶replyTo
字段,傳入自己用Handler構建的Messenger,收服務端的回調;消息順序執,一次處理一個請求,不用處理并發問題,且只能攜帶msg支持的參數,局限性還是比較大的。
④AIDL:
? IBinder是統的進程間通信規范,我們定義的.aidl文件實際上就是為了讓系統生成.java文件,內含Stub類,實現了IBinder和業務接口,主要用的就是它作為Binder返回給客戶端,服務端實現好定義的業務接口并將它在onBind()
方法中返回即可。
? AIDL支持的數據類型有6種:基本數據類型、String和CharSequence、List、Map、Parcelable和AIDL接口本身。自定義對象也要定義.aidl文件聲明為parcelable類型,自定義的Parcelable對象就算在同一個包里也要現顯式引用import;接口方法中除了基本數據類型,其它類型必須標上in、out或inout方向(服務端給客戶端推送,服務端相當于客戶端,要用in)。
? AIDL接口中只支持方法,不支持靜態常量,這點區別于普通接口。
? 客戶端和服務端包含一套相同的.aidl包結構,序列化和反序列化時會用到其中相關的類,一部分邏輯在客戶端執行,一部分邏輯在服務端執行。
? Binder中的方法調用都是在Binder線程池中的,是異步的,所以客戶端收到服務端回調,如果需要切到主線程的話要使用Handler。
? 對象本身是不能跨進程傳輸的,涉及序列化與反序列化,因此服務端Binder中傳遞到客戶端的listener會反序列化出新的對象,注冊與解注冊要使用RemoteCallbackList,內含map通過binder作為key保存每個listener實例,客戶端終止后可自動解除注冊且內部實現了線程同步;RemoteCallbackList并不是List,遍歷它要使用beginBroadcast()
和finishBroadcast()
方法,且必須配對使用。
? Binder意外死亡時需要重新連接服務,一般可以給Binder設置死亡代理DeathRecipient監聽binderDied()
回調(線程池中調用的);也可以在onServiceDisconnected()
中重連(UI線程中調用的)。
? AIDL遠程調用一般要做身份驗證,可以在onBind()
中返回null,或者在onTracsact()
中返回false都能拒絕服務,建議自定義權限和包名雙重驗證。
⑤ContentProvider:
? 底層也是AIDL實現。六個方法:onCreate、query、update、insert、delete和getType,只有onCreate在主線程其他都在子線程執行;注冊Provider一般定義authorities(uri)、permission(權限)、process(可運行在獨立進程)。
? 除了query,update、insert和delete都會引起數據源改變,因此數據更新后可以context.getContentResolver().notifyChange(uri,null)
來通知更新,客戶端可以注冊registerContentObserver()
監聽數據更新(如自動讀取短信驗證碼功能)。
? provider的query、update、insert和delete四大方法都是存在多線程并發訪問問題的,要自己做好線程同步(Sqlite單連接的話可讀不可寫,算做了線程同步了)。
⑥Socket:
? 分為流套接字和數據報套接字,對應TCP和UDP協議,TCP是面向連接的(需要三次握手),提供穩定的雙向通信,而UDP是無連接的,提供不穩定的單向通信(效率高但不保證數據正確傳輸)。
? socket不僅可以進程間通信,只要設備之間的IP地址已知,網絡通訊也可以。 - 一般一個Service對應一個Binder(業務接口),如果業務接口太多,創建太多Service就不合適了,所以一般使用Binder連接池。服務端就一個Service,創建好實現各類業務接口的實例后,統一一個queryBinder接口返回給客戶端,客戶端自己去取想要的業務接口;客戶端想要哪個自己去取(取出來的是IBinder類型,要自己去強轉一下再使用);建議Binder連接池全程單例,并加上超時重連機制。
- AIDL的通信流程:
? 由于.aidl代碼包時客戶端和服務端有相同的兩份,因此有些代碼是運行在客戶端,有些代碼是運行在服務端的,且兩邊可以相互充當客戶端或服務端,可以理解為發出去請求的就是客戶端,回應的就是服務端。
? Stub類是AIDL過程的關鍵,Stub類繼承自Binder,實現了IBinder接口和業務接口,符合遠程通信的規范,且Stub內部類Proxy在不同進程調用時充當Stub代理。
? Stub類在服務端的onBind()
方法里返回給客戶端(已實現了業務接口),在客戶端的onServiceConnected()
方法里獲取到遠程服務端的Stub(可轉為業務接口)。
? 使用Stub.AsInterface(binder)
方法時,是調用的客戶端的方法,Stub會判斷如果是同一個進程則返回本地Stub,否則返回Stub.Proxy代理類,封裝遠程傳過來的Stub(服務端的Stub),拿到統一的業務接口并使用。
? 調用業務接口進行遠程方法調用時,方法中會先從本地生成輸入輸出的pracel對象,然后會調到mRemote.transact()
,系統底層會回調遠程Stub里的onTransact()方法,遠程業務接口實現類將輸入輸入date對象寫回parcel。
? 因此注意Stub和Proxy的調用過程,transact()
前的都是在客戶端執行,onTransact()
后的都是在服務端執行,兩邊的遠程調用方法都是在Binder線程池中的,因此是耗時的,客戶端會掛起等待遠程回復,不能在客戶端直接更新UI線程。
第三章:View的事件體系
- 幾種常見的位置相關類:
? View的位置參數:主要由四個頂點top、left、right和bottom決定(相對父容器的位置);3.0以后,增加了x、y、translationX、translationY幾個參數,getLeft()
是初始位置(永遠不會變),getTranslationX()
是滑動偏移量,getX()
是最終位置,前兩個相加(會變);getX()、getY()
是相對父容器的位置(相對位置),getRawX()、getRaxY()
是相對手機屏幕的位置(絕對位置)。
? MotionEvent:封裝了一些列事件:ACTION_DOWN、ACTION_MOVE、ACTION_UP、ACTION_CANCEL等。
? TouchSlop:ViewConfiguration.get(context).getScaledTouchSlop()
獲取,系統認為的最小滑動距離,是一個常量,與設備有關,一般為8dp,優化滑動敏感度使用。
? VelocityTracker:速度追蹤。通過VelocityTracker.obtain()
獲取、tacker.addMovement(event)
添加motionEvent、tarcker.computeCurrentVelocity(1000)
設置單位、tarcker.getXVelocity()
獲取速度;注意速度值可能為負,因為速度值 = (終點位置 - 起點位置) / 時間段
,也跟設置的單位時間有關,結果就是XX像素/XX毫秒
。
? GestureDetector:手勢檢測。new GestureDetector(this)
獲取、detector.onTouchEvent(event)
接管motionEvent、構造方法里傳的是個監聽接口,在那里面獲取回調,一般只有監聽雙擊等事件才用這個。
? Scoller:計算輔助類。我們只設置滑動距離和時間,它只幫我們封裝了計算每一段時間和要滑動的距離scoller.getCurrX()
,具體滑動要自己做,在View的computeScroll()
方法中使用scroller.computeScrollOffset()
判斷是否滑動結束,配合scrollTo()
和postInvalidate()
完成分段滑動。 - View的滑動:
? Scoller、ValueAnimator和Handler等都是只計算了滑動數值,是計算輔助類,而真正的滑動只能通過View自身的scrollTo()
或scrollBy()
實現。
?scrollTo()
是絕對滑動,scrollBy()
內部也是調用了scrollTo()
,是相對滑動;自定義View時srollTo()
之后要postInvalidate()
來重繪view,如果是在外部調用的話不用重繪。
注意:
scrollTo()
和scrollBy()
只能改變View內容的滑動,并不能改變View本身的位置;滑動數值等于View的邊緣-View內容的邊緣
,因此有正負值。理解記憶:想看更多內容,往下滑內容,為正;想看之前的內容,往上滑內容,為負。
- 改變布局參數layoutParams之后可以給View再次設置
view.setLayoutParams(params)
,也可以直接view.requestLayout()
(這個params必須是view.getLayoutParams()
獲取的,修改的是同一個引用)。 -
ViewHelper.getTranslationX()
可以獲取view的滑動偏移量來改變view的距離,其實ViewHelper是nineoldandroids里的View兼容庫,view本身有許多get和set方法可以直接用。 - View事件分發機制:
? 三種事件類型:dispatchTouchEvent()
分發,事件能發給該View一定會調用,內部會調后兩個方法、onInterceptTouchEvent()
攔截,攔截一次后不會再次判斷了,直接調本層的下一個方法、onTouchEvent()
處理,如果返回fasle,后面的同一個事件序列不會再發給它。
? 調用順序:先分發、再內部判斷攔截、攔截則消費、否則繼續向下分發。
? 幾種回調優先級順序:(Acitivity->PhoneWindow->DecorView->ContentView) -> dispatchTouchEvent() > onInterceptTouchEvent() > onTouchListener() > onTouchEvent() > onLongClickListener() > onClickListener()
。
事件分發的一些結論:
①一個事件序列只能由一個View來攔截且消耗,一錘子買賣,因為ViewGroup中會保存mMotionTarget為消耗事件的子View,后面的事件序列直接發給它 。
②一個ViewGroup只要決定攔截,onInterceptTouchEvent()
只會調一次,后面就不再次判斷了。
③子View如果不消耗ACTION_DOWN事件,即onTouchEvent()
返回false后,以后的事件序列就不再傳給它了,后面依次往上判斷調用父View的onTouchEvent()
了,都返回false就給Activity處理了。
④DOWN、MOVE、UP每一個事件都會從上往下傳,都可能被攔截,onTouchEvent()
返回true只是改變了傳遞深度而已,因此除了Down,每一層可以通過requestDisallowInterceptTouchEvent()
來改變父View的攔截限制;如果子View消耗了DOWN卻沒有消耗后續事件序列,這些事件仍會傳過來,最終這些消失的事件會傳到Activity那里。
⑤ViewGroup默認不攔截事件,即onInterceptTouchEvent()
返回false,View默認消耗事件,即onTouchEvent()
返回true,不過要看view是否是可點擊的(clickable()值),button等默認是true,textview等默認是false。
⑥enable屬性不影響onTouchEvent()
的默認返回值,只要view是可點擊的(clickable為true),它就默認消耗事件。 - 事件分發流程:
①MotionEvent從Activity發出,如果Window上的所有View都不處理,即getWindow().superDispatchTouchEvent()
返回了false,最后調用Activity自己的onTouchEvent()
。
②ViewGroup的onInterceptTouchEvent()
方法不是每次都調用,只有Down事件或非Down事件且有子View處理事件(就是說子View處理了Down后的Move或Up事件)會調用,也就是即使子View處理了事件,ViewGroup還是會判斷攔截Move、Up事件的,這也是我們處理滑動沖突的地方(攔截Move處理),回到上面,如果第一次onInterceptTouchEvent()
返回true的話,后面的Move和Up也就不是Down事件且mMotionTarget為空,則不會向子View分發事件序列了,直接調super.dispatchTouchEvent()
,里面會調ViewGroup自己的onTouchEvent()
。注意Move時子元素也是可以設置disallowInterceptTouchEvent
來禁止父元素就攔截Move和Up事件的。
③子View的onTouchListener會屏蔽掉onTouchEvent,如果返回了true,onTouchEvent()
就不會被調用了,onClick點擊事件也就不會被調用了,這樣是為了方便外界來處理特殊的觸摸事件。
④子元素是否可以消耗onTouchEvent()
決定于它是否可點擊,也就是clickable是否為true,與enable無關;默認longclickable都為false,clickable的話Button類的為true,TextView類的為false;但是任何View只要設置了onClickListener()
或onLongClickListener()
就會把它的clickable或longclickable顯示置為true,它就可以處理onTouchEvent了。 - 處理滑動沖突其實Down事件控制不了,會一直傳下去,要處理的就是有子View處理Down,且父View再攔截處理后續的Move和Up事件(一般處理move最多),這時可以根據業務需求來內部攔截或者外部攔截來處理滑動事件。
- 一般推薦外部攔截法,簡單易用,在ViewGroup中控制Down的時候不攔截,Move的時候選擇性攔截,Up的時候重置為不攔截,攔截了以后在自己的onTouchEvent中就可以處理事件了。
第四章:View的工作原理
- ViewRoot對應于ViewRootImpl,它是連接WindowManager和DecorView的紐帶(Window-WindowManager-ViewRootImpl-View),View的三大流程均是通過ViewRoot來完成的,即測量、布局和繪制;
measure()
決定了View的測量寬高,layout()
決定了View的最終寬高和四個頂點的位置,draw()
將View繪制到屏幕上;View的繪制流程是從ViewRoot的performTraversals()
方法開始,經過measure、layout和draw三大流程將一個View繪制出來。 -
performTraversals()
方法會依次調用performMeasure、performLayou和performDrow,分別完成View的三大繪制流程;measure()
方法中根據自身的params和父View的Spec計算出自己的寬高的Spec,然后回調onMeasure()
方法,其中后計算出自己的真實size去setMeasuredDimension()
設置自己的寬高,如果是ViewGroup還會去計算子View的Spec傳給子View - 三大流程:
①measure過程決定了View的寬高,measure以后就能通過getMeasuredWidth()
和getMeasureHeight()
獲取View的測量寬高了,基本就定于View的最終寬高。
②layout過程決定了View四個頂點的位置坐標,layout以后就能通過getTop()、getLeft()、getRight()和getBottom()
獲取View的位置了,就能得到getWidth()
和getHeitht()
的值了。
③draw過程決定了View最終的顯示,只有draw以后View才能呈現在屏幕上(主線程繪制)。 - MeasureSpec:
? 是一個32位的int值,高兩位表示SpecMode,低30位表示SpecSize,就是封裝了一個測量值的包裝類,需要用它來確定View的測量值。
? SpecMode有三種模式:EXACTLY、AT_MOST、UNSPECIFIED,分別對應具體值、wrap_content、第三個不常用,涉及minWidth和background因素。
? 子元素的最終大小由子View的layoutParam和父View的spec共同決定(還與margin和padding有關系),這個過程只是計算spec的基本操作,得到View的spec后回調View的onMeasure()
方法傳入spec,讓View自己再去計算一下寬高size去賦值。 - 子View的MeasureSpec的創建規則:
? 當子View是固定值的時候,不管父spec是什么,子spec都是EXCATLY,以子size為準。
? 當子View是martch_parent的時候,父spec如果是EXACTLY,子spec就是EXACTLY,父spec如果是AT_MOST,子sepc也是AT_MOST,都以父size為準。
? 當子View是wrap_content的時候,不管父spec是什么(除UNSPECIFIED外),子spec都是AT_MOST,以父size為準。
? 當父spec是UNSPECIFIED的時候,子View除固定值之外也是UNSPECIFIED,且子size為0。 - View的工作流程:
①measure:
?measure()
是final類型的,其中算出spec后會回調onMeasure()
,一般我們在onMeasure()
中獲取建議的寬高值,自己再計算并設置setMeasureDimesion()
來設置View的最終的測量值。
? 直接繼承自View一般區分USPECIFIED情況,默認size是minSize或backgroundMinSize的值,大多為0;另一種EXACTLY、和AT_MOST,都是使用子spec中的size值(默認是parentSize),因此wrap時要自己算寬高否則與match一樣。
? ViewGroup是抽象類,有個measureChildren()
方法,會調用child.measure()
,根據自己的sepc和child的layoutParams來得出子spec,最終參數先去計算子View寬高,然后各個ViewGroup子類定義onMeasure()
,用子View的size總之決定自己的size。
?onMeasure()
可能會被執行多次,只有最后一次基本會與最終寬高值相等,因此一個好的習慣是在onLayout()
中去獲取View最終的寬高值。
②layout:
? ViewGroup在layout()
確定位置后,會在onLayout()
中遍歷所有子元素并調用其
layout()
方法來布局子元素,里面又會回調子元素的onLayout()
方法;layout()
就是父View來調用確定你自己的位置,然后會回調你的onLayout()
方法告訴你你的位置參數,然后你在調子View的layout()
去設置子View的位置。
③draw:
? draw繪制的過程分四步:繪制背景background.draw(canvas) -> 繪制自己(onDraw()) -> 繪制children(dispatchDraw()) -> 繪制裝飾(donDrawScrollBars())
;
View有個默認的willNotDraw標志,ViewGroup默認開啟,View默認關閉,為true的話如果不需要繪制任何內容系統會對其進行一些優化,如果明確知道一個ViewGroup必須要通過onDraw來繪制內容時,要顯示關閉這個WILL_NOT_DRAW標志。
注意:
? 由于View的measure過程和Activty的生命周期不是同步的,因此在onCreate()、onStart()、onResume()基本均無法獲取到View的寬高。
? 四種方式獲取View的寬高:1.onWindowFocusChanged()(會多次調用) 2.view.post(runnable)(推薦) 3.ViewTreeObserver.addOnGlobalLayoutListener()(也行,多次調用) 4.view.measure()(基本只有子View具體寬高值才行)。
? getMeasuredWidth()和getWidth()基本是相等的,只是前者在measure后確定(測量值),后者在layout后確定(位置相減),兩個順序不一致,且前者可能會被調用多次,因此,最好使用后者來得到View的寬高值。
- 自定義View:
①分類:繼承自View重寫onDraw()
、繼承自ViewGroup重寫onLayout()
、繼承自現有View、繼承自現有ViewGroup。
②須知:繼承自View需要處理wrap_content、需要處理padding、不用在View中使用handler,直接post即可、onDetachedFromWindow()
時記得停止線程或動畫,否則可能內存泄露、處理好滑動沖突等。
③流程:
? 自定義View在onMeasure()
中記得getPadding()
處理下padding、根據業務處理下wrap_content的情況;onMeasure()
中父View計算好了一套默認的寬高specMode和specSize,子View根據業務需要重新計算寬高值并setMeasuredDimension()
賦值寬高。
? 自定義ViewGroup時在onMeasure()
中也是父View計算好了一套specMode和specSize,先調用measureChildren()
計算一下子View大小,完了再根據子View的getMeasuredWidth()
算出來最終的總值,再給自己setMeasuredDimension()
賦值寬高。
? 在onLayout()
中根據left、top、right和bottom(父View給自己的定位),以及子View的個數等信息,遍歷子View調用childView.layout(l,t,r,b)
來布局子View。 - 對于View這一塊要掌握View的三大流程(measure、layout、draw),一些常見方法回調(構造、onAttach、onVisibilityChanged、onDetach)等,以及嵌套中滑動沖突的處理。
第五章:理解RemoteViews
- RemoteViews表示一個View結構,它可以在其他進程中顯示,提供了一組基礎的操作用于跨進程更新界面,多用于通知欄或桌面小部件,一般在系統的SystemService進程。
-
new RemoteView(packageName,layoutResId)
可以創建remoteView,remoteView.setTextViewText(R.id.tv,"xxx")
用于設置View值,再給notification.contentView = remoteView
即可;用于桌面小部件時,本質其實就是一個廣播(BroadcastReceiver)。 - PendingIntent概述:
? 延遲intent,支持三種意圖:getActivity()、getService()、getBroadcast()。
? 兩個pendingIntent相同的條件是:內部的intent相同且requestCode也相同,intent相同指componentName和intent-filter相同,extras不參與匹配過程。
? requestCode參數基本填0,flag有四種類型:FLAG_ONE_SHOT(少用)、FLAG_NO_CREATE(基本不用)、FLAG_CANCEL_CURRENT(類似第一個 不常用)、FLAG_UPDATE_CURRENT(常用)。FLAG_ONE_SHOT和FLAG_CANCEL_CURRENT都是只能打開一個PI(前者打任一個其余打不開,后者只能打開最新的,前面的被cancel掉),FLAG_NO_CREATE直接返回null,基本不用,FLAG_UPDATE_CURRENT每個都能打開,只是結果都一樣。
? notification中id相同的話會相互覆蓋前一個,不會出現pendingIntent沖突現象,如果id不同,可以顯示多條通知,這時要注意pendingIntent如果相同的話,注意flag的設置問題(id影響顯示幾條,flag影響PI相等時的點擊反應)。 - RemoteViews對象可以序列化,通過Intent進行傳輸,傳輸到其他應用后資源文件id最好通過協商好的格式
getResources().getIdentifier(id,type,packageName)
來獲取。
第六章:Android的Drawable
- Drawable是一種可以在canvas上繪制的抽象概念,比自定義View成本低,可以做一些特殊背景和效果,作為View的背景時會被拉伸,寬高沒有什么實際用處。
- 9中常用Drawable分類:
①單層:
? BitmapDrawable:src、antialias、dither、filter、gravity、mipMap、tileMode等。
? NinePatchDrawable:src、dither等。
? ShapeDrawable:shape、corners、gradient、padding、size、solid、stroke等。有rectangle、oval、line和ring四種類型,畫虛線做背景時imageView需要開啟軟件加速才有效。
②組合:
? LayerDrawable:layer-list。使用item、shape層級顯示。
? StateListDrawable:selector。使用item、shape選擇顯示,默認的狀態放到最后,可以匹配任何狀態,類似switch語句。
? LevelListDrawable:level-list。使用item、shape多層切換顯示,maxLevel取值0~10000。
③不常用:
? TransitionDrawable:transition。過渡效果。
? InsetDrawable:inset。給內置drawable添加內邊距,layerdrawable或padding也能實現。
? ScaleDrawable:scale。級別越大,內部drawable越大(正比);比例越大,內部drawable越小(反比)。
? ClipDrawable:clip。級別0~10000,級別越大,裁剪的越大,剩的越少。
第七章:Android動畫深入分析
- Android動畫類型6種:幀動畫、補間動畫、布局動畫、屬性動畫、矢量動畫和切換動畫。
- 每種動畫的特點:
? 幀動畫:animation-list標簽;oneshot屬性;item添加動畫幀;作為View的背景,獲取后轉為AnimationDrawable,自己開啟和結束;注意不要用太多大圖以免OOM。
? 補間動畫:平移、縮放、旋轉、透明度、set五種類型;interpolator、shareInterpolator、duration、repeatCount、repeatMode、fillAfter等屬性,repeatCount默認是0,-1表示無線循環,repeatMode取值restart和reverse;AnimationUtils.loadAnimation()
獲取動畫,通過view來開啟和關閉,注意detachFromWindow是要關閉動畫否則可能內存泄露。
? 布局動畫:layoutAnimation標簽;delay(0~1)、animationOrder(normal/reverse/radom)、animation等屬性;可以在布局里給View設置layoutAnimation,也可以在代碼里創建LayoutAnimationController來給View設置setLayoutAnimation()
,默認布局中設置layoutAnimationChanged=true
可實現默認動畫。
? 屬性動畫: 含ValueAnimator、ObjectAnimator和AnimatorSet;獨立的動畫類,自己創建自己開啟,可將View設為target(系統封好的或者自己定義的),類似于Scoller的特點;API11之后引入的特性,可以使用nineoldandroid庫做版本兼容(3.0以下使用補間動畫實現)。
? 矢量動畫:SVG動畫,可適配不同分辨率,實現完美平滑的過渡效果,5.0以上支持,實現方式比較復雜,建議閱讀相關博客。
? 切換動畫:overridePendingTransition(enter,exim)
,放在startActivity()
和finish()
之后才能生效,第一個參數是下個Activity進入的動畫,第二個參數是本Activity退出的動畫;Fragment切換動畫用到了FragmentTransaction的setCustomAnimations()
方法。 - 屬性動畫詳解:
? 類似Scroller,其實就是一種計算輔助類,差值器和估值算法,原始動畫為ValueAnimator,封裝了計算過程,ObjectAnimator多了target屬性包裝一個View,封裝了一些特定屬性的get()、set()效果,也可自己實現動畫效果。
? 一般在代碼中動態創建,xml中創建的話,設置valueType、propertyName等屬性,AnimatorInflater.loadAnimator()
獲取并設置setTarget()
作用對象。
? 差值器(Interpolator)和估值器(Evaluator)一般配合使用,都是計算輔助接口,一個計算差值比例,一個計算最終的估值結果,可作用于任何地方作為計算器類,可自定義計算變化算法和估值類型和結果值。
? ValueAnimator的addUpdateListener()
可設置更新監聽,animator.getAnimatedFraction()
和animator.getAnimatedValue()
可以獲取到差值和估值結果,是根據前面自己設置的計算類得到的。
? ObjectAnimator作用時View對應屬性要有get()、set()方法,否則會crash,還要定義對應效果,否則沒效果,可以自己包裝View或者直接用ValueAnimator自己來根據計算值設置View。 - 使用動畫注意事項:幀動畫注意OOM的情況;屬性動畫如果是循環的,退出時最好cancel()一下,且注意版本兼容性;補間動畫不用時clearAnimation()一下,否則有些view方法會無效果;使用動畫的過程中如果開啟硬件加速可以提高流暢度。
第八章:理解Window和WindowManager
- Window是一個抽象類,它的具體實現是PhoneWindow,它是一個存在但看不見的東西,外界訪問Window要通過WindowManager,Window是View的直接管理者,View要通過WindowManager附屬到Window上才能實現通信和顯示,Window的具體實現在WindowManagerService中,WindowManger與WindowManagerService的交互是IPC過程。
- ViewManager是個接口,有addView()、updateView()和removeView()三個方法,WindowManager繼承自ViewManager,含很多標志位,WindowManagerImpl才是真正的實現類,內部所有操作委托給WindowManagerGlobal實現。
- 有View和LayoutParams,View就能附加到Window上顯示出來,flag類型如:FLAG_NOT_FOCUSABLE(不處理焦點完全給下級)、FLAG_NOT_TOUCH_MODAL(處理自己范圍內的事件 需要開啟)、FLAG_SHOW_WHEN_LOCKED(鎖屏也顯示)。
- Window分為三種:應用Window(對應Activity)、子Window(Dialog/PopupWindow等 必須有父Window)和系統Window(Toast等 需要權限 不需要依附token)。
- Window有層級,應用Window范圍1-99,子Window范圍1000-1999,系統Window范圍2000-2999,層級越高顯示越在上面。
- 每個Window對應著一個View和ViewRootImpl,Window和View通過ViewRootImpl建立聯系,實際使用中無法直接訪問Window,可通過WindowManger訪問;WindowManagerGlobal是真正邏輯實現,內部含mViews、mRoots、mParams和mDyingViews幾個列表,存儲管理的Views,通過ViewRootImpl更新界面完成Window的添加過程,ViewRootImpl中會
scheduleTraversals()
完成View繪制,并通過WindowManagerGlobal獲取WindowSession進行addToDisplay()
,內部是與WindowManagerService進行IPC交互了;removeView也是調到ViewRootImpl中,進行View的dispatchDetachedFromWindow()
以及通過WindowSession來更新IPC等;updateView過程也類似,涉及View的重繪。 - Window的內部機制(涉及的類):
ViewManager/WindowManager -> WindowMangerImpl -> WindowManagerGlobal -> View和ViewImpl -> WindowSession -> WindowManagerService
,WindowManagerGlobal里大多是管理ViewRootImpl的邏輯,添加View到Window中的邏輯是在ViewImpl中執行的,ViewImpl中通過WindowSession與WMS進行通信,最后使用WMS來進行將View添加到Window中,屬于遠程IPC調用,真正的操作邏輯在WMS里。 - Activity中Window的創建過程:
IPolicy -> PolicyManager -> Wiondow(PhoneWindow) -> 內部創建DecorView(mContentParent) -> 等到Activity的onResume() -> makeVisible() -> ViewManager的addView()將mDecor添加到Window -> mDecor.setVisibility()
,Activity最終顯示。 - 普通的Dialog必須使用Activity的Context才能顯示出來,因為Activity的context才有windowToken;Toast可定時取消,用到了Handler,因此Toast無法在沒有Looper的線程中使用。
- 有了WindowManager,我們就可以隨意創建View和設置LayoutParams來讓這個Window顯示出來,可以設置級別和flag,用于創建應用懸浮窗等。
第九章:四大組件的工作過程
- Android四大組件中除了BroadcastReceiver外,其余三個組件都要在Menifest中注冊,BroadcastReceiver可以在Menifest中靜態注冊也可以在代碼中動態注冊;Activity、Service和BroadcastReceiver需要借助Intent來啟動。
- Activity主要作用是展示一個界面并與用戶交互,扮演一個前臺界面的角色;Service是一種計算型組件用于在后臺執行一系列計算任務;BroadcastReceiver是一種消息型組件,用于在不同組件乃至不同應用中傳遞消息;ContentProvider內部的insert、delete、update和query方法是在Binder線程池中調用的,是異步操作,所以需要處理好線程同步問題。
- BroadcastReceiver可以靜態注冊和動態注冊,靜態注冊指在Menifest中注冊,此種形式應用不需啟動便可收到廣播(5.0以后不可),適用于長期接收事件,動態注冊指在代碼中注冊,一般用的多,因為頁面關閉后就取消注冊了,廣播用于實現低耦合的觀察者模式。
- Service雖然指后臺運行,但是還是在主線程中工作的,不能直接做耗時操作,可在IntentService中可以做耗時操作,且任務完成可以自己stop自己,后臺計算不需要交互使用startService開啟,需要業務接口交互使用bindService開啟。
- ActivityManagerService(AMS)繼承自ActivityManagerNative,ActivityManagerNative繼承自Binder并實現了IActivityManager接口,因此AMS是一個Binder,是IActivityManager的具體實現;ApplicationThread繼承自ApplicationThreadNative,ApplicationThreadNative繼承自Binder并實現了IApplicationThread的接口,ApplicationThread是一個實現了很多schedule方法的業務接口,作為Binder使用,通過Handler回調到ActivityThread中處理;Instrumentation是一個輔助類,負責分擔Activity中通過ApplicationThread與AMS交互的一些業務邏輯。
- Activity的啟動過程涉及到的類:
Activity -> Instrumentation -> ApplicationThread -> AMS -> ActivityStarter -> ActivityStackSupervisor -> ActivityStack -> ActivityStackSupervisor -> ApplicationThread -> Handler -> ActivityThread(H)
;Service的啟動過程涉及到的類:Activity -> ContextImpl -> AMS -> ActiveService(AMS的輔助類) -> ApplicationThread -> Handler -> ActivityThread(H)
。 - Activity啟動流程:
? Activity內部一些邏輯執行交給Instrumentation,Instrumentation內部通過ApplicationThread這個Binder與AMS進行交互,這個交互是IPC過程,ApplicationThread是ActivityThread的一個內部類,類似Stub類,作為ActivityThread與AMS的橋接Binder對象。
? AMS處理了遠程請求的業務之后,進行一些遠程同步、控制、存儲邏輯后,調用ApplicationThread回來,由于Binder的調用是在線程池中進行(異步),因此在ApplicationThread內通過一個H(handler)來發送消息給ActivityThread處理結果。
? ActivityThread內處理結果時結合contextImpl、mInstrumentation等類進行一些列邏輯處理和生命周期等回調,因此onResume()、onConnection()、onReceive()
等方法都是在主線程執行的。
Activity啟動描述:
桌面Launcher也是一個APP,當點擊某個應用icon時,Launcher通知AMS啟動目標App,AMS記錄必要信息后pauseLanucher,當目標應用程序啟動時,入口方法為ActivityThread的main()
方法(ActivityThread就是主線程),main()
是一個靜態方法,內部會創建ActivityThread實例并創建主線程消息隊列、綁定ApplicationThread到AMS、執行主線程循環等,在ActivityThread的attach()
方法中遠程調用AMS的attachApplication()
方法將ApplicationThread最為回調Binder綁定到AMS,AMS中處理一些同步工作后,會通過ApplicationThread回調各個shcedule方法到ActivityThread,最后創建Application、Context、初始化View等完成界面繪制并顯示。
- 廣播分為普通廣播、有序廣播和粘性廣播,從Android5.0(其實3.1就開始了)開始系統為所有廣播默認添加了FLAG_EXCLUDE_STOPPED_PACKAGES標志,標記廣播是否要對已停止的應用起作用。
- ContentProvider是內容共享型組件,通過Binder向其他組件乃至其他應用提供數據;當ContentProvider所在的進程啟動時,ContentProvider會被創建并發布到AMS中,ActivityThread中會先加載ContentProvider,因此ContentProvider的
onCreate()
要先于Application的onCreate()
執行;ContentProvider是否是單例由
android:multiprocess
屬性值來控制,一般都是單實例存在。 - Service生命周期:
? startService():onCreate() -> onStartCommand()
? stopService():onDestroy()
? bindService():onCreate() -> onBind()
? unbindService():onUnbind() -> onDestroy()
先startService()
再bindService()
或先bindService()
再startService()
,只關閉其中一步不會onDestroy()
,需要stopService()
和unbindService()
都調用才會onDestory()
,順序不分先后,但是如果沒有bind的話直接調用unbindService()
會拋異常;bindService()
生命周期隨調用者,Activity退出后就unbind了,startService()
不隨調用了,即使程序雙擊退出Service仍存在,必須手動stopService()
或去應用管理里關掉Service;如果強殺進程,Service屬于意外死亡,默認情況下是粘性Service會自己重新onCreate() -> onStartCommand()
,但是沒啥用,進程殺死后內存數據都沒了,僅僅是重啟了Service;在onStartCommand()
中return標志位"START_NOT_STICKY"殺進程后不會重啟Service。