主頁 > 移動端開發 > 第23章 統一編程介面——外觀模式

第23章 統一編程介面——外觀模式

2021-01-05 10:30:20 移動端開發

第23章 統一編程介面——外觀模式

  • 23.1 外觀模式介紹
  • 23.2 外觀模式定義
  • 23.3 外觀模式的使用場景
  • 23.4 外觀模式的UML類圖
  • 23.5 外觀模式的簡單示例
  • 23.6 Android原始碼中的外觀模式
  • 23.7 深度拓展
    • 23.7.1 Android資源的加載與匹配
    • 23.7.2 動態加載框架的實作
  • 23.8 外觀模式實戰
  • 23.9 小結

23.1 外觀模式介紹

外觀模式(Facade)在開發程序中的運用頻率非常高,尤其是在現階段,各種第三方SDK"充斥”在我們的周邊,而這些SDK大多會使用外觀模式,通過一個外觀類使得整個系統的介面只有一個統一的高層介面,這樣能夠降低用戶的使用成本,也對用戶屏蔽了很多實作細節,當然,在我們的開發程序中,外觀模式也是我們封裝API的常用手段,例如網路模塊、ImageLoader模塊等, 可能你已經在開發中運用過無數次外觀模式,只是沒有在理論層面認識它,本章我們就從理論結合實踐上學習這個模式,

23.2 外觀模式定義

要求一個子系統的外部與其內部的通信必須通過一個統一的物件進行,門面模式(Facade模式)提供一個高層次的介面,使得子系統更易于使用,

23.3 外觀模式的使用場景

  1. 為一個復雜子系統提供一個簡單介面,子系統往往因為不斷演化而變得越來越復雜,甚至可能被替換,大多數模式使用時都會產生更多、更小的類,這使子系統更具可重用性的同時也更容易對子系統進行定制、修改,這種易變性使得隱藏子系統的具體實作變得尤為重要,Facade可以提供一個簡單統一的介面,對外隱藏子系統的具體實作、隔離變化,
  2. 當你需要構建一個層次結構的子系統時,使用Facade模式定義子系統中每層的入口點,如果子系統之間是相互依賴的,你可以讓它們僅通過Facade介面進行通信,從而簡化了它們之間的依賴關系,

23.4 外觀模式的UML類圖

UML類圖如圖23-1所示,
在這里插入圖片描述
角色介紹

  • Facade:系統對外的統一介面,系統內部系統地作業,
  • SystemA、SystemB、SystemC:子系統介面(實作部分未在圖23-1中給出),
    外觀模式介面比較簡單,就是通過一個統一的介面對外提供服務,使得外部程式只通過一個類就可以實作系統內部的多種功能,而這些實作功能的內部子系統之間可能也有互動,或者說完成一個功能需要幾個子系統之間進行協作,如果沒有封裝,那么用戶就需要操作幾個子系統的互動邏輯,容易出現錯誤,而通過外觀類來對外屏蔽這些復雜的互動,降低用戶的使用成本,它的結構如圖23-2所示,

23.5 外觀模式的簡單示例

生活中使用外觀模式的例子非常多,任何一個類似中央調度結構的組織都類似外觀模式,舉個簡單的例子,手機就是一個外觀模式的例子,它集合了電話功能、短信功能、GPS、拍照等于一身,通過手機你就可以完成各種功能,而不是當你打電話時使用一個諾基亞1100,要拍照時非得用一個相機,如果每使用一個功能你就必須操作特定的設備,會使得整個程序很繁瑣,而手機給了你一個統 一的入口,集電話、上網、拍照等功能于一身,使用方便,操作簡單,結構圖如圖23-3所示,

下面我們來簡單模擬一下手機的外觀模式實作,首先我們建立一個MobilePhone代碼大致如下,
在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述
MobilePhone類中含有兩個子系統,也就是撥號系統和拍照系統,MobilePhone將這兩個系統封裝起來,為用戶提供一個統一的操作介面,也就是說用戶只需要通過MobilePhone這個類就可以操作打電話和拍照這兩個功能,用戶不需要知道有Phone這個介面以及它的實作類是Phonelmpl,同樣也不需要知道Camera相關的資訊,通過MobilePhone就可以包攬一切,而在MobilePhone中也封裝了兩個子系統的互動,例如視頻電話時需要先打開攝像頭,然后再開始撥號,如果沒有這一步的封裝,每次用戶實作視頻通話功能時都需要手動打開攝像頭、進行撥號,這樣會增加用戶的使用成本,外觀模式使得這些操作更加簡單、易用,

