XUtils 源码分析(二)--图片缓存模块
发布日期:2021-10-04 02:53:45
浏览次数:8
分类:技术文章
本文共 52001 字,大约阅读时间需要 173 分钟。
一 XUtils图片缓存模块源码分析
1.BitmapUtils对象创建
bitmapUtils = new BitmapUtils(appContext);public BitmapUtils(Context context) { this(context, null);}public BitmapUtils(Context context, String diskCachePath) { if (context == null) { throw new IllegalArgumentException("context may not be null"); } this.context = context.getApplicationContext(); globalConfig = BitmapGlobalConfig.getInstance(this.context, diskCachePath); defaultDisplayConfig = new BitmapDisplayConfig();}public BitmapUtils(Context context, String diskCachePath, int memoryCacheSize) { this(context, diskCachePath); globalConfig.setMemoryCacheSize(memoryCacheSize);//内存缓存大小}public BitmapUtils(Context context, String diskCachePath, int memoryCacheSize, int diskCacheSize) { this(context, diskCachePath); globalConfig.setMemoryCacheSize(memoryCacheSize); globalConfig.setDiskCacheSize(diskCacheSize);//磁盘缓存大小}public BitmapUtils(Context context, String diskCachePath, float memoryCachePercent) { this(context, diskCachePath); globalConfig.setMemCacheSizePercent(memoryCachePercent);}public BitmapUtils(Context context, String diskCachePath, float memoryCachePercent, int diskCacheSize) { this(context, diskCachePath); globalConfig.setMemCacheSizePercent(memoryCachePercent); globalConfig.setDiskCacheSize(diskCacheSize);}
1.1 获取BitmapGlobalConfig对象BitmapGlobalConfig.getInstance(this.context, diskCachePath);
public synchronized static BitmapGlobalConfig getInstance(Context context, String diskCachePath) { if (TextUtils.isEmpty(diskCachePath)) { diskCachePath = OtherUtils.getDiskCacheDir(context, "xBitmapCache");//缓存路径 } if (configMap.containsKey(diskCachePath)) {//有缓存的对象 return configMap.get(diskCachePath); } else { BitmapGlobalConfig config = new BitmapGlobalConfig(context, diskCachePath); configMap.put(diskCachePath, config); return config; }}/** * @param context * @param dirName Only the folder name, not full path. * @return app_cache_path/dirName * 1.检查外部存储如果挂载去获取外部缓存路径 * 2.如果获取外部缓存路径为空,获取内部缓存路径 */public static String getDiskCacheDir(Context context, String dirName) { String cachePath = null; if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {//外部存储挂载 File externalCacheDir = context.getExternalCacheDir(); if (externalCacheDir != null) { cachePath = externalCacheDir.getPath(); } } if (cachePath == null) { File cacheDir = context.getCacheDir(); if (cacheDir != null && cacheDir.exists()) { cachePath = cacheDir.getPath(); } } return cachePath + File.separator + dirName;}/** * @param context * @param diskCachePath If null, use default appCacheDir+"/xBitmapCache" * BitmapGlobalConfig构造函数 */private BitmapGlobalConfig(Context context, String diskCachePath) { if (context == null) throw new IllegalArgumentException("context may not be null"); this.mContext = context; this.diskCachePath = diskCachePath; initBitmapCache();}
1.1.1 初始化磁盘与内存缓存initBitmapCache();
/** * 初始化内存缓存与磁盘缓存 */private void initBitmapCache() { new BitmapCacheManagementTask().execute(BitmapCacheManagementTask .MESSAGE_INIT_MEMORY_CACHE); new BitmapCacheManagementTask().execute(BitmapCacheManagementTask.MESSAGE_INIT_DISK_CACHE);} @Override protected Object[] doInBackground(Object... params) { if (params == null || params.length == 0) return params; BitmapCache cache = getBitmapCache(); if (cache == null) return params; try { switch ((Integer) params[0]) { case MESSAGE_INIT_MEMORY_CACHE: cache.initMemoryCache();//初始化内存缓存 break; case MESSAGE_INIT_DISK_CACHE: cache.initDiskCache();//初始化磁盘缓存 break; case MESSAGE_FLUSH: cache.flush(); break; case MESSAGE_CLOSE: cache.clearMemoryCache(); cache.close(); break; case MESSAGE_CLEAR: cache.clearCache(); break; case MESSAGE_CLEAR_MEMORY: cache.clearMemoryCache(); break; case MESSAGE_CLEAR_DISK: cache.clearDiskCache(); break; case MESSAGE_CLEAR_BY_KEY: if (params.length != 2) return params; cache.clearCache(String.valueOf(params[1])); break; case MESSAGE_CLEAR_MEMORY_BY_KEY: if (params.length != 2) return params; cache.clearMemoryCache(String.valueOf(params[1])); break; case MESSAGE_CLEAR_DISK_BY_KEY: if (params.length != 2) return params; cache.clearDiskCache(String.valueOf(params[1])); break; default: break; } } catch (Throwable e) { LogUtils.e(e.getMessage(), e); } return params; }
1.1.1.1 初始化内存缓存 initMemoryCache();
/** * Initialize the memory cache * 1.判断是否打开内存缓存,未打开就返回 * 2.如果LruMemoryCache对象不为空就清空缓存 * 3.LruMemoryCache对象为空就去新建对象覆写方法返回每个缓存单元的大小 */public void initMemoryCache() { if (!globalConfig.isMemoryCacheEnabled()) return; // Set up memory cache if (mMemoryCache != null) { try { clearMemoryCache(); } catch (Throwable e) { } } mMemoryCache = new LruMemoryCache(globalConfig.getMemoryCacheSize ()) { /** * Measure item size in bytes rather than units which is more practical * for a bitmap cache */ @Override protected int sizeOf(MemoryCacheKey key, Bitmap bitmap) { if (bitmap == null) return 0; return bitmap.getRowBytes() * bitmap.getHeight(); } };}
1.1.1.2 初始化磁盘缓存 initDiskCache() ;
/** * Initializes the disk cache. Note that this includes disk access so this should not be * executed on the main/UI thread. By default an ImageCache does not initialize the disk * cache when it is created, instead you should call initDiskCache() to initialize it on a * background thread. * 初始化磁盘缓存 * 1.获取缓存路径生成文件目录,确定可缓存磁盘占用空间大小 * 2.根据条件open相应LruDiskCache对象 * 3.设置文件名设置器 */public void initDiskCache() { // Set up disk cache synchronized (mDiskCacheLock) { if (globalConfig.isDiskCacheEnabled() && (mDiskLruCache == null || mDiskLruCache .isClosed())) { File diskCacheDir = new File(globalConfig.getDiskCachePath()); if (diskCacheDir.exists() || diskCacheDir.mkdirs()) { long availableSpace = OtherUtils.getAvailableSpace(diskCacheDir);//目录总可用空间 long diskCacheSize = globalConfig.getDiskCacheSize();//磁盘缓存大小 diskCacheSize = availableSpace > diskCacheSize ? diskCacheSize : availableSpace; try { //第一个参数指定的是数据的缓存地址,第二个参数指定当前应用程序的版本号,第三个参数指定同一个key可以对应多少个缓存文件,基本都是传1 // ,第四个参数指定最多可以缓存多少字节的数据。 mDiskLruCache = LruDiskCache.open(diskCacheDir, 1, 1, diskCacheSize); mDiskLruCache.setFileNameGenerator(globalConfig.getFileNameGenerator()); LogUtils.d("create disk cache success"); } catch (Throwable e) { mDiskLruCache = null; LogUtils.e("create disk cache error", e); } } } }}/** * OthreUtils.java * 返回文件对应目录下可用空间 * */public static long getAvailableSpace(File dir) { try { final StatFs stats = new StatFs(dir.getPath()); return (long) stats.getBlockSize() * (long) stats.getAvailableBlocks(); } catch (Throwable e) { LogUtils.e(e.getMessage(), e); return -1; }}
1.1.1.2.1 新建LruDiskCache对象LruDiskCache.open(diskCacheDir, 1, 1, diskCacheSize);
/** * Opens the cache in {@code directory}, creating a cache if none exists * there. * * @param directory a writable directory * @param valueCount the number of values per cache entry. Must be positive. * @param maxSize the maximum number of bytes this cache should use to store * @throws IOException if reading or writing the cache directory fails * * LruDiskCache.java * 1.判断传入参数是否合理,不合理抛异常。 * 2.如果日志文件存在,读取日志内容生成缓存实体并设置,计算缓存文件总大小。生成日志文件输入流 * 3.如果日志文件不存在,新建文件 * 4.返回LruDiskCache对象 */public static LruDiskCache open(File directory, int appVersion, int valueCount, long maxSize) throws IOException { if (maxSize <= 0) { throw new IllegalArgumentException("maxSize <= 0"); } if (valueCount <= 0) { throw new IllegalArgumentException("valueCount <= 0"); } // If a bkp file exists, use it instead. File backupFile = new File(directory, JOURNAL_FILE_BACKUP); if (backupFile.exists()) {//日志备份文件存在 File journalFile = new File(directory, JOURNAL_FILE); // If journal file also exists just delete backup file. if (journalFile.exists()) {//源文件也存在,删除备份 backupFile.delete(); } else { renameTo(backupFile, journalFile, false); } } // Prefer to pick up where we left off. LruDiskCache cache = new LruDiskCache(directory, appVersion, valueCount, maxSize); if (cache.journalFile.exists()) {//日志文件存在 try { cache.readJournal();//读取日志 cache.processJournal();//计算总大小 cache.journalWriter = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(cache.journalFile, true), HTTP.US_ASCII));//写文件流 return cache; } catch (Throwable journalIsCorrupt) { LogUtils.e("DiskLruCache " + directory + " is corrupt: " + journalIsCorrupt .getMessage() + ", removing", journalIsCorrupt); cache.delete(); } } // Create a new empty cache. //重建一个日志文件。 if (directory.exists() || directory.mkdirs()) { cache = new LruDiskCache(directory, appVersion, valueCount, maxSize); cache.rebuildJournal(); } return cache;}
1.1.1.2.1.1 读取日志文件cache.readJournal();
/** * 读取日志文件,设置缓存实体必要信息。 * 执行步骤: * 1.读取日志头部,判断是否合理。 * 2.循环读取日志内容。 */private void readJournal() throws IOException { StrictLineReader reader = null; try { reader = new StrictLineReader(new FileInputStream(journalFile)); String magic = reader.readLine();//第一行是个固定的字符串“libcore.io // .DiskLruCache”,标志着我们使用的是DiskLruCache技术。 String version = reader.readLine();//第二行是DiskLruCache的版本号,这个值是恒为1的. String appVersionString = reader.readLine();//第三行是应用程序的版本号,我们在open() // 方法里传入的版本号是什么这里就会显示什么。 String valueCountString = reader.readLine();//第四行是valueCount,这个值也是在open() // 方法中传入的,通常情况下都为1。 String blank = reader.readLine();//第五行是一个空行。 if (!MAGIC.equals(magic) || !VERSION.equals(version) || !Integer.toString(appVersion) .equals(appVersionString) || !Integer.toString(valueCount).equals (valueCountString) || !"".equals(blank)) { throw new IOException("unexpected journal header: [" + magic + ", " + "" + version + ", " + valueCountString + ", " + blank + "]"); } int lineCount = 0; while (true) {//死循环读取日志文件 try { readJournalLine(reader.readLine()); lineCount++; } catch (EOFException endOfJournal) { break; } } redundantOpCount = lineCount - lruEntries.size(); } finally { IOUtils.closeQuietly(reader); }} /** * Reads the next line. A line ends with {@code "\n"} or {@code "\r\n"}, * this end of line marker is not included in the result. * * @return the next line from the input. * @throws IOException for underlying {@code InputStream} errors. * @throws EOFException for the end of source stream. * StrictLineReader.java * 读取日志文件 * 1.读取文件输入到字节数组,初始化位置变量 * 2.对字节数组逐个遍历,遇到换行符构造字符串返回并且更新位置变量 * 3.如果字符数组不存在换行符,新建ByteArrayOutputStream假设已读出80字节,覆写toString * 4.死循环内循环判断遇到换行符ByteArrayOutputStream写出到字节缓存,更新位置变量,返回toString */ public String readLine() throws IOException { synchronized (in) { if (buf == null) { throw new IOException("LineReader is closed"); } // Read more data if we are at the end of the buffered data. // Though it's an error to read after an exception, we will let {@code fillBuf()} // throw again if that happens; thus we need to handle end == -1 as well as end // == pos. //初始化pos与end变量 if (pos >= end) { fillBuf(); } // Try to find LF in the buffered data and return the line if successful. for (int i = pos; i != end; ++i) { if (buf[i] == LF) {//新行 int lineEnd = (i != pos && buf[i - 1] == CR) ? i - 1 : i;//lineEnd之后为换行 String res = new String(buf, pos, lineEnd - pos, charset.name()); pos = i + 1;//重置起始位 return res; } } // Let's anticipate up to 80 characters on top of those already read. //假定80个字符已经读出 ByteArrayOutputStream out = new ByteArrayOutputStream(end - pos + 80) { @Override public String toString() { int length = (count > 0 && buf[count - 1] == CR) ? count - 1 : count; try { return new String(buf, 0, length, charset.name()); } catch (UnsupportedEncodingException e) { throw new AssertionError(e); // Since we control the charset this // will never happen. } } }; while (true) { out.write(buf, pos, end - pos);//将数据写入字节数组缓存 // Mark unterminated line in case fillBuf throws EOFException or IOException. end = -1; fillBuf(); // Try to find LF in the buffered data and return the line if successful. for (int i = pos; i != end; ++i) { if (buf[i] == LF) {//新行 if (i != pos) { out.write(buf, pos, i - pos);//写出这一行 } out.flush(); pos = i + 1; return out.toString(); } } } } } /** * Reads new input data into the buffer. Call only with pos == end or end == -1, * depending on the desired outcome if the function throws. */ private void fillBuf() throws IOException { int result = in.read(buf, 0, buf.length); if (result == -1) { throw new EOFException(); } pos = 0; end = result; }
1.1.1.2.1.1.1 readJournalLine(reader.readLine());
/** * 读取日志文件内容。 * 注意日志格式例如:D xxxxxxxx * 第六行是以一个DIRTY前缀开始的,后面紧跟着缓存图片的key。 * 通常我们看到DIRTY这个字样都不代表着什么好事情,意味着这是一条脏数据。 * 没错,每当我们调用一次DiskLruCache的edit()方法时,都会向journal文件中写入一条DIRTY记录,表示我们正准备写入一条缓存数据,但不知结果如何。 * 然后调用commit()方法表示写入缓存成功,这时会向journal中写入一条CLEAN记录,意味着这条“脏”数据被“洗干净了”,调用abort() * 方法表示写入缓存失败,这时会向journal中写入一条REMOVE记录。 * 也就是说,每一行DIRTY的key,后面都应该有一行对应的CLEAN或者REMOVE的记录,否则这条数据就是“脏”的,会被自动删除掉。 * 除了CLEAN前缀和key之外,后面还有一个152313,这是什么意思呢? * 其实,DiskLruCache会在每一行CLEAN记录的最后加上该条缓存数据的大小,以字节为单位。 * * 执行步骤: * 1.根据第一个空格解析字符串获取命令标识:CRUD * 2.获取第二个空格索引,如果不存在第二个空格获取缓存的key,并且命令标识为D,就删除缓存实体函数返回。 * 如果存在第二个空格,得到缓存文件的key * 3.根据key获取缓存的实体,如果不存在就创建。 * 4.根据命令标识符执行操作。 * 对于CLEAN格式:C XXXXXX txxxx 123000,首先判断是否有过期前缀,如果存在保存过期时间不存在设置为最大值。接着保存缓存文件大小。 * 对于UDPATE:获取Editor对象。 */private void readJournalLine(String line) throws IOException { int firstSpace = line.indexOf(' '); char lineTag = 0; if (firstSpace == 1) {//命令标识 lineTag = line.charAt(0); } else { throw new IOException("unexpected journal line: " + line); } int keyBegin = firstSpace + 1; int secondSpace = line.indexOf(' ', keyBegin);//第二个空格索引 final String diskKey; if (secondSpace == -1) {//不存在第二个空格 diskKey = line.substring(keyBegin);//获取缓存文件的key if (lineTag == DELETE) {//删除指令 lruEntries.remove(diskKey);//移除这个key return; } } else {//存在第二个空格 diskKey = line.substring(keyBegin, secondSpace);//获取缓存文件的key } Entry entry = lruEntries.get(diskKey);//缓存实体 if (entry == null) { entry = new Entry(diskKey); lruEntries.put(diskKey, entry); } switch (lineTag) { case CLEAN: { entry.readable = true; entry.currentEditor = null; String[] parts = line.substring(secondSpace + 1).split(" "); if (parts.length > 0) { try { if (parts[0].charAt(0) == EXPIRY_PREFIX) {//过期前缀 entry.expiryTimestamp = Long.valueOf(parts[0].substring(1)); entry.setLengths(parts, 1);//设置缓存文件的大小 } else {//不存在过期前缀 entry.expiryTimestamp = Long.MAX_VALUE; entry.setLengths(parts, 0);//设置缓存文件的大小 } } catch (Throwable e) { throw new IOException("unexpected journal line: " + line); } } break; } case UPDATE: { entry.currentEditor = new Editor(entry); break; } case READ: { // This work was already done by calling lruEntries.get(). break; } default: { throw new IOException("unexpected journal line: " + line); } }}
1.1.1.2.1.2 计算总大小 cache.processJournal();
/** * Computes the initial size and collects garbage as a part of opening the * cache. Dirty entries are assumed to be inconsistent and will be deleted. * 执行步骤: * 1.删除日志临时文件 * 2.迭代实体,如果不是update直接累加计算缓存文件总大小,是update删除实体对应的dirty及clean文件。 */private void processJournal() throws IOException { deleteIfExists(journalFileTmp);//删除日志临时文件 for (Iteratori = lruEntries.values().iterator(); i.hasNext(); ) { Entry entry = i.next(); if (entry.currentEditor == null) { for (int t = 0; t < valueCount; t++) { size += entry.lengths[t];//累计计算缓存文件总大小 } } else {//update状态删除实体的Dirty和Clean文件 ? entry.currentEditor = null; for (int t = 0; t < valueCount; t++) { deleteIfExists(entry.getCleanFile(t)); deleteIfExists(entry.getDirtyFile(t)); } i.remove(); } }}
2.显示图片, mBitmapUtils.display(mRecyclerViewHolder.mArticleImage, article.getPhotoURL());
publicvoid display(T container, String uri) { display(container, uri, null, null);}public void display(T container, String uri, BitmapDisplayConfig displayConfig) { display(container, uri, displayConfig, null);}public void display(T container, String uri, BitmapLoadCallBack callBack) { display(container, uri, null, callBack);} /** * 1.设置加载回调,设置展示配置BitmapDisplayConfig,设置Bitmap大小参数 * 2.先从内存中获取Bitmap,如果存在设置回调状态 * 3.内存中不存在,检查如果加载Bitmap任务不存在,新建BitmapLoadTask对象获得PriorityExecutor线程池加载器 * 4.从磁盘获取缓存文件,缓存存在并且线程池加载器正在忙,切换磁盘缓存加载器 * 5.获得配置正在加载Drawable对象封装添加对应Task的AsyncDrawable对象 * 6.回调接口设置该AsyncDrawable对象 * 7.传入线程池加载器执行BitmapLoadTask对象 */public void display(T container, String uri, BitmapDisplayConfig displayConfig, BitmapLoadCallBack callBack) { if (container == null) { return; } if (callBack == null) { callBack = new DefaultBitmapLoadCallBack ();//默认图片加载回调 } if (displayConfig == null || displayConfig == defaultDisplayConfig) { displayConfig = defaultDisplayConfig.cloneNew(); } // Optimize Max Size BitmapSize size = displayConfig.getBitmapMaxSize(); displayConfig.setBitmapMaxSize(BitmapCommonUtils.optimizeMaxSizeByView(container, size .getWidth(), size.getHeight()));//设置Bitmap显示的最大值 container.clearAnimation();//清除动画 if (TextUtils.isEmpty(uri)) {//下载地址为空,回调失败返回 callBack.onLoadFailed(container, uri, displayConfig.getLoadFailedDrawable()); return; } // start loading callBack.onPreLoad(container, uri, displayConfig); // find bitmap from mem cache.先从内存中获取Bitmap Bitmap bitmap = globalConfig.getBitmapCache().getBitmapFromMemCache(uri, displayConfig); if (bitmap != null) {//内存中存在Bitmap,设置回调状态结果 callBack.onLoadStarted(container, uri, displayConfig); callBack.onLoadCompleted(container, uri, bitmap, displayConfig, BitmapLoadFrom .MEMORY_CACHE); } else if (!bitmapLoadTaskExist(container, uri, callBack)) {//bitmap加载任务不存在 final BitmapLoadTask loadTask = new BitmapLoadTask (container, uri, displayConfig, callBack);//新建下载任务 // get executor 获得加载执行器 PriorityExecutor executor = globalConfig.getBitmapLoadExecutor(); File diskCacheFile = this.getBitmapFileFromDiskCache(uri);//从磁盘中获取缓存 boolean diskCacheExist = diskCacheFile != null && diskCacheFile.exists(); if (diskCacheExist && executor.isBusy()) {//文件存在,并且Bitmap加载线程池忙 executor = globalConfig.getDiskCacheExecutor();//获取磁盘缓存处理线程池执行器 } // set loading image Drawable loadingDrawable = displayConfig.getLoadingDrawable();//设置正在加载显示的图片 callBack.setDrawable(container, new AsyncDrawable (loadingDrawable, loadTask)); loadTask.setPriority(displayConfig.getPriority()); loadTask.executeOnExecutor(executor); }}
2.1 设置Bitmap显示的最大值displayConfig.setBitmapMaxSize(BitmapCommonUtils.optimizeMaxSizeByView(container, size.getWidth(), size.getHeight()));
/** * BitmapCommonUtils.java * 设置Bitmap大小返回BitmapSize。 * 1.如果用户设置了最大最小值,直接构造返回BitmapSize对象。 * 2.获取View的Layout参数设置大小 * 3.如果上述得到值小于0,反射获取ImageView的“mMaxWidth”“mMaxHeight”值 * 4.如果上述得到值小于0,获取窗口大小,并进行设置。 * */public static BitmapSize optimizeMaxSizeByView(View view, int maxImageWidth, int maxImageHeight) { int width = maxImageWidth; int height = maxImageHeight; if (width > 0 && height > 0) { return new BitmapSize(width, height); } final ViewGroup.LayoutParams params = view.getLayoutParams(); if (params != null) {//根据父容器参数设置View显示大小 if (params.width > 0) { width = params.width; } else if (params.width != ViewGroup.LayoutParams.WRAP_CONTENT) { width = view.getWidth(); } if (params.height > 0) { height = params.height; } else if (params.height != ViewGroup.LayoutParams.WRAP_CONTENT) { height = view.getHeight(); } } if (width <= 0) width = getImageViewFieldValue(view, "mMaxWidth");//根据ImageView声明字段获取大小 if (height <= 0) height = getImageViewFieldValue(view, "mMaxHeight"); BitmapSize screenSize = getScreenSize(view.getContext()); if (width <= 0) width = screenSize.getWidth();//根据窗口大小设置大小 if (height <= 0) height = screenSize.getHeight(); return new BitmapSize(width, height);} /** * 获得ImageView声明的mMaxWidth,mMaxHeight字段数值 */private static int getImageViewFieldValue(Object object, String fieldName) { int value = 0; if (object instanceof ImageView) { try { Field field = ImageView.class.getDeclaredField(fieldName); field.setAccessible(true); int fieldValue = (Integer) field.get(object); if (fieldValue > 0 && fieldValue < Integer.MAX_VALUE) {//不是默认值 value = fieldValue; } } catch (Throwable e) { } } return value;}
2.2 从内存缓存中获取Bitmap,globalConfig.getBitmapCache().getBitmapFromMemCache(uri, displayConfig);
public BitmapCache getBitmapCache() { if (bitmapCache == null) { bitmapCache = new BitmapCache(this); } return bitmapCache;} /** * Get the bitmap from memory cache. * * @param uri Unique identifier for which item to get * @param config * @return The bitmap if found in cache, null otherwise * 从内存缓存中查找是否存在Bitmap */public Bitmap getBitmapFromMemCache(String uri, BitmapDisplayConfig config) { if (mMemoryCache != null && globalConfig.isMemoryCacheEnabled()) { MemoryCacheKey key = new MemoryCacheKey(uri, config); return mMemoryCache.get(key); } return null;}
2.3 判断Bitmap加载任务是否存在,bitmapLoadTaskExist(container, uri, callBack)
/** * 判断当前bitmap加载任务是否已经存在 * 1.传入container,获得Drawable对象强制转换判断时候存在下载任务 * 2.如果存在下载任务,在判断该任务与最新任务差异,存在就取消已存在任务 * 3.不存在就返回false */private staticboolean bitmapLoadTaskExist(T container, String uri, BitmapLoadCallBack callBack) { final BitmapLoadTask oldLoadTask = getBitmapTaskFromContainer(container, callBack); if (oldLoadTask != null) { final String oldUrl = oldLoadTask.uri; if (TextUtils.isEmpty(oldUrl) || !oldUrl.equals(uri)) { oldLoadTask.cancel(true);//取消旧的下载任务 } else { return true; } } return false;} private static BitmapLoadTask getBitmapTaskFromContainer(T container, BitmapLoadCallBack callBack) { if (container != null) { final Drawable drawable = callBack.getDrawable(container); if (drawable instanceof AsyncDrawable) {//获取BitmapTask对象 final AsyncDrawable asyncDrawable = (AsyncDrawable ) drawable; return asyncDrawable.getBitmapWorkerTask(); } } return null;}
2.4 bitmap加载任务不存在,从磁盘中获取缓存 this.getBitmapFileFromDiskCache(uri);
public File getBitmapFileFromDiskCache(String uri) { return globalConfig.getBitmapCache().getBitmapFileFromDiskCache(uri);}/** * Get the bitmap file from disk cache. * * @param uri Unique identifier for which item to get * @return The file if found in cache. */public File getBitmapFileFromDiskCache(String uri) { synchronized (mDiskCacheLock) { if (mDiskLruCache != null) { return mDiskLruCache.getCacheFile(uri, DISK_CACHE_INDEX); } else { return null; } }}/**LruDiskCache.java * 获取缓存文件 */public File getCacheFile(String key, int index) { String diskKey = fileNameGenerator.generate(key);//生成key File result = new File(this.directory, diskKey + "." + index); if (result.exists()) { return result; } else { try { this.remove(key); } catch (IOException ignore) { } return null; }}
2.5执行Btimap加载人任务loadTask.executeOnExecutor(executor);
/** * BitmapLoadTask.java * 后台线程执行 * 1.死循环判断暂定状态,并阻塞线程 * 2.发布状态,从磁盘缓存中获取Birmap * 3.磁盘缓存中不存在去下载 */ @Override protected Bitmap doInBackground(Object... params) { synchronized (pauseTaskLock) { while (pauseTask && !this.isCancelled()) { try { pauseTaskLock.wait();//线程阻塞 if (cancelAllTask) { return null; } } catch (Throwable e) { } } } Bitmap bitmap = null; // get cache from disk cache if (!this.isCancelled() && this.getTargetContainer() != null) { this.publishProgress(PROGRESS_LOAD_STARTED); bitmap = globalConfig.getBitmapCache().getBitmapFromDiskCache(uri, displayConfig); } // download image if (bitmap == null && !this.isCancelled() && this.getTargetContainer() != null) {//磁盘缓存不存在,去下载 bitmap = globalConfig.getBitmapCache().downloadBitmap(uri, displayConfig, this); from = BitmapLoadFrom.URI; } return bitmap; }
2.5.1 获取磁盘缓存的Bitmap,globalConfig.getBitmapCache().getBitmapFromDiskCache(uri, displayConfig);
/** * Get the bitmap from disk cache. * @param uri * @param config * @return * BitmapCache.java * 从磁盘缓存中获取Bitmap * 1.检查LruDiskCache是否存在,初始化LruDiskCache对象 * 2.根据传入url获取磁盘缓存处理Snapshot对象,如果为null即不存在缓存,直接返回null * 3.判断配置是否为空或者显示原图,如果是,直接根据输入流构造Bitmap。否则根据传入配置压缩Bitmap * 4.根据需要处理Bitmap旋转 * 5.将Bitmap添加到内存缓存中,返回Bitmap */public Bitmap getBitmapFromDiskCache(String uri, BitmapDisplayConfig config) { if (uri == null || !globalConfig.isDiskCacheEnabled()) return null; if (mDiskLruCache == null) { initDiskCache(); } if (mDiskLruCache != null) { LruDiskCache.Snapshot snapshot = null; try { snapshot = mDiskLruCache.get(uri); if (snapshot != null) { Bitmap bitmap = null; if (config == null || config.isShowOriginal()) {//显示原图 bitmap = BitmapDecoder.decodeFileDescriptor(snapshot.getInputStream (DISK_CACHE_INDEX).getFD()); } else {//图片压缩 bitmap = BitmapDecoder.decodeSampledBitmapFromDescriptor(snapshot .getInputStream(DISK_CACHE_INDEX).getFD(), config .getBitmapMaxSize(), config.getBitmapConfig()); } bitmap = rotateBitmapIfNeeded(uri, config, bitmap);//图片旋转 bitmap = addBitmapToMemoryCache(uri, config, bitmap, mDiskLruCache .getExpiryTimestamp(uri)); return bitmap; } } catch (Throwable e) { LogUtils.e(e.getMessage(), e); } finally { IOUtils.closeQuietly(snapshot); } } return null;}
2.5.1.1 获取snapshot = mDiskLruCache.get(uri);
public Snapshot get(String key) throws IOException { String diskKey = fileNameGenerator.generate(key); return getByDiskKey(diskKey);}/** * Returns a snapshot of the entry named {@code diskKey}, or null if it doesn't * exist is not currently readable. If a value is returned, it is moved to * the head of the LRU queue. * 1.根据key获取对应Entry对象,检查是否时间过期,如果过期就删除该Entry对象内的所有缓存文件,删除过期对象检查是否需要重构日志文件,最后返回null * 2.列举该Entry对象对应所有缓存文件的输入流,如果遇到不存在的文件异常关闭输入流返回null * 3.再次检查是否重构日志,同时新建Snapshot对象返回 */private synchronized Snapshot getByDiskKey(String diskKey) throws IOException { checkNotClosed(); Entry entry = lruEntries.get(diskKey); if (entry == null) { return null; } if (!entry.readable) { return null; } // If expired, delete the entry. if (entry.expiryTimestamp < System.currentTimeMillis()) {//删除过期 for (int i = 0; i < valueCount; i++) { File file = entry.getCleanFile(i); if (file.exists() && !file.delete()) { throw new IOException("failed to delete " + file); } size -= entry.lengths[i]; entry.lengths[i] = 0; } redundantOpCount++; journalWriter.append(DELETE + " " + diskKey + '\n');//添加删除记录 lruEntries.remove(diskKey); if (journalRebuildRequired()) {//重构日志文件 executorService.submit(cleanupCallable); } return null; } // Open all streams eagerly to guarantee that we see a single published // snapshot. If we opened streams lazily then the streams could come // from different edits. //列举该key对应的所有输入流,对不存在 FileInputStream[] ins = new FileInputStream[valueCount]; try { for (int i = 0; i < valueCount; i++) { ins[i] = new FileInputStream(entry.getCleanFile(i)); } } catch (FileNotFoundException e) { // A file must have been deleted manually! for (int i = 0; i < valueCount; i++) { if (ins[i] != null) { IOUtils.closeQuietly(ins[i]); } else { break; } } return null; } journalWriter.append(READ + " " + diskKey + '\n');//添加读取记录 if (journalRebuildRequired()) { executorService.submit(cleanupCallable); }
2.5.1.2 构造原图bitmap = BitmapDecoder.decodeFileDescriptor(snapshot.getInputStream(DISK_CACHE_INDEX).getFD());
/*** * BitmapFactory.Options.inPurgeable; * BitmapDecoder.java * * 如果 inPurgeable 设为True的话表示使用BitmapFactory创建的Bitmap * 用于存储Pixel的内存空间在系统内存不足时可以被回收, * 在应用需要再次访问Bitmap的Pixel时(如绘制Bitmap或是调用getPixel), * 系统会再次调用BitmapFactory decoder重新生成Bitmap的Pixel数组。 * 为了能够重新解码图像,bitmap要能够访问存储Bitmap的原始数据。 * * 在inPurgeable为false时表示创建的Bitmap的Pixel内存空间不能被回收, * 这样BitmapFactory在不停decodeByteArray创建新的Bitmap对象, * 不同设备的内存不同,因此能够同时创建的Bitmap个数可能有所不同, * 200个bitmap足以使大部分的设备重新OutOfMemory错误。 * 当isPurgable设为true时,系统中内存不足时, * 可以回收部分Bitmap占据的内存空间,这时一般不会出现OutOfMemory 错误。 */public static Bitmap decodeFileDescriptor(FileDescriptor fileDescriptor) { synchronized (lock) { final BitmapFactory.Options options = new BitmapFactory.Options(); options.inPurgeable = true; options.inInputShareable = true; // 与inPurgeable 一起使用 try { return BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options); } catch (Throwable e) { LogUtils.e(e.getMessage(), e); return null; } }}
2.5.1.3 构造缩放bitmap = BitmapDecoder.decodeSampledBitmapFromDescriptor(snapshot.getInputStream(DISK_CACHE_INDEX).getFD(), config.getBitmapMaxSize(), config.getBitmapConfig());
public static Bitmap decodeSampledBitmapFromDescriptor(FileDescriptor fileDescriptor, BitmapSize maxSize, Bitmap.Config config) { synchronized (lock) { final BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true;//只是获取Bitmap参数 options.inPurgeable = true; options.inInputShareable = true; BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options); options.inSampleSize = calculateInSampleSize(options, maxSize.getWidth(), maxSize .getHeight()); options.inJustDecodeBounds = false; if (config != null) { options.inPreferredConfig = config; } try { return BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options); } catch (Throwable e) { LogUtils.e(e.getMessage(), e); return null; } }} /** * BitmapDecoder.java * 计算实际Bitmap尺寸 */public static int calculateInSampleSize(BitmapFactory.Options options, int maxWidth, int maxHeight) { final int height = options.outHeight;//测量获得Bitmap尺寸 final int width = options.outWidth; int inSampleSize = 1; if (width > maxWidth || height > maxHeight) { if (width > height) { inSampleSize = Math.round((float) height / (float) maxHeight);//四舍五入取整 } else { inSampleSize = Math.round((float) width / (float) maxWidth); } final float totalPixels = width * height; final float maxTotalPixels = maxWidth * maxHeight * 2; while (totalPixels / (inSampleSize * inSampleSize) > maxTotalPixels) {//? inSampleSize++; } } return inSampleSize;}
2.5.1.4 处理图片旋转,bitmap = rotateBitmapIfNeeded(uri, config, bitmap);
/** * 1.获取配置判断是否自动旋转 * 2.如果是,获取Bitmap文件构造ExifInterface对象,获取图片方向参数 * 3.如果方向不是0,矫正Bitmap方法返回 */private synchronized Bitmap rotateBitmapIfNeeded(String uri, BitmapDisplayConfig config, Bitmap bitmap) { Bitmap result = bitmap; if (config != null && config.isAutoRotation()) { File bitmapFile = this.getBitmapFileFromDiskCache(uri); if (bitmapFile != null && bitmapFile.exists()) {//获取缓存Bitmap文件对象 ExifInterface exif = null;//这个接口提供了图片文件的旋转,gps,时间等信息。 try { exif = new ExifInterface(bitmapFile.getPath()); } catch (Throwable e) { return result; } int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED);//获取图片方向参数 int angle = 0; switch (orientation) { case ExifInterface.ORIENTATION_ROTATE_90: angle = 90; break; case ExifInterface.ORIENTATION_ROTATE_180: angle = 180; break; case ExifInterface.ORIENTATION_ROTATE_270: angle = 270; break; default: angle = 0; break; } if (angle != 0) { Matrix m = new Matrix(); m.postRotate(angle); result = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap .getHeight(), m, true);//重新构造Bitmap bitmap.recycle(); bitmap = null; } } } return result;}
2.5.1.5 将Bitmap添加到内存缓存中 bitmap = addBitmapToMemoryCache(uri, config, bitmap, mDiskLruCache.getExpiryTimestamp(uri));
/** * 将Bitmap添加到内存中 */private Bitmap addBitmapToMemoryCache(String uri, BitmapDisplayConfig config, Bitmap bitmap, long expiryTimestamp) throws IOException { if (config != null) { BitmapFactory bitmapFactory = config.getBitmapFactory(); if (bitmapFactory != null) {//? bitmap = bitmapFactory.cloneNew().createBitmap(bitmap); } } if (uri != null && bitmap != null && globalConfig.isMemoryCacheEnabled() && mMemoryCache != null) { MemoryCacheKey key = new MemoryCacheKey(uri, config); mMemoryCache.put(key, bitmap, expiryTimestamp);//添加到内存缓存 } return bitmap;}
2.5.2 磁盘缓存Bitmap不存在,去下载bitmap = globalConfig.getBitmapCache().downloadBitmap(uri, displayConfig, this);
/** * BitmapCache.java * 下载Bitmap * 1.如果配置开启磁盘缓存,下载Bitmap到dirtyFile,根据结果clean或者delete,构造Bitmap * 2.如果上述下载Bitmap为null,将Bitmap下载到内存输出流中,构造Bitmap * 3.处理Bitmap旋转以及添加到内存缓存中 */public Bitmap downloadBitmap(String uri, BitmapDisplayConfig config, final BitmapUtils .BitmapLoadTask task) { BitmapMeta bitmapMeta = new BitmapMeta(); OutputStream outputStream = null; LruDiskCache.Snapshot snapshot = null; try { Bitmap bitmap = null; // try download to disk,下载到磁盘缓存 if (globalConfig.isDiskCacheEnabled()) { if (mDiskLruCache == null) { initDiskCache(); } if (mDiskLruCache != null) { try { snapshot = mDiskLruCache.get(uri); if (snapshot == null) {//缓存不存在 LruDiskCache.Editor editor = mDiskLruCache.edit(uri); if (editor != null) { outputStream = editor.newOutputStream(DISK_CACHE_INDEX);//dirtyFile bitmapMeta.expiryTimestamp = globalConfig.getDownloader() .downloadToStream(uri, outputStream, task); //下载Bitmap存储到dirtyFile if (bitmapMeta.expiryTimestamp < 0) {//下载出错 editor.abort(); return null; } else {//下载成功 editor.setEntryExpiryTimestamp(bitmapMeta.expiryTimestamp); editor.commit(); } snapshot = mDiskLruCache.get(uri); } } if (snapshot != null) { bitmapMeta.inputStream = snapshot.getInputStream(DISK_CACHE_INDEX); bitmap = decodeBitmapMeta(bitmapMeta, config);//构造Bitmap if (bitmap == null) {//获取的Bitmap为null,删除对应clean文件 bitmapMeta.inputStream = null; mDiskLruCache.remove(uri); } } } catch (Throwable e) { LogUtils.e(e.getMessage(), e); } } } // try download to memory stream, if (bitmap == null) { outputStream = new ByteArrayOutputStream(); bitmapMeta.expiryTimestamp = globalConfig.getDownloader().downloadToStream(uri, outputStream, task); if (bitmapMeta.expiryTimestamp < 0) {//下载失败了 return null; } else {//成功,去生成Bitmap bitmapMeta.data = ((ByteArrayOutputStream) outputStream).toByteArray(); bitmap = decodeBitmapMeta(bitmapMeta, config); } } if (bitmap != null) {//处理旋转,添加到内存缓存 bitmap = rotateBitmapIfNeeded(uri, config, bitmap); bitmap = addBitmapToMemoryCache(uri, config, bitmap, bitmapMeta.expiryTimestamp); } return bitmap; } catch (Throwable e) { LogUtils.e(e.getMessage(), e); } finally { IOUtils.closeQuietly(outputStream); IOUtils.closeQuietly(snapshot); } return null;}
2.5.2.1 获取Editor对象,mDiskLruCache.edit(uri);
/** * Returns an editor for the entry named {@code Key}, or null if another * edit is in progress. */public Editor edit(String key) throws IOException { String diskKey = fileNameGenerator.generate(key); return editByDiskKey(diskKey, ANY_SEQUENCE_NUMBER);}/** * 获取Editor对象 * 1.检查是否存在Entry对象,不存在新建添加到缓存 * 2.新建Editor对象,添加到Entry对象添加日志。 */private synchronized Editor editByDiskKey(String diskKey, long expectedSequenceNumber) throws IOException { checkNotClosed(); Entry entry = lruEntries.get(diskKey); if (expectedSequenceNumber != ANY_SEQUENCE_NUMBER && (entry == null || entry .sequenceNumber != expectedSequenceNumber)) { return null; // Snapshot is stale.? } if (entry == null) {//不存在缓存记录 entry = new Entry(diskKey); lruEntries.put(diskKey, entry); } else if (entry.currentEditor != null) { return null; // Another edit is in progress. } Editor editor = new Editor(entry);//新建 entry.currentEditor = editor; // Flush the journal before creating files to prevent file leaks. journalWriter.write(UPDATE + " " + diskKey + '\n'); journalWriter.flush(); return editor;}
2.5.2.2 获取dirtyFile输出流outputStream = editor.newOutputStream(DISK_CACHE_INDEX);
/** * Returns a new unbuffered output stream to write the value at * {@code index}. If the underlying output stream encounters errors * when writing to the filesystem, this edit will be aborted when * {@link #commit} is called. The returned output stream does not throw * IOExceptions. * LruDiskCache.java * 1.判断Entry对象当前的Editor应用是否与此相同,不同异常 * 2.取出Entry对象对应的dirtyFile,新建输出流并返回 */ public OutputStream newOutputStream(int index) throws IOException { synchronized (LruDiskCache.this) { if (entry.currentEditor != this) { throw new IllegalStateException(); } if (!entry.readable) { written[index] = true; } File dirtyFile = entry.getDirtyFile(index); FileOutputStream outputStream; try { outputStream = new FileOutputStream(dirtyFile); } catch (FileNotFoundException e) { // Attempt to recreate the cache directory. directory.mkdirs(); try { outputStream = new FileOutputStream(dirtyFile); } catch (FileNotFoundException e2) { // We are unable to recover. Silently eat the writes. return NULL_OUTPUT_STREAM; } } return new FaultHidingOutputStream(outputStream); } }
2.5.2.3 去下载BitmapbitmapMeta.expiryTimestamp = globalConfig.getDownloader().downloadToStream(uri, outputStream, task);
/** * Download bitmap to outputStream by uri. * * @param uri file path, assets path(assets/xxx) or http url. * @param outputStream * @param task * @return The expiry time stamp or -1 if failed to download. * 下载文件到dirtyFile * 1.url是文件路径,获取响应输入流和文件长度,构造过期时间戳 * 2.url是assets路径获取响应输入流和文件长度,构造过期时间戳为最大值 * 3.url是网址,获取URLConnection,获取输入流,构造时间戳 * 4.将获取的输入流读入资源,写出到提供的输出流 * 5.返回过期时间戳 */@Overridepublic long downloadToStream(String uri, OutputStream outputStream, final BitmapUtils .BitmapLoadTask task) { if (task == null || task.isCancelled() || task.getTargetContainer() == null) return -1; URLConnection urlConnection = null; BufferedInputStream bis = null; OtherUtils.trustAllHttpsURLConnection(); long result = -1; long fileLen = 0; long currCount = 0; try { if (uri.startsWith("/")) {//url是一个文件路径 FileInputStream fileInputStream = new FileInputStream(uri); fileLen = fileInputStream.available();//该方法返回可估算从这个输入流中可无阻塞读取剩余的字节数。 bis = new BufferedInputStream(fileInputStream); result = System.currentTimeMillis() + this.getDefaultExpiry(); } else if (uri.startsWith("assets/")) {//assets文件夹中的资源 InputStream inputStream = this.getContext().getAssets().open(uri.substring(7, uri .length())); fileLen = inputStream.available(); bis = new BufferedInputStream(inputStream); result = Long.MAX_VALUE; } else {//设置一个资源网址 final URL url = new URL(uri); urlConnection = url.openConnection(); urlConnection.setConnectTimeout(this.getDefaultConnectTimeout()); urlConnection.setReadTimeout(this.getDefaultReadTimeout()); bis = new BufferedInputStream(urlConnection.getInputStream()); result = urlConnection.getExpiration();//响应过期时间戳 result = result < System.currentTimeMillis() ? System.currentTimeMillis() + this .getDefaultExpiry() : result; fileLen = urlConnection.getContentLength(); } if (task.isCancelled() || task.getTargetContainer() == null) return -1; byte[] buffer = new byte[4096]; int len = 0; BufferedOutputStream out = new BufferedOutputStream(outputStream); while ((len = bis.read(buffer)) != -1) { out.write(buffer, 0, len);//向提供的输出流写出,这个输出流一般是dirtyFile currCount += len; if (task.isCancelled() || task.getTargetContainer() == null) return -1; task.updateProgress(fileLen, currCount); } out.flush(); } catch (Throwable e) { result = -1; LogUtils.e(e.getMessage(), e); } finally { IOUtils.closeQuietly(bis); } return result;}
2.5.2.4 下载文件出错editor.abort();
/** * Aborts this edit. This releases the edit lock so another edit may be * started on the same key. */public void abort() throws IOException { completeEdit(this, false);}/** * 1.检查初次生成entry的index时候有值 * 2.遍历所有缓存文件,如果状态为success,将存在的dirty文件清洗,缓存大小信息 * 3.如果状态为失败,删除所有dirty文件 * 4.更新日志文件 */private synchronized void completeEdit(Editor editor, boolean success) throws IOException { Entry entry = editor.entry; if (entry.currentEditor != editor) { throw new IllegalStateException(); } // If this edit is creating the entry for the first time, every index must have a value. if (success && !entry.readable) { for (int i = 0; i < valueCount; i++) { if (!editor.written[i]) { editor.abort(); throw new IllegalStateException("Newly created entry didn't create value for " + "index " + i); } if (!entry.getDirtyFile(i).exists()) { editor.abort(); return; } } } for (int i = 0; i < valueCount; i++) { File dirty = entry.getDirtyFile(i); if (success) {//如果成功,清洗旧文件 if (dirty.exists()) { File clean = entry.getCleanFile(i); dirty.renameTo(clean);//重命名文件 long oldLength = entry.lengths[i]; long newLength = clean.length(); entry.lengths[i] = newLength;//更新文件长度 size = size - oldLength + newLength; } } else { deleteIfExists(dirty);//失败就删除dirtyFile } }//更新日志文件 redundantOpCount++; entry.currentEditor = null; if (entry.readable | success) { entry.readable = true; journalWriter.write(CLEAN + " " + entry.diskKey + " " + EXPIRY_PREFIX + entry .expiryTimestamp + entry.getLengths() + '\n'); if (success) { entry.sequenceNumber = nextSequenceNumber++; } } else { lruEntries.remove(entry.diskKey); journalWriter.write(DELETE + " " + entry.diskKey + '\n'); } journalWriter.flush(); if (size > maxSize || journalRebuildRequired()) { executorService.submit(cleanupCallable); }}
2.5.2.5下载成功提交editor.commit();
/** * Commits this edit so it is visible to readers. This releases the * edit lock so another edit may be started on the same key. */ public void commit() throws IOException { if (hasErrors) { completeEdit(this, false); removeByDiskKey(entry.diskKey); // The previous entry is stale. } else { completeEdit(this, true); } committed = true; }/** * Drops the entry for {@code diskKey} if it exists and can be removed. Entries * actively being edited cannot be removed. * * @return true if an entry was removed. * 删除clean文件 */private synchronized boolean removeByDiskKey(String diskKey) throws IOException { checkNotClosed(); Entry entry = lruEntries.get(diskKey); if (entry == null || entry.currentEditor != null) { return false; } for (int i = 0; i < valueCount; i++) {//删除clean文件 File file = entry.getCleanFile(i); if (file.exists() && !file.delete()) { throw new IOException("failed to delete " + file); } size -= entry.lengths[i]; entry.lengths[i] = 0; } redundantOpCount++; journalWriter.append(DELETE + " " + diskKey + '\n'); lruEntries.remove(diskKey); if (journalRebuildRequired()) { executorService.submit(cleanupCallable); } return true;}
2.5.2.6再次获取snapshot = mDiskLruCache.get(uri);见2.5.1.1,此次snapshot对象已经存在
2.5.2.7根据下载的资源构造bitmap = decodeBitmapMeta(bitmapMeta, config);
/** * 通过BitmapMeta对象构造Bitmap * 1.如果BitmapMeta对象输入流不为空,以输入流获取Bitmap * 2.如果BitmapMeta对象byte数据不为空,以此数据获取Bitmap */private Bitmap decodeBitmapMeta(BitmapMeta bitmapMeta, BitmapDisplayConfig config) throws IOException { if (bitmapMeta == null) return null; Bitmap bitmap = null; if (bitmapMeta.inputStream != null) { if (config == null || config.isShowOriginal()) { bitmap = BitmapDecoder.decodeFileDescriptor(bitmapMeta.inputStream.getFD()); } else { bitmap = BitmapDecoder.decodeSampledBitmapFromDescriptor(bitmapMeta.inputStream .getFD(), config.getBitmapMaxSize(), config.getBitmapConfig()); } } else if (bitmapMeta.data != null) { if (config == null || config.isShowOriginal()) { bitmap = BitmapDecoder.decodeByteArray(bitmapMeta.data); } else { bitmap = BitmapDecoder.decodeSampledBitmapFromByteArray(bitmapMeta.data, config .getBitmapMaxSize(), config.getBitmapConfig()); } } return bitmap;}
2.5.2.8 如果下载到磁盘缓存为null再去下载到内存缓存中bitmapMeta.expiryTimestamp = globalConfig.getDownloader().downloadToStream(uri,outputStream, task);见2.5.2.3
2.5.2.9 处理下载的资源构造Bitmap,bitmap = decodeBitmapMeta(bitmapMeta, config);见2.5.2.7
2.5.2.10 如果下载成功,先去处理图片旋转问题;见2.5.1.4。同时将Bitmap添加到内存缓存中;见2.5.1.5
2.5.3 后台任务执行结束,处理回调onPostExecute
//Bitmap加载成功回调 @Override protected void onPostExecute(Bitmap bitmap) { final T container = this.getTargetContainer(); if (container != null) { if (bitmap != null) { callBack.onLoadCompleted(container, this.uri, bitmap, displayConfig, from); } else { callBack.onLoadFailed(container, this.uri, displayConfig .getLoadFailedDrawable()); } } }
至此BitmapLoadTask完成。
二 致敬原作者,谢谢作者辛苦付出,代码受益匪浅。
三 其他讲解:
架构讲解:
四 源码注释
源码详细注释:
转载地址:https://blog.csdn.net/leif_/article/details/50379433 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!
发表评论
最新留言
网站不错 人气很旺了 加油
[***.192.178.218]2024年04月22日 15时59分36秒
关于作者
喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!
推荐文章
拜占庭容错共识(PBFT)
2019-04-26
预言机如何读取和验证数据?
2019-04-26
git- git checkout 创建新的分支、切换分支、切换到指定tag
2019-04-26
什么是非同质化代币(NFT)\ NFT有哪些应用?
2019-04-26
VRF是什么?
2019-04-26
Qt:01---Qt Creator与SDK的下载与安装
2019-04-26
【直播回顾】获取 iOS 用户,您还可以这么做
2019-04-27
如何保证游戏长盛不衰
2019-04-27
Kotlin Vocabulary | 内联函数的原理与应用
2019-04-27
467_Arduino AD采集范围标定
2019-04-27
468_Arduino生成ASCII码表
2019-04-27
469_Arduino超声波距离传感器例程调试
2019-04-27
470_Arduino LCD驱动初步
2019-04-27
472_Arduino setup之前的工作分析
2019-04-27
473_Arduino.h内容分析
2019-04-27
478_Arduino telnet连接测试
2019-04-27
479_C语言sizeof知识点小结
2019-04-27
480_C语言编译链接结果文件分析
2019-04-27
481_C语言野指针
2019-04-27