Android總結篇

update time 2021年04月27日19:50:07,文章版本:V 1.4,閱讀時間40分鐘,建議先收藏后閱讀,注意以點學面,面試問法千變萬化但是答案就那些。 主要收集在面試過程中普遍問到的基礎知識(面試收集 主要來自于bilibili 嵩恒 螞蟻金服 2345 趣頭條 平安等互聯網公司)
由于總結的東西很多很亂,所以知識點并沒有深入探討,很多小標題的東西都可以寫成一篇單獨的總結,這里偷懶直接放在一起匯總了。

中級Android面試總結之網絡、Java基礎篇 鏈接

注:簡書平臺下的目錄不具備點擊跳轉功能

Android

啟動

啟動模式

  1. standard 標準模式
  2. singleTop 棧頂復用模式 (例如:推送點擊消息界面)
  3. singleTask 棧內復用模式 (例如:首頁)
  4. singleInstance 單例模式 (單獨位于一個任務棧中,例如:撥打電話界面)

App啟動流程

啟動流程

app啟動交互邏輯
??在Android 層 第一步就是 fork Zygote 進程(1. 創建服務端Socket,為后續創建進程通信做準備 2. 加載虛擬機 3.fork了System Server進程,負責啟動和管理Java Framework層,包括ActivityManagerService,PackageManagerService,WindowManagerService、binder線程池等 )。

?? wanandroid 有一個經典的問題 : Activity啟動流程中,大部分都是用Binder通訊,為啥跟Zygote通信的時候要用socket呢?

  1. ServiceManager (初始化binder線程池的地方)不能保證在zygote起來的時候已經初始化好,所以無法使用Binder
  2. Binder工作依賴于多線程,但是fork的時候是不允許存在多線程的,多線程情況下進程fork容易造成死鎖,所以就不用Binder了

多線程fork為什么會死鎖

  1. 線程里的doit()先執行.

  2. doit執行的時候會給互斥體變量mutex加鎖.

  3. 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效率比較低,主要有以下原因:

  1. Serializable在序列化過程中會創建大量的臨時變量,這樣就會造成大量的GC。
  2. Serializable使用了大量反射,而反射操作耗時。
  3. Serializable使用了大量的IO操作,也影響了耗時。

所以Android就像重新設計了IPC方式Binder一樣,重新設計了一種序列化方式,結合Binder的方式,對上述三點進行了優化,一定程度上提高了序列化和反序列化的效率。

進程

進程保活

進程被殺原因:1.切到后臺內存不足時被殺;2.切到后臺廠商省電機制殺死;3.用戶主動清理

保活方式:

  1. Activity 提權:掛一個 1像素 Activity 將進程優先級提高到前臺進程
  2. Service 提權:啟動一個前臺服務(API>18會有正在運行通知欄)
  3. 廣播拉活 (監聽 開機 等系統廣播)
  4. Service 拉活
  5. JobScheduler 定時任務拉活 (android 高版本不行)
  6. 雙進程拉活
  7. 監聽其他大廠 廣播 (tx baidu 全家桶互相拉)

Hook

Hook android 使用

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秒

平時代碼中要注意幾點:

  1. 避免在主線程各種做耗時操作(訪問網絡,Socket通信,查詢大量SQL 語句,復雜邏輯計算等)
  2. 慎用Thread.wait()或者Thread.sleep()來阻塞主線程。
  3. 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

參考博客1
參考博客2

想要在 onCreate 中獲取到View寬高的方法有:

  1. ViewTreeObserver 監聽界面繪制事件,在layout時調用,使用完畢后記得removeListener
  2. 就是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 線程通信

