Android ImageLoader 框架之初始配置與請求排程
前言
在Android ImageLoader框架之基本架構中我們對SimpleImageLoader框架進行了基本的介紹,今天我們就從原始碼的角度來剖析ImageLoader的設計與實現。
在我們使用ImageLoader前都會通過一個配置類來設定一些基本的東西,比如載入中的圖片、載入失敗的圖片、快取策略等等,SimpleImageLoader的設計也是如此。配置類這個比較簡單,我們直接看原始碼吧。
ImageLoaderConfig配置
/** * ImageLoader配置類, * * @author mrsimple */ public class ImageLoaderConfig { /** * 圖片快取配置物件 */ public BitmapCache bitmapCache = new MemoryCache(); /** * 載入圖片時的loading和載入失敗的圖片配置物件 */ public DisplayConfig displayConfig = new DisplayConfig(); /** * 載入策略 */ public LoadPolicy loadPolicy = new SerialPolicy(); /** * */ public int threadCount = Runtime.getRuntime().availableProcessors() + 1; /** * @param count * @return */ public ImageLoaderConfig setThreadCount(int count) { threadCount = Math.max(1, count); return this; } public ImageLoaderConfig setCache(BitmapCache cache) { bitmapCache = cache; return this; } public ImageLoaderConfig setLoadingPlaceholder(int resId) { displayConfig.loadingResId = resId; return this; } public ImageLoaderConfig setNotFoundPlaceholder(int resId) { displayConfig.failedResId = resId; return this; } public ImageLoaderConfig setLoadPolicy(LoadPolicy policy) { if (policy != null) { loadPolicy = policy; } return this; } }
都是很簡單的setter函式,但是不太一樣的是這些setter都是返回一個ImageLoaderConfig物件的,在這裡也就是返回了自身。這個設計是類似Builder模式的,便於使用者的鏈式呼叫,例如:
private void initImageLoader() { ImageLoaderConfig config = new ImageLoaderConfig() .setLoadingPlaceholder(R.drawable.loading) .setNotFoundPlaceholder(R.drawable.not_found) .setCache(new DoubleCache(this)) .setThreadCount(4) .setLoadPolicy(new ReversePolicy()); // 初始化 SimpleImageLoader.getInstance().init(config); }
對於Builder模式不太瞭解的同學可以參考 Android原始碼分析之Builder模式。構建好配置物件之後我們就可以通過這個配置物件來初始化SimpleImageLoader了。SimpleImageLoader會根據配置物件來初始化一些內部策略,例如快取策略、執行緒數量等。呼叫init方法後整個ImageLoader就正式啟動了。
SimpleImageLoader的實現
SimpleImageLoader這個類的職責只是作為使用者入口,它的工作其實並沒有那麼多,只是一個門童罷了。我們看看它的原始碼吧。
/** * 圖片載入類,支援url和本地圖片的uri形式載入.根據圖片路徑格式來判斷是網路圖片還是本地圖片,如果是網路圖片則交給SimpleNet框架來載入, * 如果是本地圖片那麼則交給mExecutorService從sd卡中載入 * .載入之後直接更新UI,無需使用者干預.如果使用者設定了快取策略,那麼會將載入到的圖片快取起來.使用者也可以設定載入策略,例如順序載入{@see * SerialPolicy}和逆向載入{@see ReversePolicy}. * * @author mrsimple */ public final class SimpleImageLoader { /** SimpleImageLoader例項 */ private static SimpleImageLoader sInstance; /** 網路請求佇列 */ private RequestQueue mImageQueue; /** 快取 */ private volatile BitmapCache mCache = new MemoryCache(); /** 圖片載入配置物件 */ private ImageLoaderConfig mConfig; private SimpleImageLoader() { } /** * 獲取ImageLoader單例 * * @return */ public static SimpleImageLoader getInstance() { if (sInstance == null) { synchronized (SimpleImageLoader.class) { if (sInstance == null) { sInstance = new SimpleImageLoader(); } } } return sInstance; } /** * 初始化ImageLoader,啟動請求佇列 * @param config 配置物件 */ public void init(ImageLoaderConfig config) { mConfig = config; mCache = mConfig.bitmapCache; checkConfig(); mImageQueue = new RequestQueue(mConfig.threadCount); mImageQueue.start(); } private void checkConfig() { if (mConfig == null) { throw new RuntimeException( "The config of SimpleImageLoader is Null, please call the init(ImageLoaderConfig config) method to initialize"); } if (mConfig.loadPolicy == null) { mConfig.loadPolicy = new SerialPolicy(); } if (mCache == null) { mCache = new NoCache(); } } public void displayImage(ImageView imageView, String uri) { displayImage(imageView, uri, null, null); } public void displayImage(final ImageView imageView, final String uri, final DisplayConfig config, final ImageListener listener) { BitmapRequest request = new BitmapRequest(imageView, uri, config, listener); // 載入的配置物件,如果沒有設定則使用ImageLoader的配置 request.displayConfig = request.displayConfig != null ? request.displayConfig : mConfig.displayConfig; // 新增對佇列中 mImageQueue.addRequest(request); } // 程式碼省略... /** * 圖片載入Listener * * @author mrsimple */ public static interface ImageListener { public void onComplete(ImageView imageView, Bitmap bitmap, String uri); } }
從上述程式碼中我們可以看到SimpleImageLoader的工作比較少,也比較簡單。它就是根據使用者傳遞進來的配置來初始化ImageLoader,並且作為圖片載入入口,使用者呼叫displayImage之後它會將這個呼叫封裝成一個BitmapRequest請求,然後將該請求新增到請求佇列中。
BitmapRequest圖片載入請求
BitmapRequest只是一個儲存了ImageView、圖片uri、DisplayConfig以及ImageListener的一個物件,封裝這個物件的目的在載入圖片時減少引數的個數,***在BitmapRequest的建構函式中我們會將圖片uri設定為ImageView的tag,這樣做的目的是防止圖片錯位顯示。***BitmapRequest類實現了Compare介面,請求佇列會根據它的序列號進行排序,排序策略使用者也可以通過配置類來設定,具體細節在載入策略的章節我們再聊吧。
public BitmapRequest(ImageView imageView, String uri, DisplayConfig config, ImageListener listener) { mImageViewRef = new WeakReference<ImageView>(imageView); displayConfig = config; imageListener = listener; imageUri = uri; // 設定ImageView的tag為圖片的uri imageView.setTag(uri); imageUriMd5 = Md5Helper.toMD5(imageUri); }
RequestQueue圖片請求佇列
請求佇列我們採用了SImpleNet中一樣的模式,通過封裝一個優先順序佇列來維持圖片載入佇列,mSerialNumGenerator會給每一個請求分配一個序列號,PriorityBlockingQueue會根據BitmapRequest的compare策略來決定BitmapRequest的順序。RequestQueue內部會啟動使用者指定數量的執行緒來從請求佇列中讀取請求,分發執行緒不斷地從佇列中讀取請求,然後進行圖片載入處理,這樣ImageLoader就happy起來了。
/** * 請求佇列, 使用優先佇列,使得請求可以按照優先順序進行處理. [ Thread Safe ] * * @author mrsimple */ public final class RequestQueue { /** * 請求佇列 [ Thread-safe ] */ private BlockingQueue<BitmapRequest> mRequestQueue = new PriorityBlockingQueue<BitmapRequest>(); /** * 請求的序列化生成器 */ private AtomicInteger mSerialNumGenerator = new AtomicInteger(0); /** * 預設的核心數 */ public static int DEFAULT_CORE_NUMS = Runtime.getRuntime().availableProcessors() + 1; /** * CPU核心數 + 1個分發執行緒數 */ private int mDispatcherNums = DEFAULT_CORE_NUMS; /** * NetworkExecutor,執行網路請求的執行緒 */ private RequestDispatcher[] mDispatchers = null; /** * */ protected RequestQueue() { this(DEFAULT_CORE_NUMS); } /** * @param coreNums 執行緒核心數 * @param httpStack http執行器 */ protected RequestQueue(int coreNums) { mDispatcherNums = coreNums; } /** * 啟動RequestDispatcher */ private final void startDispatchers() { mDispatchers = new RequestDispatcher[mDispatcherNums]; for (int i = 0; i < mDispatcherNums; i++) { mDispatchers[i] = new RequestDispatcher(mRequestQueue); mDispatchers[i].start(); } } public void start() { stop(); startDispatchers(); } /** * 停止RequestDispatcher */ public void stop() { if (mDispatchers != null && mDispatchers.length > 0) { for (int i = 0; i < mDispatchers.length; i++) { mDispatchers[i].interrupt(); } } } /** * 不能重複新增請求 * @param request */ public void addRequest(BitmapRequest request) { if (!mRequestQueue.contains(request)) { request.serialNum = this.generateSerialNumber(); mRequestQueue.add(request); } else { Log.d("", "### 請求佇列中已經含有"); } } private int generateSerialNumber() { return mSerialNumGenerator.incrementAndGet(); } }
RequestDispatcher請求分發
請求Dispatcher,繼承自Thread,從網路請求佇列中迴圈讀取請求並且執行,也比較簡單,直接上原始碼吧。
final class RequestDispatcher extends Thread { /** * 網路請求佇列 */ private BlockingQueue<BitmapRequest> mRequestQueue; /** * @param queue 圖片載入請求佇列 */ public RequestDispatcher(BlockingQueue<BitmapRequest> queue) { mRequestQueue = queue; } @Override public void run() { try { while (!this.isInterrupted()) { final BitmapRequest request = mRequestQueue.take(); if (request.isCancel) { continue; } // 解析圖片schema final String schema = parseSchema(request.imageUri); // 根據schema獲取對應的Loader Loader imageLoader = LoaderManager.getInstance().getLoader(schema); // 載入圖片 imageLoader.loadImage(request); } } catch (InterruptedException e) { Log.i("", "### 請求分發器退出"); } } /** * 這裡是解析圖片uri的格式,uri格式為: schema:// + 圖片路徑。 */ private String parseSchema(String uri) { if (uri.contains("://")) { return uri.split("://")[0]; } else { Log.e(getName(), "### wrong scheme, image uri is : " + uri); } return ""; } }
第一個重點就是run函式了,不斷地從佇列中獲取請求,然後解析到圖片uri的schema,從schema的格式就可以知道它是儲存在哪裡的圖片。例如網路圖片物件的schema是http或者https,sd卡儲存的圖片對應的schema為file。根據schema我們從LoaderManager中獲取對應的Loader來載入圖片,這個設計保證了SimpleImageLoader可載入圖片型別的可擴充套件性,這就是為什麼會增加loader這個包的原因。使用者只需要根據uri的格式來構造圖片uri,並且實現自己的Loader類,然後將Loader物件注入到LoaderManager即可,後續我們會再詳細說明。
這裡的另一個重點是parseSchema函式,它的職責是解析圖片uri的格式,uri格式為: schema:// + 圖片路徑,例如網路圖片的格式為http://xxx.image.jpg,而本地圖片的uri為file:///sdcard/xxx/image.jpg。如果你要實現自己的Loader來載入特定的格式,那麼它的uri格式必須以schema://開頭,否則解析會錯誤,例如可以為drawable://image,然後你註冊一個schema為”drawable”的Loader到LoaderManager中,SimpleImageLoader在載入圖片時就會使用你註冊的Loader來載入圖片,這樣就可以應對使用者的多種多樣的需求。如果不能擁抱變化那就不能稱之為框架了,應該叫功能模組。
本章總結
最後我們來整理一下這個過程吧,SimpleImageLoader根據使用者的配置來配置、啟動請求佇列,請求佇列又會根據使用者配置的執行緒數量來啟動幾個分發執行緒。這幾個執行緒不斷地從請求佇列(執行緒安全)中讀取圖片載入請求,並且執行圖片載入請求。這些請求是使用者通過呼叫SimpleImageLoader的displayImage函式而產生的,內部會把這個呼叫封裝成一個BitmapRequest物件,並且將該物件新增到請求佇列中。這樣就有了生產者(使用者)和消費者(分發執行緒),整個SimpleImageLoader就隨著CPU跳動而熱血沸騰起來了!
Github倉庫連結
相關文章
- Android ImageLoader框架之基本架構Android框架架構
- Android ImageLoader框架之圖片載入與載入策略Android框架
- Android ImageLoader 框架之圖片快取Android框架快取
- Android Http請求框架二:xUtils 框架網路請求AndroidHTTP框架
- Android 擴充套件 OkHttp 支援請求優先順序排程Android套件HTTP
- Android Http請求框架一:Get 和 Post 請求AndroidHTTP框架
- Android網路請求(終) 網路請求框架RetrofitAndroid框架
- Android網路請求(3) 網路請求框架OkHttpAndroid框架HTTP
- Android程式框架:程式的建立、啟動與排程流程Android框架
- Android 網路框架之OKhttp實現https請求Android框架HTTP
- Android網路請求(4) 網路請求框架VolleyAndroid框架
- Android okHttp網路請求之Get/Post請求AndroidHTTP
- Android系統“資源排程框架”Android框架
- 請求框架&移動請求框架
- Django框架之csrf跨站請求Django框架
- Android開源框架ImageLoader的完美例子Android框架
- DRF之請求與響應
- OkHttp 知識梳理(2) OkHttp 原始碼解析之非同步請求 & 執行緒排程HTTP原始碼非同步執行緒
- Go runtime 排程器精講(二):排程器初始化Go
- Web請求過程Web
- Android探索之HttpURLConnection網路請求AndroidHTTP
- 前端效能優化之http請求的過程前端優化HTTP
- Spring框架系列(14) - SpringMVC實現原理之DispatcherServlet處理請求的過程框架SpringMVCServlet
- k8s排程器介紹(排程框架版本)K8S框架
- Android Http請求AndroidHTTP
- HTTP的請求過程HTTP
- Android後臺排程任務與省電Android
- Android RxJava2+Retrofit2搭建網路請求框架AndroidRxJava框架
- Celery非同步排程框架(二)與Django結合使用非同步框架Django
- Android okHttp網路請求之Json解析AndroidHTTPJSON
- 【Android】開源專案UniversalImageLoader及開源框架ImageLoaderAndroid框架
- 網路請求框架之Retrofit2.0基本講解框架
- Go語言goroutine排程器初始化Go
- MySQL 配置InnoDB清理排程MySql
- 網路請求優化之取消請求優化
- SpringBoot整合任務排程框架Quartz及持久化配置Spring Boot框架quartz持久化
- python 排程框架 apschedulerPython框架
- 使用lua-nginx模組實現請求解析與排程Nginx