我們來看看Phone介面和Phonelmpl,
在這里插入圖片描述
代碼很簡單,就是單純的抽象與實作,Camera也是類似的實作,具體代碼如下,
在這里插入圖片描述
在這里插入圖片描述
運行結果:
在這里插入圖片描述
從上述代碼中可以看到,外觀模式就是統一介面封裝,將子系統的邏輯、互動隱藏起來,為用戶提供一個高層次的介面,使得系統更加易用,同時也對外隱藏了具體的實作,這樣即使具體的子系統發生了變化,用戶也不會感知到,因為用戶使用的是Facade高層介面,內部的變化對于用戶來說并不可見,這樣一來就將變化隔離開來,使得系統也更為靈活,

23.6 Android原始碼中的外觀模式

在用Android開發程序中,Context是最重要的一個型別,Context意為背景關系,也就是程式的運行環境,它封裝了很多重要的操作,如startActivity、sendBroadcast()、bindService等,因此,Context對開發者來說是最重要的高層介面,Context只是一個定義了很多介面的抽象類,這些介面的功能實作并不是在Context及其子類中,而是通過其他子系統來完成,例如startActivity的真正實作是通過ActivityManagerService,獲取應用包相關資訊則是通過PackageManagerService,Context只是做了一個高層次的統一封裝,正如上文所示,Context只是一個抽象類,它的真正實作在Contextlmpl類中,Contextlmpl就是今天我們要分析的外觀類,

在本書的前面章節中已經提到多次,在應用啟動時,首先會fork一個子行程,并且呼叫ActivityThread.main方法啟動該行程,ActivityThread又會構建Application物件,然后和Activity、Contextlmpl關聯起來,最后會呼叫Activity的onCreate、onStart、onResume函式使Activity運行起來,此時應用的用戶界面就呈現在我們面前了,main函式會間接地呼叫ActivityThread中的handleLaunchActivity函式啟動默認的Activity, handleLaunchActivity代碼如下,
在這里插入圖片描述
在handleLaunchActivity函式中會呼叫 perfromLaunchActivity函式執行Applicaton、Contextlmpl、Activity的創建作業,并且通過Activity類的attach函式將這3者關聯起來,相關代碼在注釋4處, 而Activity本身又是Context的子類,因此,Activity就具有了Context定義的所有方法,但Activity并不實作具體的功能,它只是繼承了Context的介面,并且將相關的操作轉發給Contextlmpl物件

這個Contextlmpl存盤在Activity的上兩層父類ContextWrapper中,變數名為mBase,具體代碼如下,
在這里插入圖片描述
在ActivityThread類的perfromLaunchActivity函式中會呼叫Activity的attach方法將Contextlmpl等物件關聯到Activity中,這個Contextlmpl最侄訓被Contentwrapper類的mBase欄位參考,我們先看看attach方法的內部實作,
在這里插入圖片描述
attach函式主要就是一些賦值操作,這里我們只關心mBase的初始化,在attach函式中,第一句就呼叫了attachBaseContext函式,該函式定義在ContextWrapper類,它就是簡單地將Context引數傳遞給mBase欄位,此時,我們的Activity內部就持有了Contextlmpl的參考