HandlerAsyncTask (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 進行主線程繪制邏輯。

問題:

  1. Handler 同步屏障機制:通過發送異步消息,在msg.next 中會優先處理異步消息,達到優先級的作用
  2. Looper.loop 為什么不會卡死:為了app不掛掉,就要保證主線程一直運行存在,使用死循環代碼阻塞在msgQueue.next()中的nativePollOnce()方法里 ,主線程就會掛起休眠釋放cpu,線程就不會退出。Looper死循環之前,在ActivityThread.main()中就會創建一個 Binder 線程(ApplicationThread),接收系統服務AMS發送來的事件。當系統有消息產生(其實系統每 16ms 會發送一個刷新 UI 消息喚醒)會通過epoll機制 向pipe管道寫端寫入數據 就會發送消息給 looper 接收到消息后處理事件,保證主線程的一直存活。只有在主線程中處理超時才會讓app崩潰 也就是ANR。
  3. Messaage復用: 將使用完的Message清除附帶的數據后, 添加到復用池中 ,當我們需要使用它時,直接在復用池中取出對象使用,而不需要重新new創建對象。復用池本質還是Message 為node 的單鏈表結構。所以推薦使用Message.obation獲取 對象。
  4. Looper、messageQuene、Handler關系線程:handle =1:多
    ,線程:Looper =1:1,線程:messagequeue=1:1

WebView

參考文章

優化:單獨新起一個進程維護WebView,為了應對 webview 持續增加的內存使用。(牽扯到進程間通訊 可以參考上述鏈接 配置aidl 通訊)

WebView native和js通信

image

參考文章 webview js通訊

app優化 (項目中處理的一些難點)

主要分為 啟動優化,布局優化 ,打包優化 等

內存優化參考文章

啟動優化

  1. 閃屏頁 優化,設置theme 默認歡迎背景
  2. 懶加載 第三方庫,不要都放在application 中初始化(關于多個啟動任務 有順序的話,可以使用有向無環 BFS算法進行排序啟動)
  3. 如果項目中有 webview ,可以提前在app空閑時間加載 webview 的內核,如果多處使用 可以創建緩存池,緩存webview,
  4. 如果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 的時候都存在耗時。通常優化方式有:

  1. 減少UI層級、使用merge、Viewstub標簽 優化重復的布局
  2. 優化 layout ,盡量多使用ConstraintLayout,因為 relalayout 和 linearlayout 比重的情況下都存在多次測量
  3. recyclerView 緩存 ( 可擴展 說明 rv的緩存原理 )
  4. 比較極端的 將 measure 和 layout 放在子線程,在主線程進行draw。或者 子線程中 加載view 進行IO讀取xml,通過Handler 回調主線程 加載view(比如android 原生類 AsyncLayoutInflate )
  5. 將xml直接通過 第三方工具(原理 APT 注解 翻譯xml)直接將xml 轉為 java代碼
    更多UI優化文章

打包優化

Analyze APK 后可以發現代碼 和 資源其實是 app包的主要內存

  1. res 文件夾下 分辨率下的圖片 國內基本提供 xxhdpi 或者 xhdpi 即可,android 會分析手機分辨率到對應分辨率文件夾下加載資源
  2. res中的 png 圖片 都可以轉為 webg 或者 svg格式的 ,如果不能轉 則可以通過 png壓縮在減少內存
  3. 通過在 build.gradle 中配置 minifyEnabled true(混淆)shrinkResources true (移除無用資源)
  4. Assests 中的 mp4 /3 可以在需要使用的時候從服務器上下載下來,字體文件 使用字體提取工具FontZip 刪除不用的文字格式,畢竟幾千個中文app中怎么可能都使用
  5. lib 包如果 適配機型大多為高通 RAM ,可以單獨引用abiFilters "armeabi-v7a"
  6. 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;
        ...
    }
}

攔截器

  1. RetryAndFollowUpInterceptor :重連并跟蹤 攔截器。
  2. BridgeInterceptor : 將用戶請求構建為網絡請求(hander cooker content-type 等) 并發起請求 。
  3. CacheInterceptor : 緩存攔截器 負責從緩存中返回響應和把網絡請求響應寫入緩存。
  4. ConnectInterceptor : 與服務端 建立連接,并且獲得通向服務端的輸入和輸出流對象。

OkHttp 流程

  1. 采用責任鏈方式的攔截器,實現分成處理網絡請求,可更好的擴展自定義攔截器(采用GZIP壓縮,支持http緩存)
  2. 采用線程池(thread pool)和連接池(Socket pool)解決多并發問題,同時連接池支持多路復用(http2才支持,可以讓一個Socket同時發送多個網絡請求,內部自動維持順序.相比http只能一個一個發送,更能減少創建開銷))
  3. 底層采用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 實現流程

  1. 掃描Java代碼中所有的ButterKnife注解
  2. 發現注解, ButterKnifeProcessor會幫你生成一個Java類,名字<類名>$$ViewBinding.java,這個新生成的類實現了Unbinder接口,類中的各個view 聲明和添加事件都添加到Map中,遍歷每個注解對應通過JavaPoet生成的代碼。

