ImageLoader加密解密共存
問題提出
Android開發中異步圖片顯示應用特別廣泛,我們常常會用到imageloader、Glide、picasso或者其他開源框架,各有好處,我們這邊開發中經常使用的是imageloader,但是在最近的項目開發中,我們圖片的URL是加密的二進制流,出於項目的需要,我們需要對加密流進行解密。一個簡單的解決方法
出於快速開發的想法,想尋找一個特別簡單的能夠拿到圖片InputStream,很快的找到了Glide的imageDecoder回調,在這裏面有一個decode方法,能夠操作InputStream進行解密操作,然後倒入了Glide框架,這樣一個項目中擁有了兩個異步圖片加載框架,怎麼說也不是一個好方法。幹掉一個異步加載框架
上面提出了項目中因爲解密加入了Gilde框架,這樣的做法是簡單(其實imageloader也很簡單),但是我絕對不允許一個項目兩個相同功能的框架,所以我要看看imageloader的源碼,獲取inputstream,進而在decode的時候把加密的圖片給解密了,然後再使用中只需要給參數加上key,滿足在使用中沒有差別,首先看一下源碼,理解一下imageloader的工作流程:
/**
這個方法是顯示圖片的時候調用的方法,存在一Imageloader.java裏面我們看看這個傳遞的參數,options裏面傳遞了圖片顯示的一些參數,我接下來會通過option傳遞圖片解密的密碼。
**/
public void displayImage(String uri, ImageAware imageAware, DisplayImageOptions options,
ImageSize targetSize,
ImageLoadingListener listener,
ImageLoadingProgressListener progressListener) {
checkConfiguration();
if (imageAware == null) {
throw new IllegalArgumentException(ERROR_WRONG_ARGUMENTS);
}
if (listener == null) {
listener = defaultListener;
}
if (options == null) {
options = configuration.defaultDisplayImageOptions;
}
if (TextUtils.isEmpty(uri)) {
engine.cancelDisplayTaskFor(imageAware);
listener.onLoadingStarted(uri, imageAware.getWrappedView());
if (options.shouldShowImageForEmptyUri()) {
imageAware.setImageDrawable(options.getImageForEmptyUri(configuration.resources));
} else {
imageAware.setImageDrawable(null);
}
listener.onLoadingComplete(uri, imageAware.getWrappedView(), null);
return;
}
if (targetSize == null) {
targetSize = ImageSizeUtils.defineTargetSizeForView(imageAware, configuration.getMaxImageSize());
}
String memoryCacheKey = MemoryCacheUtils.generateKey(uri, targetSize);
engine.prepareDisplayTaskFor(imageAware, memoryCacheKey);
listener.onLoadingStarted(uri, imageAware.getWrappedView());
// 上面都是一些判空等基本操作,可以從這兒開始看。
// 獲取圖片的軟應用。
Bitmap bmp = configuration.memoryCache.get(memoryCacheKey);
// 我們看else,確定下載圖片時候的流程,忽略有緩存之後的圖片顯示
if (bmp != null && !bmp.isRecycled()) {
L.d(LOG_LOAD_IMAGE_FROM_MEMORY_CACHE, memoryCacheKey);
if (options.shouldPostProcess()) {
ImageLoadingInfo imageLoadingInfo = new ImageLoadingInfo(uri, imageAware, targetSize, memoryCacheKey,
options, listener, progressListener, engine.getLockForUri(uri));
ProcessAndDisplayImageTask displayTask = new ProcessAndDisplayImageTask(engine, bmp, imageLoadingInfo,
defineHandler(options));
if (options.isSyncLoading()) {
displayTask.run();
} else {
engine.submit(displayTask);
}
} else {
options.getDisplayer().display(bmp, imageAware, LoadedFrom.MEMORY_CACHE);
listener.onLoadingComplete(uri, imageAware.getWrappedView(), bmp);
}
} else {
if (options.shouldShowImageOnLoading()) {
imageAware.setImageDrawable(options.getImageOnLoading(configuration.resources));
} else if (options.isResetViewBeforeLoading()) {
imageAware.setImageDrawable(null);
}
ImageLoadingInfo imageLoadingInfo = new ImageLoadingInfo(uri, imageAware, targetSize, memoryCacheKey,
options, listener, progressListener, engine.getLockForUri(uri));
// 真正處理圖片是在這個task的異步線程裏面
LoadAndDisplayImageTask displayTask = new LoadAndDisplayImageTask(engine, imageLoadingInfo,
defineHandler(options));
if (options.isSyncLoading()) {
displayTask.run();
} else {
engine.submit(displayTask);
}
}
}
接下來我們在看看LoadAndDisplayImageTask 這個類:
@Override
public void run() {
if (waitIfPaused()) return;
if (delayIfNeed()) return;
ReentrantLock loadFromUriLock = imageLoadingInfo.loadFromUriLock;
L.d(LOG_START_DISPLAY_IMAGE_TASK, memoryCacheKey);
if (loadFromUriLock.isLocked()) {
L.d(LOG_WAITING_FOR_IMAGE_LOADED, memoryCacheKey);
}
loadFromUriLock.lock();
Bitmap bmp;
try {
checkTaskNotActual();
// 再次看看內存裏面有沒有該圖片
bmp = configuration.memoryCache.get(memoryCacheKey);
if (bmp == null || bmp.isRecycled()) {
// 下載圖片的真正方法
bmp = tryLoadBitmap();
// ...... 這個線程下面的代碼和我們要做的事無關,都是在緩存圖片之類的
}
接下來看看tryLoadBitmap:
private Bitmap tryLoadBitmap() throws TaskCancelledException {
Bitmap bitmap = null;
try {
File imageFile = configuration.diskCache.get(uri);
// 如果已經有了那不是我要乾的事,所以跳過if
if (imageFile != null && imageFile.exists() && imageFile.length() > 0) {
L.d(LOG_LOAD_IMAGE_FROM_DISK_CACHE, memoryCacheKey);
loadedFrom = LoadedFrom.DISC_CACHE;
checkTaskNotActual();
bitmap = decodeImage(Scheme.FILE.wrap(imageFile.getAbsolutePath()));
}
if (bitmap == null || bitmap.getWidth() <= 0 || bitmap.getHeight() <= 0) {
L.d(LOG_LOAD_IMAGE_FROM_NETWORK, memoryCacheKey);
loadedFrom = LoadedFrom.NETWORK;
String imageUriForDecoding = uri;
if (options.isCacheOnDisk() && tryCacheImageOnDisk()) {
imageFile = configuration.diskCache.get(uri);
if (imageFile != null) {
imageUriForDecoding = Scheme.FILE.wrap(imageFile.getAbsolutePath());
}
}
checkTaskNotActual();
// 找到了真正decode的代碼,當然decodeImage要幹啥,我們還得去看看
bitmap = decodeImage(imageUriForDecoding);
// ..... 下面的代碼我又可以不看了
return bitmap;
}
好吧,該看decodeImage:
private Bitmap decodeImage(String imageUri) throws IOException {
ViewScaleType viewScaleType = imageAware.getScaleType();
ImageDecodingInfo decodingInfo = new ImageDecodingInfo(memoryCacheKey, imageUri, uri, targetSize, viewScaleType,
getDownloader(), options);
// 這裏應該去看decode,按理論這個方法應該到頭了。
return decoder.decode(decodingInfo);
}
我們看到decode是接口的方法,我們看看誰來實現了這個接口ImageDecoder,很容易知道這個真正的方法在BaseImageDecoder裏面,最後看一下真正處理stream的方法:
@Override
public Bitmap decode(ImageDecodingInfo decodingInfo) throws IOException {
Bitmap decodedBitmap;
ImageFileInfo imageInfo;
InputStream imageStream = getImageStream(decodingInfo);
if (imageStream == null) {
L.e(ERROR_NO_IMAGE_STREAM, decodingInfo.getImageKey());
return null;
}
try {
imageInfo = defineImageSizeAndRotation(imageStream, decodingInfo);
imageStream = resetStream(imageStream, decodingInfo);
Options decodingOptions = prepareDecodingOptions(imageInfo.imageSize, decodingInfo);
// decodedBitmap 就是要顯示的圖片了,所以我們在這裏嚴處理掉加密圖片。
decodedBitmap = BitmapFactory.decodeStream(imageStream, null, decodingOptions);
/**
這裏貼一下修改之後的處理邏輯,把decodedBitmap = BitmapFactory.decodeStream(imageStream, null, decodingOptions);改爲下面的代碼:
if(decodingInfo.isEncryption()){
//加密了,這裏解密方法就不貼了,私有方法,貼出來也沒有。
decodedBitmap = decryptBitmap(imageStream, decodingOptions,decodingInfo);
}else{
decodedBitmap = BitmapFactory.decodeStream(imageStream, null, decodingOptions);
}
**/
// ...... 省略部門代碼
return decodedBitmap;
}
源碼我們已經追溯完畢,接下來就是改。
- 增加Options參數值,處理加密圖片
既然知其所以然了,那就幹吧,我們給Options增加一個是否加密的標誌位,然後再增加一個解密密碼變量,這裏就是傳遞參數的問題了.
private final boolean isEncryption;
private final String password;
我們在上面的源碼中可以看到這個DisplayImageOptions類的參數是一直傳遞的,所以在最外面調用的時候,給options加上這兩個值吧,當然我給isEncryption默認值是false,這樣對非加密的圖片就是跟以前一樣的處理方式,貼一下加密的調用方式吧,然後decode的方法我就直接放到剛剛的源碼裏了。
ImageLoader.getInstance().displayImage(mPicturePaths.get(i).getmDataSource(),
imageView, ImageLoaderUtils.getEncryptionOptions(password));
// 這裏也貼一下getEncryptionOptions,放在ImageLoaderUtils類裏面
public static DisplayImageOptions getEncryptionOptions(String key){
return new DisplayImageOptions.Builder()
.showImageOnLoading(R.mipmap.pre_setting_bg) //設置圖片在下載期間顯示的圖片
.showImageForEmptyUri(R.mipmap.pre_setting_bg)//設置圖片Uri爲空或是錯誤的時候顯示的圖片
.showImageOnFail(R.mipmap.pre_setting_bg) //設置圖片加載/解碼過程中錯誤時候顯示的圖片
.cacheInMemory(true)//設置下載的圖片是否緩存在內存中
.cacheOnDisk(true)//設置下載的圖片是否緩存在SD卡中
.imageScaleType(ImageScaleType.IN_SAMPLE_INT)//設置圖片以如何的編碼方式顯示
.bitmapConfig(Bitmap.Config.RGB_565)//設置圖片的解碼類型
.imageScaleType(ImageScaleType.EXACTLY)
.setEncryption(true)
.setPassword(key)
.build();//構建完成
}
- 最後
代碼完全是自己寫的,如果有什麼不對的地方,還望批評指正,我的郵箱:[email protected],發現記錄一下,會有所作用,因爲好記性不如爛筆頭。