Activity在開發程序中部分充當了代理的角色,例如,當我們通過Activity物件呼叫sendBroadcast、getResource等函式時,實際上Activity只是代理了ContextImpl的操作,也就是內部都呼叫了mBase物件的相應方法來處理,這些操作被封裝在Activity的父類的ContentWrapper中,代碼如下所示,在這里插入圖片描述
在這里插入圖片描述
既然Contextlmpl那么重要,包含了各個系統服務的呼叫與操作,那么我們來看看它的相關實作,
在這里插入圖片描述
在這里插入圖片描述
從上述程式中可以看到,Contextlmpl內部封裝了很多不同子系統的操作,例如,Activity的跳轉、發送廣播、啟動服務、設定壁紙等,這些作業并不是在Contextlmpl中實作,而是轉交給了具體的子系統進行處理,通過Context這個抽象了、定義了一組介面,Contextlmpl實作Context定義的介面,這使得用戶可以通過Context這個介面統一進行與Android系統的互動,這樣用戶通常情況下就不需要對每個子系統進行了解,例如啟動Activity時用戶不需要手動呼叫mMainThread.getInstrurnentation().execStartActivity函式進行執行,發送廣播時也不需要直接操作ActivityManagerNative類,用戶與系統服務的互動都通過Context的高層介面,這樣對用戶屏蔽了具體實作的細節,降低了使用成本,

通過Contextlmpl的封裝之后,用戶與系統服務之間的互動如圖23-4所示,
在這里插入圖片描述
從上述示例來看,外觀模式的實作非常簡單, 沒有復雜的型別結構,只是通過一組高層介面封裝了各個子系統的操作,并且統一提供給用戶,試想一下如果沒有外觀模式的封裝,那么用戶就必須知道各個子系統的相關細節,甚至它們之間的協作流程,當子系統較多時這些相互之間的關系就會很亂,導致用戶在使用相關功能時難度也會變大,易于出錯,而使用外觀模式的封裝就避免了用戶需要與多個子系統進行互動,降低了用戶的使用成本,對外也屏蔽了具體細節,保證了系統的易用性、穩定性,

23.7 深度拓展

23.7.1 Android資源的加載與匹配

在Android開發中,我們為了螢屏適配通常會為一個應用做多套資源,使得在不同解析度的設備上能夠盡量保持一致的UI效果,那么我們不禁要問Android的資源在哪?讀取資源的形式又是怎樣?下面我們就來簡單分析一下Android的資源機制,

其實在Android應用程式資源的編譯和打包之后就生成一個資源索引表檔案resources.arsc,這個應用程式資源會被打包到APK檔案中,Android應用程式在運行的程序中,通過一個稱為Resource來獲取資源,但實際上Resource內部又是通過一個叫AssetManager的資源管理器來讀取打包在APK檔案里面的資源檔案,那么AssetManager如何與應用關聯在一起,它又是如何找到應用的資源,這就是我們本節要關注的重點,

我們在上文中說到,獲取資源的操作實際上是由Contextlmpl來完成的,Activity、Service等組件的getResource函式最終都轉發給了ContextImpl型別的mBase欄位,也就是呼叫了Contextlmpl的getResource函式,而這個Resource在Contextlmpl關聯到Activity之前就會初始化Resource物件,相關代碼如下,
在這里插入圖片描述
在上述performLaunchActivity函式中,首先創建了Activity與Application,然后通過 createBaseContextForActivity創建了一個Contextlmpl物件,而在createBaseContextForActivity函式中又呼叫了Contextlmpl類的createActivityContext靜態函式,我們看看原始碼,
在這里插入圖片描述
在這里插入圖片描述
createActivityContext函式中最終呼叫了Contextlmpl的建構式,在該函式中會初始化該行程的各個欄位,例如資源、包資訊、螢屏配置等,這里只關心與資源相關的代碼,在通過mPackagelnfo得到對應的資源之后,會呼叫ResourcesManager的getTopLevelResources來根據設備配置等相關資訊獲取到對應的資源,也就是資源的適配,getTopLevelResources代碼如下,
在這里插入圖片描述
在這里插入圖片描述
首先會以APK路徑、螢屏設備id、配置等構建一個資源key,根據這個key到ActivityThread類的mActiveResources(HashMap型別)中查詢是否已經加載過該APK的資源,如果含有快取那么直接使用快取,這個mActiveResources維護了當前應用程式行程中加載的每一個APK檔案及其對應的Resources物件的對應關系,如果沒有快取,那么就會新創建一個,并且保存在mActiveResources中