未來
??Gradle插件升級到5.0版本之后ButterKnife將無法再被使用,R文件中的 id將添加final標識符,雖然 jake大神通過生成R2文件的方式,嘗試避開版本升級帶來的影響。但是隨著官方ViewBinding等技術的出現,身為開發也要不斷學習新技術才是正途。
??我們這里說下 被final修飾的基礎類型和String類型為什么不能被反射?

答:由于JVM 內聯優化的機制,編譯器將指定的函數體插入并取代每一處調用該函數的地方(就是在方法編譯前已經進行了賦值),從而節省了每次調用函數帶來的額外時間開支。

Glide

Glide基礎流程

  1. with 流程 (anctivity fragment application 不同參數 不同的流程 )隱藏fragment 獲取activity 生命周期
  2. load 流程 (Engine數據的封裝、 緩存key準備 、EngineJob配合 第三部 into的使用)
  3. into 流程 (只討論普通的bitmap:開始進行網絡請求,設置 loading 狀態 或者 err狀態根據狀態顯示不同的已經設置的圖片,根絕請求結果獲取inputsteam,轉為 bitmap 封裝成Resource<Bitmap>,通過回調 返回給主線程 handler,更新界面ui)

Glide 緩存

  1. 內存緩存 : ( ActiveResources(弱引用對象WeakReference HashMap )中去獲取,如果沒有去 LruCache中查找 。(lruCache 如果緩存達到LinkedHashMap設置最大數目,清除最少使用的緩存 。) Glide 中正在使用中的圖片使用弱引用來進行緩存,不在使用中的圖片使用LruCache來進行緩存)
  2. 硬盤緩存

放一個圖片方便理解


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);
}
  1. 一級緩存
    ??activeResources 在構建時會和 ReferenceQueue 進行綁定,當弱引用被移除的時候 ReferenceQueue就可以知道弱引用是否被移除掉。
    ReferenceQueue 會通過 addIdleHandler 的方式 添加到MessageQueue 中一個IdleHandler 對象,Handler在空閑的時候會調用該方法,在方法中檢查是否gc。

  2. Bitmap復用
    ??LruBitmapPool策略模式,主要實現都在 LruPoolStrategy(主要實現類:SizeConfigStrategy) 中,和二級緩存一樣,也是使用的Lru算法來維護的BitmapPool(Glide自定義了這里的數據結構,詳情查閱下文的 Glide緩存參考文章)

  3. 二級內存緩存
    ??內存緩存同樣使用 LRU 算法 (可以參考Java中的 LinkedHashMap,在HashMap的基礎上,將Value 串成一個雙向鏈表,根據訪問修改操作,調整鏈表順序)

  4. 三級磁盤緩存
    ??Glide磁盤緩存都放在getCacheDir()下的image_manager_disk_cache文件中,文件名稱是通過圖片多個配置生成的,保證唯一性。在向磁盤寫入文件時(put 方法)會使用重入鎖來同步代碼(可以理解為 :ReentrantLock),磁盤緩存也是使用的Lru算法,不過是基于 journal 日志,記錄圖片的添加刪除和讀取操作。

Glide 緩存 參考文章
Glide 緩存 參考文章2

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 別人的總結不一定適合自己,所以盡量多做一些自己的總結,針對自己的薄弱點重點說明,適當的借鑒別人,少走一些彎路。最重...
    renkuo閱讀 7,457評論 2 48
  • 表情是什么,我認為表情就是表現出來的情緒。表情可以傳達很多信息。高興了當然就笑了,難過就哭了。兩者是相互影響密不可...
    Persistenc_6aea閱讀 125,795評論 2 7
  • 16宿命:用概率思維提高你的勝算 以前的我是風險厭惡者,不喜歡去冒險,但是人生放棄了冒險,也就放棄了無數的可能。 ...
    yichen大刀閱讀 6,088評論 0 4