update time 2021年04月27日19:50:07,文章版本:V 1.4,閱讀時間40分鐘,建議先收藏后閱讀,注意以點學面,面試問法千變萬化但是答案就那些。 主要收集在面試過程中普遍問到的基礎知識(面試收集 主要來自于bilibili 嵩恒 螞蟻金服 2345 趣頭條 平安等互聯網公司)
由于總結的東西很多很亂,所以知識點并沒有深入探討,很多小標題的東西都可以寫成一篇單獨的總結,這里偷懶直接放在一起匯總了。
注:簡書平臺下的目錄不具備點擊跳轉功能
- Android
-
第三方庫 源碼總結
- LeakCanary 原理
- OkHttp
- ButterKnife
- Glide V1.4 緩存完善
Android
啟動
啟動模式
- standard 標準模式
- singleTop 棧頂復用模式 (例如:推送點擊消息界面)
- singleTask 棧內復用模式 (例如:首頁)
- singleInstance 單例模式 (單獨位于一個任務棧中,例如:撥打電話界面)
App啟動流程
?? wanandroid 有一個經典的問題 : Activity啟動流程中,大部分都是用Binder通訊,為啥跟Zygote通信的時候要用socket呢?
- ServiceManager (初始化binder線程池的地方)不能保證在zygote起來的時候已經初始化好,所以無法使用Binder。
- Binder工作依賴于多線程,但是fork的時候是不允許存在多線程的,多線程情況下進程fork容易造成死鎖,所以就不用Binder了。
多線程fork為什么會死鎖
線程里的doit()先執行.
doit執行的時候會給互斥體變量mutex加鎖.
mutex變量的內容會原樣拷貝到fork出來的子進程中(在此之前,mutex變量的內容已經被線程改寫成鎖定狀態).
4.子進程再次調用doit的時候,在鎖定互斥體mutex的時候會發現它已經被加鎖,所以就一直等待,直到擁有該互斥體的進程釋放它(實際上沒有人擁有這個mutex鎖).
5.線程的doit執行完成之前會把自己的mutex釋放,但這是的mutex和子進程里的mutex已經是兩份內存.所以即使釋放了mutex鎖也不會對子進程里的mutex造成什么影響.
Binder
直觀的說,Binder是一個類,實現了IBinder接口。
從IPC(進程間通信)角度來說,Binder是Android中一種跨進程通信方式。
還可以理解為一種虛擬的物理設備,它的設備驅動是/dev/binder。
從Android FrameWork角度來說,Binder是ServiceManager連接各種Manager(ActivityManager,WindowManager等等)和響應ManagerService的橋梁。
從Android應用層來說,Binder是客戶端和服務端進行通信的媒介。
??以AIDL為例子,客戶端在請求服務端通信的時候,并不是直接和服務端的某個對象聯系,而是用到了服務端的一個代理對象,通過對這個代理對象操作,然后代理類會把方法對應的code、傳輸的序列化數據、需要返回的序列化數據交給底層,也就是Binder驅動。然后Binder驅動把對應的數據交給服務器端,等結果計算好之后,再由Binder驅動把數據返回給客戶端。
如果想要更加詳細的資源 -> Binder設計與實現
ServiceManager
ServiceManager其實是為了管理系統服務而設置的一種機制,每個服務注冊在ServiceManager中,由ServiceManager統一管理,我們可以通過服務名在ServiceManager中查詢對應的服務代理,從而完成調用系統服務的功能。所以ServiceManager有點類似于DNS,可以把服務名稱和具體的服務記錄在案,供客戶端來查找。
??ServiceManager本身也運行在一個單獨的線程,本身也是一個服務端,客戶端其實是先通過跨進程獲取到ServiceManager的代理對象,然后通過ServiceManager代理對象再去找到對應的服務。所以每個APP程序都可以通過binder機制在自己的進程空間中創建一個ServiceManager代理對象。
??所以通過ServiceManager查找系統服務并調用方法的過程是進行了兩次跨進程通信。
序列化
- Serializable : Java 序列化方式,適用于存儲和網絡傳輸,serialVersionUID 用于確定反序列化和類版本是否一致,不一致時反序列化回失敗
- Parcelable :Android 序列化方式,適用于組件通信數據傳遞,性能高,
java中的序列化方式Serializable效率比較低,主要有以下原因:
- Serializable在序列化過程中會創建大量的臨時變量,這樣就會造成大量的GC。
- Serializable使用了大量反射,而反射操作耗時。
- Serializable使用了大量的IO操作,也影響了耗時。
所以Android就像重新設計了IPC方式Binder一樣,重新設計了一種序列化方式,結合Binder的方式,對上述三點進行了優化,一定程度上提高了序列化和反序列化的效率。
進程
進程保活
進程被殺原因:1.切到后臺內存不足時被殺;2.切到后臺廠商省電機制殺死;3.用戶主動清理
保活方式:
- Activity 提權:掛一個 1像素 Activity 將進程優先級提高到前臺進程
- Service 提權:啟動一個前臺服務(API>18會有正在運行通知欄)
- 廣播拉活 (監聽 開機 等系統廣播)
- Service 拉活
- JobScheduler 定時任務拉活 (android 高版本不行)
- 雙進程拉活
- 監聽其他大廠 廣播 (tx baidu 全家桶互相拉)
Hook
Hook 的選擇點:靜態變量和單例,因為一旦創建對象,它們不容易變化,非常容易定位。
Hook 過程:
尋找 Hook 點,原則是靜態變量或者單例對象,盡量 Hook public 的對象和方法。
選擇合適的代理方式,如果是接口可以用動態代理。
偷梁換柱——用代理對象替換原始對象。
多數插件化 也使用的 Hook技術
內存泄漏
- 構造單例的時候盡量別用Activity的引用;
- 靜態引用時注意應用對象的置空或者少用靜態引用;
- 使用靜態內部類+軟引用代替非靜態內部類;
- 及時取消廣播或者觀察者注冊;耗時任務、屬性動畫在Activity銷毀時記得cancel;
- 文件流、Cursor等資源及時關閉;
- Activity銷毀時WebView的移除和銷毀。
ANR
ANR原理
??ANR 全稱 Applicatipon No Response;Android 設計 ANR 的用意,是系統通過與之交互的組件(Activity,Service,Receiver,Provider)以及用戶交互(InputEvent)進行超時監控,以判斷應用進程(主線程)是否存在卡死或響應過慢的問題,通俗來說就是很多系統中看門狗(watchdog)的設計思想。
??以有序廣播為例(因為無序廣播沒有超時限制),在客戶端進程中,Binder 線程接收到 AMS 服務發送過來的廣播消息之后,會將此消息進行封裝成一個 Message,然后將 Message 發送到主線程消息隊列 (插入到消息隊列當前時間節點的位置,也正是基于此類設計導致較多消息調度及時性的問題)
??正常情況下,很多廣播請求都會在客戶端及時響應,然后通知到系統 AMS 服務取消本次超時監控。但是在部分業務場景或系統場景異常的情況下,發送的廣播未及時調度,沒有及時通知到系統服務,便會在系統服務側觸發超時,判定應用進程響應超時。
避免產生ANR
??不同的組件發生ANR的時間不一樣,Activity是阻塞5秒,BroadCastReceiver是阻塞10秒,Service是阻塞20秒
平時代碼中要注意幾點:
- 避免在主線程各種做耗時操作(訪問網絡,Socket通信,查詢大量SQL 語句,復雜邏輯計算等)
- 慎用Thread.wait()或者Thread.sleep()來阻塞主線程。
- Activity的onCreate和onResume回調中盡量避免耗時的代碼。
BroadcastReceiver中onReceive代碼也要盡量減少耗時,建議使用IntentService處理。
View
Window WindowManager WMS
Window :抽象類 不是實際存在的,而是以 View 的形式存在,通過 PhoneWindow 實現 (PhoneWindow = DecorView = Title + ContentView)
WindowManager:外界訪問 Window 的入口 管理Window 中的View , 內部通過 Binder 與 WMS IPC 進程交互
WMS:管理窗口 Surface 的布局和次序,作為系統級服務單獨運行在一個進程
SurfaceFlinger:將 WMS 維護的窗口按一定次序混合后顯示到屏幕上
View 工作流程
通過 SetContentView(),調用 到PhoneWindow ,后實例DecorView ,通過 LoadXmlResourceParser() 進行IO操作 解析xml文件 通過反射 創建出View,并將View繪制在 DecorView上,這里的繪制則交給了ViewRootImpl 來完成,通過performTraversals() 觸發繪制流程,performMeasure 方法獲取View的尺寸,performLayout 方法獲取View的位置 ,然后通過 performDraw 方法遍歷View 進行繪制。
事件分發
一個 MotionEvent 產生后,按 Activity -> Window -> DecorView(ViewGroup) -> View 順序傳遞,View 傳遞過程就是事件分發(可以理解為責任鏈設計模式),因為開發過程中存在事件沖突,所以需要熟悉流程:
- dispatchTouchEvent:用于分發事件,只要接受到點擊事件就會被調用,返回結果表示是否消耗了當前事件
- onInterceptTouchEvent:用于判斷是否攔截事件(只有ViewGroup中存在),當 ViewGroup 確定要攔截事件后,該事件序列都不會再觸發調用此 ViewGroup 的 onIntercept
- onTouchEvent:用于處理事件,返回結果表示是否處理了當前事件,未處理則傳遞給父容器處理。(事件順序是:OnTouchListener -> OnTouchEvent -> OnClick)
View.post
想要在 onCreate 中獲取到View寬高的方法有:
- ViewTreeObserver 監聽界面繪制事件,在layout時調用,使用完畢后記得removeListener
- 就是View.post
源碼分析:
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.post(action);
}
getRunQueue().post(action);
return true;
}
View.post(runable) 通過將runable 封裝為HandlerAction對象,如果attachInfo為null 則將Runnable事件 添加到等待數組中, attachInfo初始化是在 dispatchAttachedToWindow 方法,置空則是在detachedFromWindow方法中,所以在這兩個方法生命周期中,調用View.post方法都是直接讓 mAttachInfo.handler 執行。
ViewRootImpl.class
mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,
context);
final ViewRootHandler mHandler = new ViewRootHandler();
通過查找 mAttachInfo.handler 是在主線程中聲明的,沒有傳參則 Looper 為主線程Looper,所以在View.post中可以更新UI。
但是為什么可以再View.post()中獲取控件尺寸呢?
android 運行是消息驅動,通過源碼 可以看到 ViewRootImpl 中 是先將 TraversalRunnable添加到 Handler 中運行的 之后 才是 View.post()
ViewRootImpl.class
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
// 該方法之后才有 view.post()
performTraversals();
...
}
}
因此,這個時候Handler正在執行著TraversalRunnable這個Runnable,而我們post的Runnable要等待TraversalRunnable執行完才會去執行,而TraversalRunnable這里面又會進行measure,layout和draw流程,所以等到執行我們的Runnable時,此時的View就已經被measure過了,所以獲取到的寬高就是measure過后的寬高。
動畫
- 幀動畫 :AnimationDrawable 實現,在資源文件中存放多張圖片,占用內存多,容易OOM
- 補間動畫 :作用對象只限于 View 視覺改變,并沒有改變View 的 xy 坐標,支持 平移、縮放、旋轉、透明度,但是移動后,響應時間的位置還在 原處,補間動畫在執行的時候,直接導致了 View 執行 onDraw() 方法。補間動畫的核心本質就是在一定的持續時間內,不斷改變 Matrix 變換,并且不斷刷新的過程。
- 屬性動畫 :ObjectAnimator、ValuetAnimator、AnimatorSet 可以是任何View,動畫選擇也比較多,其中包含 差速器,可以控制動畫速度,節奏。類型估值器 可以根據當前屬性改變的百分比計算改變后的屬性值 。因為ViewGroup 在 getTransformedMotionEvent方法中通過子 View 的 hasIdentityMatrix() 來判斷子 View 是否經過位移之類的屬性動畫。調用子 View 的 getInverseMatrix() 做「反平移」操作,然后判斷處理后的觸摸點是否在子 View 的邊界范圍內。
提升動畫 可以打開 硬件加速,使GPU 承擔一部分CPU的工作。
Android 進程通訊方式
bundle : 由于Activity,Service,Receiver都是可以通過Intent來攜帶Bundle傳輸數據的,所以我們可以在一個進程中通過Intent將攜帶數據的Bundle發送到另一個進程的組件。(bundle只能傳遞三種類型,一是鍵值對的形式,二是鍵為String類型,三是值為Parcelable類型)
ContentProvider :ContentProvider是Android四大組件之一,以表格的方式來儲存數據,提供給外界,即Content Provider可以跨進程訪問其他應用程序中的數據
文件 :兩個進程可以到同一個文件去交換數據,我們不僅可以保存文本文件,還可以將對象持久化到文件,從另一個文件恢復。要注意的是,當并發讀/寫時可能會出現并發的問題。
Broadcast :Broadcast可以向android系統中所有應用程序發送廣播,而需要跨進程通訊的應用程序可以監聽這些廣播。
AIDL :AIDL通過定義服務端暴露的接口,以提供給客戶端來調用,AIDL使服務器可以并行處理。
Messager :Messenger封裝了AIDL之后只能串行運行,所以Messenger一般用作消息傳遞
Socket
Android 線程通信
Handler 和 AsyncTask (AsyncTask:異步任務,內部封裝了Handler)
Handler線程間通信
作用:線程之間的消息通信
流程:主線程默認實現了Looper (調用loop.prepare方法 向sThreadLocal中set一個新的looper對象, looper構造方法中又創建了MsgQueue) 手動創建Handler ,調用 sendMessage 或者 post (runable) 發送Message 到 msgQueue ,如果沒有Msg 這添加到表頭,有數據則判斷when時間 循環next 放到合適的 msg的next 后。Looper.loop不斷輪訓Msg,將msg取出 并分發到Handler 或者 post提交的 Runable 中處理,并重置Msg 狀態位。回到主線程中 重寫 Handler 的 handlerMessage 回調的msg 進行主線程繪制邏輯。
問題:
- Handler 同步屏障機制:通過發送異步消息,在msg.next 中會優先處理異步消息,達到優先級的作用
- Looper.loop 為什么不會卡死:為了app不掛掉,就要保證主線程一直運行存在,使用死循環代碼阻塞在msgQueue.next()中的nativePollOnce()方法里 ,主線程就會掛起休眠釋放cpu,線程就不會退出。Looper死循環之前,在ActivityThread.main()中就會創建一個 Binder 線程(ApplicationThread),接收系統服務AMS發送來的事件。當系統有消息產生(其實系統每 16ms 會發送一個刷新 UI 消息喚醒)會通過epoll機制 向pipe管道寫端寫入數據 就會發送消息給 looper 接收到消息后處理事件,保證主線程的一直存活。只有在主線程中處理超時才會讓app崩潰 也就是ANR。
- Messaage復用: 將使用完的Message清除附帶的數據后, 添加到復用池中 ,當我們需要使用它時,直接在復用池中取出對象使用,而不需要重新new創建對象。復用池本質還是Message 為node 的單鏈表結構。所以推薦使用Message.obation獲取 對象。
-
Looper、messageQuene、Handler關系線程:handle =1:多
,線程:Looper =1:1,線程:messagequeue=1:1
WebView
優化:單獨新起一個進程維護WebView,為了應對 webview 持續增加的內存使用。(牽扯到進程間通訊 可以參考上述鏈接 配置aidl 通訊)
WebView native和js通信
app優化 (項目中處理的一些難點)
主要分為 啟動優化,布局優化 ,打包優化 等
啟動優化
- 閃屏頁 優化,設置theme 默認歡迎背景
- 懶加載 第三方庫,不要都放在application 中初始化(關于多個啟動任務 有順序的話,可以使用有向無環 BFS算法進行排序啟動)
- 如果項目中有 webview ,可以提前在app空閑時間加載 webview 的內核,如果多處使用 可以創建緩存池,緩存webview,
- 如果android 5.0- 在applicaton 的 attchbaseContext() 中加載MultiDex.install 會更加耗時,可以采用 子線程(子線程加載 需要擔心ANR 和ContentProvider 未加載報錯的問題)或者單獨開一個進程B,進程B開啟子線程運行MultiDex.install ,讓applicaton 進入while 循環等待B進程加載結果。
MultiDex 優化,apk打包分為 android 5.0 + 使用 ART虛擬機 不用擔心
布局UI優化
看過布局繪制源碼流程后,可以知道 setContextView中 在ViewRootImpl 中使用 pull 的方法(這里可以擴展xml讀取方式 SAX :逐行解析、dom:將整個文件加載到內存 然后解析,不推薦、pull:類似于 SAX 進行了android平臺的優化,更加輕量級 方便)迭代讀取 xml標簽,然后對view 進行 measure,layout 和draw 的時候都存在耗時。通常優化方式有:
- 減少UI層級、使用merge、Viewstub標簽 優化重復的布局
- 優化 layout ,盡量多使用ConstraintLayout,因為 relalayout 和 linearlayout 比重的情況下都存在多次測量
- recyclerView 緩存 ( 可擴展 說明 rv的緩存原理 )
- 比較極端的 將 measure 和 layout 放在子線程,在主線程進行draw。或者 子線程中 加載view 進行IO讀取xml,通過Handler 回調主線程 加載view(比如android 原生類 AsyncLayoutInflate )
- 將xml直接通過 第三方工具(原理 APT 注解 翻譯xml)直接將xml 轉為 java代碼
更多UI優化文章
打包優化
Analyze APK 后可以發現代碼 和 資源其實是 app包的主要內存
- res 文件夾下 分辨率下的圖片 國內基本提供 xxhdpi 或者 xhdpi 即可,android 會分析手機分辨率到對應分辨率文件夾下加載資源
- res中的 png 圖片 都可以轉為 webg 或者 svg格式的 ,如果不能轉 則可以通過 png壓縮在減少內存
- 通過在 build.gradle 中配置 minifyEnabled true(混淆)shrinkResources true (移除無用資源)
- Assests 中的 mp4 /3 可以在需要使用的時候從服務器上下載下來,字體文件 使用字體提取工具FontZip 刪除不用的文字格式,畢竟幾千個中文app中怎么可能都使用
- lib 包如果 適配機型大多為高通 RAM ,可以單獨引用abiFilters "armeabi-v7a"
- build文件中 resConfigs "zh" 剔除掉 官方中或者第三方庫中的 外國文字資源
第三方庫 源碼總結
LeakCanary 原理
通過 registerActivityLifecycleCallbacks 監聽Activity或者Fragment 銷毀時候的生命周期(如果不想那個對象被監控則通過 AndroidExcludedRefs 枚舉,避免被檢測)
public void watch(Object watchedReference, String referenceName) {
if (this == DISABLED) {
return;
}
checkNotNull(watchedReference, "watchedReference");
checkNotNull(referenceName, "referenceName");
final long watchStartNanoTime = System.nanoTime();
String key = UUID.randomUUID().toString();
retainedKeys.add(key);
final KeyedWeakReference reference =
new KeyedWeakReference(watchedReference, key, referenceName, queue);
ensureGoneAsync(watchStartNanoTime, reference);
}
然后通過弱引用和引用隊列監控對象是否被回收(弱引用和引用隊列ReferenceQueue聯合使用時,如果弱引用持有的對象被垃圾回收,Java虛擬機就會把這個弱引用加入到與之關聯的引用隊列中。即 KeyedWeakReference持有的Activity對象如果被垃圾回收,該對象就會加入到引用隊列queue)
void waitForIdle(final Retryable retryable, final int failedAttempts) {
// This needs to be called from the main thread.
Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
@Override public boolean queueIdle() {
postToBackgroundWithDelay(retryable, failedAttempts);
return false;
}
});
}
IdleHandler,就是當主線程空閑的時候,如果設置了這個東西,就會執行它的queueIdle()方法,所以這個方法就是在onDestory以后,一旦主線程空閑了,就會執行一個延時五秒的子線程任務,任務:檢測到未被回收則主動 gc ,然后繼續監控,如果還是沒有回收掉,就證明是內存泄漏了。 通過抓取 dump文件,在使用 第三方 HAHA 庫 分析文件,獲取到到達泄露點最近的線路,通過 啟動另一個進程的 DisplayLeakService 發送通知 進行消息的展示。
OkHttp
同步和異步 網絡請求使用方法
// 同步get請求
OkHttpClient okHttpClient=new OkHttpClient();
final Request request=new Request.Builder().url("xxx").get().build();
final Call call = okHttpClient.newCall(request);
try {
Response response = call.execute();
} catch (IOException e) {
}
//異步get請求
OkHttpClient okHttpClient=new OkHttpClient();
final Request request=new Request.Builder().url("xxx").get().build();
final Call call = okHttpClient.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
}
});
// 異步post 請求
OkHttpClient okHttpClient1 = new OkHttpClient();
RequestBody requestBody = new FormBody.Builder()
.add("xxx", "xxx").build();
Request request1 = new Request.Builder().url("xxx").post(requestBody).build();
okHttpClient1.newCall(request1).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
}
@Override
public void onResponse(Call call, Response response) throws IOException {
}
});
同步請求流程:
通過OkHttpClient new生成call實例 Realcall
Dispatcher.executed() 中 通過添加realcall到runningSyncCalls隊列中
通過 getResponseWithInterceptorChain() 對request層層攔截,生成Response
通過Dispatcher.finished(),把call實例從隊列中移除,返回最終的response
異步請求流程:
生成一個AsyncCall(responseCallback)實例(實現了Runnable)
AsyncCall通過調用Dispatcher.enqueue(),并判斷maxRequests (最大請求數)maxRequestsPerHost(最大host請求數)是否滿足條件,如果滿足就把AsyncCall添加到runningAsyncCalls中,并放入線程池中執行;如果條件不滿足,就添加到等待就緒的異步隊列,當那些滿足的條件的執行時 ,在Dispatcher.finifshed(this)中的promoteCalls();方法中 對等待就緒的異步隊列進行遍歷,生成對應的AsyncCall實例,并添加到runningAsyncCalls中,最后放入到線程池中執行,一直到所有請求都結束。
責任鏈模式 和 攔截器
責任鏈
源碼跟進 execute() 進入到 getResponseWithInterceptorChain() 方法
Response getResponseWithInterceptorChain() throws IOException {
//責任鏈 模式
List<Interceptor> interceptors = new ArrayList<>();
interceptors.addAll(client.interceptors());
interceptors.add(retryAndFollowUpInterceptor);
interceptors.add(new BridgeInterceptor(client.cookieJar()));
interceptors.add(new CacheInterceptor(client.internalCache()));
interceptors.add(new ConnectInterceptor(client));
if (!forWebSocket) {
interceptors.addAll(client.networkInterceptors());
}
interceptors.add(new CallServerInterceptor(forWebSocket));
Interceptor.Chain chain = new RealInterceptorChain(interceptors, null, null, null, 0,
originalRequest, this, eventListener, client.connectTimeoutMillis(),
client.readTimeoutMillis(), client.writeTimeoutMillis());
return chain.proceed(originalRequest);
}
chain.proceed() 方法核心代碼。每個攔截器 intercept()方法中的chain,都在上一個 chain實例的 chain.proceed()中被初始化,并傳遞了攔截器List與 index,調用interceptor.intercept(next),直接最后一個 chain實例執行即停止。
//遞歸循環下一個 攔截器
RealInterceptorChain next = new RealInterceptorChain(interceptors, streamAllocation, httpCodec,
connection, index + 1, request, call, eventListener, connectTimeout, readTimeout,
writeTimeout);
Interceptor interceptor = interceptors.get(index);
Response response = interceptor.intercept(next);
@Override public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
RealInterceptorChain realChain = (RealInterceptorChain) chain;
Call call = realChain.call();
EventListener eventListener = realChain.eventListener();
StreamAllocation streamAllocation = new StreamAllocation(client.connectionPool(),
createAddress(request.url()), call, eventListener, callStackTrace);
while (true) {
...
// 循環中 再次調用了 chain 對象中的 proceed 方法,達到遞歸循環。
response = realChain.proceed(request, streamAllocation, null, null);
releaseConnection = false;
...
}
}
攔截器
- RetryAndFollowUpInterceptor :重連并跟蹤 攔截器。
- BridgeInterceptor : 將用戶請求構建為網絡請求(hander cooker content-type 等) 并發起請求 。
- CacheInterceptor : 緩存攔截器 負責從緩存中返回響應和把網絡請求響應寫入緩存。
- ConnectInterceptor : 與服務端 建立連接,并且獲得通向服務端的輸入和輸出流對象。
OkHttp 流程
- 采用責任鏈方式的攔截器,實現分成處理網絡請求,可更好的擴展自定義攔截器(采用GZIP壓縮,支持http緩存)
- 采用線程池(thread pool)和連接池(Socket pool)解決多并發問題,同時連接池支持多路復用(http2才支持,可以讓一個Socket同時發送多個網絡請求,內部自動維持順序.相比http只能一個一個發送,更能減少創建開銷))
- 底層采用socket和服務器進行連接.采用okio實現高效的io流讀寫
ButterKnife
butterKnife 使用的是 APT 技術 也就是編譯時注解,不同于運行時注解(在運行過程中通過反射動態地獲取相關類,方法,參數等信息,效率低耗時等缺點),編譯時注解 則是在代碼編譯過程中對注解進行處理(annotationProcessor技術),通過注解獲取相關類,方法,參數等信息,然后在項目中生成代碼,運行時調用,其實和直接手寫代碼一樣,沒有性能問題,只有編輯時效率問題。
ButterKnife在Bind方法中 獲取到DecorView,然后通過Activity和DecorView對象獲取xx_ViewBinding類的構造對象,然后通過構造方法反射實例化了這個類 Constructor。
在編寫完demo之后,需要先build一下項目,之后可以在build/generated/source/apt/debug/包名/下面找到 對應的xx_ViewBinding類,查看bk 幫我們做的事情,
# xx_ViewBinding.java
@UiThread
public ViewActivity_ViewBinding(ViewActivity target, View source) {
this.target = target;
target.view = Utils.findRequiredView(source, R.id.view, "field 'view'");
}
# Utils.java
public static View findRequiredView(View source, @IdRes int id, String who) {
View view = source.findViewById(id);
if (view != null) {
return view;
}
String name = getResourceEntryName(source, id);
throw new IllegalStateException("Required view ...."}
通過上述上述代碼 可以看到 注解也是幫我們完成了 findviewbyid 的工作。
butterknife 實現流程
- 掃描Java代碼中所有的ButterKnife注解
- 發現注解, ButterKnifeProcessor會幫你生成一個Java類,名字<類名>$$ViewBinding.java,這個新生成的類實現了Unbinder接口,類中的各個view 聲明和添加事件都添加到Map中,遍歷每個注解對應通過JavaPoet生成的代碼。
未來
??Gradle插件升級到5.0版本之后ButterKnife將無法再被使用,R文件中的 id將添加final標識符,雖然 jake大神通過生成R2文件的方式,嘗試避開版本升級帶來的影響。但是隨著官方ViewBinding等技術的出現,身為開發也要不斷學習新技術才是正途。
??我們這里說下 被final修飾的基礎類型和String類型為什么不能被反射?
答:由于JVM 內聯優化的機制,編譯器將指定的函數體插入并取代每一處調用該函數的地方(就是在方法編譯前已經進行了賦值),從而節省了每次調用函數帶來的額外時間開支。
Glide
Glide基礎流程
- with 流程 (anctivity fragment application 不同參數 不同的流程 )隱藏fragment 獲取activity 生命周期
- load 流程 (Engine數據的封裝、 緩存key準備 、EngineJob配合 第三部 into的使用)
- into 流程 (只討論普通的bitmap:開始進行網絡請求,設置 loading 狀態 或者 err狀態根據狀態顯示不同的已經設置的圖片,根絕請求結果獲取inputsteam,轉為 bitmap 封裝成Resource<Bitmap>,通過回調 返回給主線程 handler,更新界面ui)
Glide 緩存
- 內存緩存 : ( ActiveResources(弱引用對象WeakReference HashMap )中去獲取,如果沒有去 LruCache中查找 。(lruCache 如果緩存達到LinkedHashMap設置最大數目,清除最少使用的緩存 。) Glide 中正在使用中的圖片使用弱引用來進行緩存,不在使用中的圖片使用LruCache來進行緩存)
- 硬盤緩存 :
放一個圖片方便理解
Glide創建源碼
//Map弱引用 glide 一級緩存 (儲存當前正在活動/可見的資源)
Map<Key, WeakReference<EngineResource<?>>> activeResources
//根據當前機器參數計算需要設置的緩存大小
MemorySizeCalculator calculator = new MemorySizeCalculator(context);
//創建 Bitmap 池
if (bitmapPool == null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
int size = calculator.getBitmapPoolSize();
//Bitmap復用
bitmapPool = new LruBitmapPool(size);
} else {
bitmapPool = new BitmapPoolAdapter();
}
}
//創建內存緩存 基于lruCache 實現二級緩存 (儲存 不可見的資源)
if (memoryCache == null) {
memoryCache = new LruResourceCache(calculator.getMemoryCacheSize());
}
//創建磁盤緩存 三級緩存
if (diskCacheFactory == null) {
diskCacheFactory = new InternalCacheDiskCacheFactory(context);
}
一級緩存
??activeResources 在構建時會和 ReferenceQueue 進行綁定,當弱引用被移除的時候 ReferenceQueue就可以知道弱引用是否被移除掉。
ReferenceQueue 會通過 addIdleHandler 的方式 添加到MessageQueue 中一個IdleHandler 對象,Handler在空閑的時候會調用該方法,在方法中檢查是否gc。Bitmap復用
??LruBitmapPool策略模式,主要實現都在 LruPoolStrategy(主要實現類:SizeConfigStrategy) 中,和二級緩存一樣,也是使用的Lru算法來維護的BitmapPool(Glide自定義了這里的數據結構,詳情查閱下文的 Glide緩存參考文章)二級內存緩存
??內存緩存同樣使用 LRU 算法 (可以參考Java中的 LinkedHashMap,在HashMap的基礎上,將Value 串成一個雙向鏈表,根據訪問修改操作,調整鏈表順序)三級磁盤緩存
??Glide磁盤緩存都放在getCacheDir()下的image_manager_disk_cache文件中,文件名稱是通過圖片多個配置生成的,保證唯一性。在向磁盤寫入文件時(put 方法)會使用重入鎖來同步代碼(可以理解為 :ReentrantLock),磁盤緩存也是使用的Lru算法,不過是基于 journal 日志,記錄圖片的添加刪除和讀取操作。