在沒有資源快取的情況下,ActivityThread會新創建一個AssetManager物件,并且呼叫AssetManager物件的addAssetPath函式來將引數resDir作為它的資源目錄,這個resDir就是APK檔案的絕對路徑,創建了一個新的AssetManager物件之后,會將這個AssetManager物件作為Resource建構式的第一個引數來構建一個新的Resources物件,這個新創建的Resources物件會以前面所創建的ResourcesKey物件為鍵值快取在mActiveResources所描述的一個HashMap中,以便重復使用該資源時無需重復創建

接下來,我們首先分析AssetManager類的建構式和成員函式addAssetPath的實作,接著再分析Resources類的建構式的實作,以便可以了解用來訪問應用程式資源的AssetManager物件和Resources物件的創建以及初始化程序,
在這里插入圖片描述
在這里插入圖片描述
在AssetManager的建構式中,首先會呼叫init函式進行初始化然后再呼叫ensureSystemAssets函式來加載系統資源,這些系統資源存盤在mSystem物件中,mSystem也是AssetManager型別,需要注意的是,init函式并不是一個Java函式,而是一個Native層的方法,它的實作在android_util_AssetManager.cpp檔案中,具體代碼如下,
在這里插入圖片描述
在init函式中首先創建了一個Native層的AssetManager物件,然后添加了默認的系統資源,最后將這個AssetManager物件轉換為整型并且傳遞給Java層的AssetManager的mObject欄位,這樣Java層就相當于存有了一個Native層AssetManager的句柄,這里我們關注的是addDefaultAssets函式,具體代碼如下,
在這里插入圖片描述
代碼也比較簡單,就是拼接系統資源路徑,最好將該路徑傳遞給addAssetPath函式中,實際上就是將系統資源路徑添加到AssetManager的資源路徑串列中,
在這里插入圖片描述
在這里插入圖片描述
此時,在ActivityThread的getTopLevelResources函式中的new AssetManager的程序就執行完畢了,然后繼續執行AssetManager物件的addAssetPath函式,具體代碼如下,
在這里插入圖片描述
在Java層的AssetManager的addAssetPath函式中實際上呼叫的是Native層的addAssetPathNative
函式
,需要注意的是,這個path引數必須是一個目錄或者是一個zip壓縮檔案(APK本質上就是1個zip檔案)路徑,這個addAssetPathNative函式在Native層對應的函式為android_content_AssetManager_addAssetPath,該函式定義在android_util_AssetManager.cpp檔案中
在這里插入圖片描述
在android_content_AssetManager_addAssetPath函式中會呼叫assetManagerForJavaObject函式先從Java層的AssetManager物件中獲取到mObject欄位,該欄位存盤了由Native層AssetManager指標轉換而來的整型值,此時需要通過這個整型值逆向地轉換為Native層的AssetManager物件,然后將APK路徑添加到資源路徑中,這樣也就含有了應用本身的資源,

我們再來分析這個程序,首先在Java層的AssetManager建構式中呼叫了init函式初始化系統資源,創建與初始化完畢之后又呼叫了Java層AssetManager物件的addAssetPath函式將應用APK的路徑傳遞給Native層的AssetManager,使得應用的資源也被添加到AssetManager中,這樣資源路徑就構建完畢了

最后在ResourcesManager的getTopLevelResources函式中將初始化好的AssetManager、設備配置等作為引數來構造Java層的Resource物件,也就是上文中getTopLevelResources函式中的注釋4處,Resource的建構式如下,
在這里插入圖片描述
Resources類的建構式首先將引數assets所指向的一個AssetManager物件保存在成員變數mAssets中,我們獲取資源就是通過這個AssetManager物件進行操作,接下來呼叫updateConfiguration函式來設定設備配置資訊,最后呼叫AssetManager的成員函式ensureStringBlocks來創建字串資源池

我們來看看updateConfiguration函式的實作,
在這里插入圖片描述
在這里插入圖片描述
Resources類的成員函式updateConfiguration首先是根據引數config和metrics來更新設備的當前配置資訊,例如,螢屏大小和密碼、國家(地區)和語言、鍵盤配置情況等,接著再呼叫成員變數mAssets所指向的一個Java層的AssetManager物件的成員函式setConfiguration來將這些配置資訊設定到與之關聯的C++層的AssetManager物件中,這樣一來,在我們通過Resource獲取資源時,Native層就會根據這個配置資訊尋找最適合的資源回傳,從而達到多螢屏適配的效果

從上述的分析中我們可以知道,Activity、Contextlmpl、Resource、AssetManager的結構圖如圖23-5所示,在這里插入圖片描述
經過上述的分析,我們也可以得出一個結論:應用的資源存盤在APK中,一個AssetManager可以管理多個路徑的資源檔案,Resource通過AssetManager獲取資源,那就是說我們可以通過AssetManager的addAssetPath方法添加APK路徑以達到一些資源替換或者換膚的效果,還有類似于動態加載一個未安裝的APK時也可以通過這種形式加載插件APK的資源,使得插件Activity等組件可以通過資源類R來訪問應用資源

23.7.2 動態加載框架的實作

在上一節中我們學習了Android中的資源機制,本節我們將資源機制作為基本知識點來實作一個簡單的動態加載框架,在開始之前我們需要知道,未安裝的APK檔案是可以通過DexClassLoader進行加載、運行的,具體代碼如下,
在這里插入圖片描述
其中mContext是一個Context型別的物件,apkPath就是這個APK在手機中的絕對路徑,dexOutputDir就是APK解壓出的dex檔案的存放目錄,通過這種形式就有了一個可以加載這個APK的ClassLoader,然后通過這個ClassLoader加載這個APK默認啟動的Activity,并且通過反射呼叫這個插件Activity的onCreate、onStart、onResume函式就會加載這個應用

我們的實作原理當然沒有上面所說的那么簡單,還需要處理的問題包括加載插件APK的資源、處理它的生命周期函式等,不過在此之前,首先做的還是構建能夠加載插件APK的ClassLoader和資源,我們新創建了一個PluginManager類來完成這些作業,具體代碼如下,
在這里插入圖片描述
在PluginManager類中以插件APK的包名為key,插件APK資訊為值快取了插件相關的資訊,加載插件APK時會從map中檢測是否含有快取,如果有則使用快取的APK,否則通過DexClassLoader和資源創建一個PluginApk物件,最后將這個物件快取到map中,創建PluginApk的代碼是createApk函式,具體代碼如下:
在這里插入圖片描述
在這里插入圖片描述
在createApk函式中首先通過AssetManager物件添加了插件APK所在的絕對路徑,使得AssetManager可以獲取到該插件APK的資源,然后又通過該AssetManager和設備配置等創建了Resource物件,并且將這個物件存盤到PluginApk中,最后構建插件APK的DexClassLoader并存盤在PluginApk物件中,最后這個PluginApk物件會快取到PluginManager的資訊表中

在啟動插件之前,用戶需要呼叫PluginManager的loadApk函式加載插件APK的相關資訊,也就是上述所說的資源、DexClassLoader等,然后再呼叫PluginManager的startActivity函式啟動插件,具體代碼如下,
在這里插入圖片描述
需要注意的是,啟動插件時需要在Intent中傳遞插件APK的包名和默認加載的Activity類名,否則會拋出例外,因為PluginManager需要知道插件APK默認的啟動Activity才能將插件APK運行起來,還有一個重要的地方是,我們在startActivity中會重新構建一個Intent,這個Intent啟動的卻是ActivityProxy這個類,我們是要啟動插件APK的默認Activity,怎么換成了ActivityProxy呢?

實際上我們動態加載的并不是插件APK的Activity,而是一個Activity空殼,也就是這里的ActivityProxy,在這個空殼Activity中包裝了插件Activity的宣告周期,也就說插件Activity和ActivityProxy是共存的,相關代碼如下,
在這里插入圖片描述
在ActivityProxy中有一個LifbCircleControIler,這個類就負責通過DexClassLoader加載插件APK的Activity,我們看到ActivityProxy的宣告周期函式中都呼叫了LifeCircleController物件對應的方法,而LifeCircleController中實際上又呼叫了插件Activity的生命周期方法,我們看看LifeCircleController的實作,
在這里插入圖片描述
在這里插入圖片描述
在 ActivityProxy 的 onCreate 函式中呼叫了 LifeCircleController 的 onCreate 函式,在 LifeCircleControIler的onCreate函式中主要做了如下幾步操作

  1. 獲取插件Activity的類路徑、包名
  2. 通過包名到PluginManager中獲取PluginApk資訊
  3. 通過PluginApk中的DexClassLoader加載插件Activity
  4. 將ActivityProxy物件注入到插件Activity中
  5. 呼叫插件Activity的onCreate函式

在PluginManager的startActivity函式中我們強調過,跳轉到插件Activity的Intent必須包含插件Activity的類路徑、包名就是為了在這一步使用,獲取到類名之后再得到DexClassLoader,最后動態加載插件Activity,并且將ActivityProxy注入到插件Activity中,最后呼叫插件Activity的onCreate函式,

對于插件Activity我們也定義了一個基類,叫做PluginActivity,具體代碼如下,
在這里插入圖片描述
在這里插入圖片描述
從上述程式中可以看到,在PluginActivity中所有的函式幾乎都轉交給了ActivityProxy處理,比如setContentView,實際上也是設定給了ActivityProxy物件,此時我們可以思考一下,我們在開發插件時會繼承自PluginActivity,然后在onCreate函式中設定setContentView為插件Activity設定布局內容,而PluginActivity呼叫的卻是ActivityProxy的setContentView函式,就相當于是為ActivityProxy物件設定內容布局當我們要啟動插件Actiivty時實際上又是啟動ActivityProxy,又在ActivityProxy的onCreate函式中通過LifeCircleController動態地用DexClassLoader加載插件Activity,并且呼叫它的onCreate函式、setContentView方法等,而setContentView實際上呼叫了ActivityProxy的setContentView,在呼叫 ActivityProxy的onStart、onResume之后ActivityProxy就顯示到我們面前了,它的視圖就是通過插件Activity設定的內容

一句話概括就是:ActivityProxy加載的是插件Activity的內容與資源,這樣一來,插件Activity就會以ActivityProxy的形式展現出來,

例如,我們有一個插件APK在SD卡的plugins目錄下,它的包名為com.example.plugin,默認啟動的Activity類路徑為com.example.plugin.MainActivity,首先需要一個宿主應用來加載插件應用,宿主應用中要加載插件APK時代碼呼叫如下,
在這里插入圖片描述
在這里插入圖片描述
我們再創建一個插件工程,這個工程需要參考動態加載框架的代碼,然后新創建一個繼承自PluginActivity的插件Activity,具體代碼如下,
在這里插入圖片描述
生成APK之后將該插件APK命名為plugin.apk,放到SD卡的plugins目錄下,然后運行宿主APK,而在宿主APK中又會加載plugin.apk,此時得到的效果如圖23-6和圖23-7所示,
在這里插入圖片描述
在宿主中,我們通過PluginManager啟動了插件APK,但是,實際上啟動是ActivityProxy,只是我們將插件Activity的contentview設定給了ActivityProxy,這樣看起來像是啟動了插件Activity,

在這個程序中最終的兩點就是contentview的設定和資源的替換,當然這只是一個最基本的實作,通過這個示例只是讓大家對于動態加載有一個基本的認識,更完善的動態加載框架請查看https://github.com/singwhatiwanna/dynamic-load-apk,也希望更多的人參與到這個開源專案來,

23.8 外觀模式實戰

對于SDK和開源庫來說,外觀模式通常是使用率最高的模式,這些庫通過外觀類為用戶提供統一的高層介面,使得用戶不必了解一些更細節的實作,例如在使用ImageLoader時我們通常只操作一個ImageLoader類就可以完成,而不要了解網路請求類、快取類以及它們的互動細節,又比如在使用友盟統計SDK時,我們基本上通過MobclickAgent這個類就可以完成我們所需的功能,至于MobclickAgent的內部有其他的什么型別、它們的具體互動是什么我們都不需要關心,這個MobclickAgent類也就是一個外觀類,可見,外觀模式實際上在我們的開發中無處不在,今天我們還是以小民的ImageLoader(類名為SimplelmageLoader,簡稱ImageLoader)為例來講述外觀模式的運用,

在本書的前面章節中我們講到,用戶在使用ImageLoader時只需要操作ImageLoader類,其他的型別基本不用關心,ImageLoader封裝了內部邏輯,通過ImageLoader的介面用戶就可以完成圖片加載的操作,核心代碼如下,
在這里插入圖片描述
在ImageLoader類中對外暴露的函式基本只有displayImage,也就是加載圖片的函式,最簡單的情況下,用戶只需要傳遞ImageView和圖片的uri即可實作圖片加載,在該函式中,首先會將傳遞進來的引數轉換為一個BitmapRequest物件,然后將該物件傳遞到請求佇列中,請求佇列的初始化就在ImageLoader的初始化函式中,該請求佇列初始化之后就會啟動CPU數量+1個的請求處理執行緒,在這些執行緒的run中不斷地從請求佇列中獲取請求、加載請求、最后將圖片投遞到UI執行緒更新ImageView,首先我們來看訊息佇列的初始化,
在這里插入圖片描述
在RequestQueue中會啟動指定數量的RequestDispatcher執行緒,每個RequestDispatcher本質上是一個執行緒,在它們的run函式中有一個死回圈不斷地查詢請求佇列中是否含有請求,并且處理這些請求,
在這里插入圖片描述
在RequestDispatcher的run方法中主要分為4步,

  1. 從訊息佇列中獲取訊息
  2. 根據請求的uri地址獲取圖片的uri schema
  3. 根據schema獲取對應的圖片Loader
  4. 加載圖片,并且將結果傳遞給UI執行緒,更新ImageView

從上述代碼看,RequestDispatcher中又封裝了LoaderManager系統,而在LoaderManager中又管理了各個Loader類,而Loader類中又管理了圖片的加載流程、圖片快取等邏輯,而這些相關的型別、邏輯都被封裝在ImageLoader類的外衣之下,對于用戶來說它們根本不知道這些型別的存在,幾乎所有的操作都是通過SimplelmageLoader這個類完成,它們的結構如圖23-8所示,
在這里插入圖片描述
從圖23-8中我們可以看到, ReuqestQueue等型別都被封裝在了SimplelmageLoader類之下,用戶根本不知道這些型別的存在,而只需要操作SimplelmageLoader的介面就可以完成圖片加載的操作,這樣就避免了暴露過多的實作細節,也使得ImageLoader的使用更為簡單,試想一下,每次都需要手動構建一個BitmapRequest類,然后添加到請求佇列中,那么整個程序就增加了用戶的使用成本,用戶也直接依賴了具體的實作,這對于系統的升級、維護造成了一定的困難,通過SimplelmageLoder類將這些具體細節都隱藏起來,在降低用戶使用成本的同時也增加了靈活性,例如,當你需要替換RequestDispatcher為執行緒池實作時,用戶并不能感知到,因為原來的用戶并不知道內部實作有RequestDispatcher這個型別,這就能很好地在不對用戶產生影響的情況下對產品進行升級、維護,

23.9 小結

外觀模式是一個使用頻率較高的設計模式,它的精髓就在于“封裝”二字,通過一個高層次結構為用戶提供統一的API入口,使得用戶通過一個型別就基本能夠操作整個系統,這樣減少了用戶的使用成本,也能夠提升系統的靈活性,

優點

  • 對客戶程式隱藏子系統細節,因而減少了客戶對于子系統的耦合,能夠擁抱變化,
  • 外觀類對子系統的介面封裝,使得系統更易于使用,

缺點

  • 外觀類介面膨脹,由于子系統的介面都有外觀類統一對外暴露,使得外觀類的API介面較多,在一定程度上增加了用戶使用成本,
  • 外觀類沒有遵循開閉原則,當業務出現變更時,可能需要直接修改外觀類,

轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/244697.html

標籤:其他

上一篇:windows安裝雙系統的問題記錄

下一篇:基于android的音樂APP大作業和設計指導專案

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【從零開始擼一個App】Dagger2

    Dagger2是一個IOC框架,一般用于Android平臺,第一次接觸的朋友,一定會被搞得暈頭轉向。它延續了Java平臺Spring框架代碼碎片化,注解滿天飛的傳統。嘗試將各處代碼片段串聯起來,理清思緒,真不是件容易的事。更不用說還有各版本細微的差別。 與Spring不同的是,Spring是通過反射 ......

    uj5u.com 2020-09-10 06:57:59 more
  • Flutter Weekly Issue 66

    新聞 Flutter 季度調研結果分享 教程 Flutter+FaaS一體化任務編排的思考與設計 詳解Dart中如何通過注解生成代碼 GitHub 用對了嗎?Flutter 團隊分享如何管理大型開源專案 插件 flutter-bubble-tab-indicator A Flutter librar ......

    uj5u.com 2020-09-10 06:58:52 more
  • Proguard 常用規則

    介紹 Proguard 入口,如何查看輸出,如何使用 keep 設定入口以及使用實體,如何配置壓縮,混淆,校驗等規則。

    ......

    uj5u.com 2020-09-10 06:59:00 more
  • Android 開發技術周報 Issue#292

    新聞 Android即將獲得類AirDrop功能:可向附近設備快速分享檔案 谷歌為安卓檔案管理應用引入可安全隱藏資料的Safe Folder功能 Android TV新主界面將顯示電影、電視節目和應用推薦內容 泄露的Android檔案暗示了傳說中的谷歌Pixel 5a與折疊屏新機 谷歌發布Andro ......

    uj5u.com 2020-09-10 07:00:37 more
  • AutoFitTextureView Error inflating class

    報錯: Binary XML file line #0: Binary XML file line #0: Error inflating class xxx.AutoFitTextureView 解決: <com.example.testy2.AutoFitTextureView android: ......

    uj5u.com 2020-09-10 07:00:41 more
  • 根據Uri,Cursor沒有獲取到對應的屬性

    Android: 背景:呼叫攝像頭,拍攝視頻,指定保存的地址,但是回傳的Cursor檔案,只有名稱和大小的屬性,沒有其他諸如時長,連ID屬性都沒有 使用 cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATIO ......

    uj5u.com 2020-09-10 07:00:44 more
  • Android連載29-持久化技術

    一、持久化技術 我們平時所使用的APP產生的資料,在記憶體中都是瞬時的,會隨著斷電、關機等丟失資料,因此android系統采用了持久化技術,用于存盤這些“瞬時”資料 持久化技術包括:檔案存盤、SharedPreference存盤以及資料庫存盤,還有更復雜的SD卡記憶體儲。 二、檔案存盤 最基本存盤方式, ......

    uj5u.com 2020-09-10 07:00:47 more
  • Android Camera2Video整合到自己專案里

    背景: Android專案里呼叫攝像頭拍攝視頻,原本使用的 MediaStore.ACTION_VIDEO_CAPTURE, 后來因專案需要,改成了camera2 1.Camera2Video 官方demo有點問題,下載后,不能直接整合到專案 問題1.多次拍攝視頻崩潰 問題2.雙擊record按鈕, ......

    uj5u.com 2020-09-10 07:00:50 more
  • Android 開發技術周報 Issue#293

    新聞 谷歌為Android TV開發者提供多種新功能 Android 11將自動填表功能整合到鍵盤輸入建議中 谷歌宣布Android Auto即將支持更多的導航和數字停車應用 谷歌Pixel 5只有XL版本 搭載驍龍765G且將比Pixel 4更便宜 [圖]Wear OS將迎來重磅更新:應用啟動時間 ......

    uj5u.com 2020-09-10 07:01:38 more
  • 海豚星空掃碼投屏 Android 接收端 SDK 集成 六步驟

    掃碼投屏,開放網路,獨占設備,不需要額外下載軟體,微信掃碼,發現設備。支持標準DLNA協議,支持倍速播放。視頻,音頻,圖片投屏。好點意思。還支持自定義基于 DLNA 擴展的操作動作。好像要收費,沒體驗。 這里簡單記錄一下集成程序。 一 跟目錄的build.gradle添加私有mevan倉庫 mave ......

    uj5u.com 2020-09-10 07:01:43 more
最新发布
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:40:31 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:40:11 more
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:39:36 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:39:13 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:16:23 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:16:15 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:15:46 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:14:53 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:14:08 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:08:34 more