0.背景
公司是做和金融相关的,最近领导要整加密,对于图片这种要做防盗链处理,与一般的使用http自带的refer不同的是这次处理是需要向http请求头中添加请求参数,之前的加载图片的框架使用的ImageLoader,但并未听说过有这方面的api(如果有麻烦告诉下,谢谢)。幸好对http还是有简单了解,知道这个的处理无非就是在http请求中添加头或者其他信息。本着这个方向,开始向ImageLoader源码中挖掘(来不及看分析的同学可以直接移步到2.添加防盗链逻辑)。
##1.寻找网络请求
我们在使用ImageLoader的时候一般是先在Application中进行初始化,然后在配置ImageLoaderConfig:
public static final DisplayImageOptions imageLoaderBanner = new DisplayImageOptions.Builder()
.showImageOnLoading(R.mipmap.loading)
.showImageForEmptyUri(R.mipmap.empty)
.showImageOnFail(R.mipmap.fail).cacheInMemory(true)
.cacheOnDisc(true).considerExifParams(true)
.bitmapConfig(Bitmap.Config.RGB_565).build();
最后,调用:
ImageLoader.getInstance().displayImage(url, imageView, imageLoaderConfig);
既然url是从调用的方法传入的,那么我们有理由认为网路请求就在这个方法里。点击进去查看,可以看到如下代码:
if(bmp != null && !bmp.isRecycled()) {
...
} else {
if(options.shouldShowImageOnLoading()) {
imageAware.setImageDrawable(options.getImageOnLoading(this.configuration.resources));
} else if(options.isResetViewBeforeLoading()) {
imageAware.setImageDrawable((Drawable)null);
}
imageLoadingInfo = new ImageLoadingInfo(uri, imageAware, targetSize, memoryCacheKey, options, listener, progressListener, this.engine.getLockForUri(uri));
LoadAndDisplayImageTask displayTask = new LoadAndDisplayImageTask(this.engine, imageLoadingInfo, defineHandler(options));
if(options.isSyncLoading()) {
displayTask.run();
} else {
this.engine.submit(displayTask);
}
}
上面的if判断走的是缓存,else中是从网络中获取,里面的LoadAndDisplayImageTask就是异步加载图片的任务。点击到LoadAndDisplayImageTask中,查看里面的run方法:
public void run() {
...
Bitmap bmp;
label129: {
...
bmp = this.tryLoadBitmap();
...
}
...
}
其实这一大段中,我们只关心他的bmp是怎么来的,里面调用了 bmp = this.tryLoadBitmap(),继续查看里面代码:
private Bitmap tryLoadBitmap() throws LoadAndDisplayImageTask.TaskCancelledException {
Bitmap bitmap = null;
...
bitmap = this.decodeImage(imageUriForDecoding);
...
return bitmap;
}
同样,我们只要知道bitmap的生成,即this.decodeImage(imageUriForDecoding)这个方法,继续查看
private Bitmap decodeImage(String imageUri) throws IOException {
ViewScaleType viewScaleType = this.imageAware.getScaleType();
ImageDecodingInfo decodingInfo = new ImageDecodingInfo(this.memoryCacheKey, imageUri, this.uri, this.targetSize, viewScaleType, this.getDownloader(), this.options);
return this.decoder.decode(decodingInfo);
}
这时候调用的是decoder.decode的方法,decoder又是从engine.configuration.decoder来的,这个在ImageLoader中就已经赋值了,是new ImageLoaderEngine(ImageLoaderConfiguration),所以此时的engine就是从ImageLoader中的ImageLoaderConfiguration中得到的。而我们在一般使用时候没有对ImageLoaderConfiguration的decoder赋值,因此在ImageLoaderConfiguration中执行下面逻辑:
if(this.decoder == null) {
this.decoder = DefaultConfigurationFactory.createImageDecoder(this.writeLogs);
}
继续
public static ImageDecoder createImageDecoder(boolean loggingEnabled) {
return new BaseImageDecoder(loggingEnabled);
}
public Bitmap decode(ImageDecodingInfo decodingInfo) throws IOException {
InputStream imageStream = this.getImageStream(decodingInfo);
if(imageStream == null) {
L.e("No stream for image [%s]", new Object[]{decodingInfo.getImageKey()});
return null;
} else {
Bitmap decodedBitmap;
BaseImageDecoder.ImageFileInfo imageInfo;
try {
imageInfo = this.defineImageSizeAndRotation(imageStream, decodingInfo);
imageStream = this.resetStream(imageStream, decodingInfo);
Options decodingOptions = this.prepareDecodingOptions(imageInfo.imageSize, decodingInfo);
decodedBitmap = BitmapFactory.decodeStream(imageStream, (Rect)null, decodingOptions);
} finally {
IoUtils.closeSilently(imageStream);
}
if(decodedBitmap == null) {
L.e("Image can\'t be decoded [%s]", new Object[]{decodingInfo.getImageKey()});
} else {
decodedBitmap = this.considerExactScaleAndOrientatiton(decodedBitmap, decodingInfo, imageInfo.exif.rotation, imageInfo.exif.flipHorizontal);
}
return decodedBitmap;
}
}
其核心代码:
imageStream = this.resetStream(imageStream, decodingInfo);
Options decodingOptions = this.prepareDecodingOptions(imageInfo.imageSize, decodingInfo);
decodedBitmap = BitmapFactory.decodeStream(imageStream, (Rect)null, decodingOptions);
继续查看resetStream这个方法:
protected InputStream resetStream(InputStream imageStream, ImageDecodingInfo decodingInfo) throws IOException {
try {
imageStream.reset();
} catch (IOException var4) {
IoUtils.closeSilently(imageStream);
imageStream = this.getImageStream(decodingInfo);
}
return imageStream;
}
继续看getImageStream
protected InputStream getImageStream(ImageDecodingInfo decodingInfo) throws IOException {
return decodingInfo.getDownloader().getStream(decodingInfo.getImageUri(), decodingInfo.getExtraForDownloader());
}
就这样我们找到了ImageDownloader,点进去我们就能看到的http等等,同样由于之前并没有在ImageLoaderConfiguration中进行处理,因此,还是使用默认的:
if(this.downloader == null) {
this.downloader = DefaultConfigurationFactory.createImageDownloader(this.context);
}
因此,我们只需要重写一个类,将防盗链规则加进去,在配置ImageLoaderConfiguration时添加上就OK了。
##2.添加防盗链逻辑
通过上面的源码跟踪我们可以看到网络请求放在了ImageDownloader这个类中,而这个类我们可以通过ImageLoaderConfiguration设置不同的ImageDownloader,这样我们就可以重写一个ImageDownloader,在其网络请求部分加上我们的防盗链处理:
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Bitmap;
import android.media.ThumbnailUtils;
import android.net.Uri;
import android.os.Build;
import android.webkit.MimeTypeMap;
import com.nostra13.universalimageloader.core.assist.ContentLengthInputStream;
import com.nostra13.universalimageloader.core.download.BaseImageDownloader;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
public class SafeImageDownloader extends BaseImageDownloader {
public SafeImageDownloader(Context context) {
super(context);
}
public SafeImageDownloader(Context context, int connectTimeout, int readTimeout) {
super(context, connectTimeout, readTimeout);
}
protected HttpURLConnection createConnection(String url, Object extra) throws IOException {
String encodedUrl = Uri.encode(url, "@#&=*+-_.,:!?()/~\'%");
HttpURLConnection conn = (HttpURLConnection) (new URL(encodedUrl)).openConnection();
conn.setConnectTimeout(this.connectTimeout);
conn.setReadTimeout(this.readTimeout);
conn.setRequestMethod("GET");
conn.setRequestProperty("User-Agent", "FullCat");
return conn;
}
protected InputStream getStreamFromFile(String imageUri, Object extra) throws IOException {
String filePath = Scheme.FILE.crop(imageUri);
if (this.isVideoFileUri(imageUri)) {
return this.getVideoThumbnailStream(filePath);
} else {
BufferedInputStream imageStream = new BufferedInputStream(new FileInputStream(filePath), '耀');
return new ContentLengthInputStream(imageStream, (int) (new File(filePath)).length());
}
}
private boolean isVideoFileUri(String uri) {
String extension = MimeTypeMap.getFileExtensionFromUrl(uri);
String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
return mimeType != null && mimeType.startsWith("video/");
}
@TargetApi(8)
private InputStream getVideoThumbnailStream(String filePath) {
if (Build.VERSION.SDK_INT >= 8) {
Bitmap bitmap = ThumbnailUtils.createVideoThumbnail(filePath, 2);
if (bitmap != null) {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.PNG, 0, bos);
return new ByteArrayInputStream(bos.toByteArray());
}
}
return null;
}
}
上面的conn.setRequestProperty(“User-Agent”, “FullCat”);就是这个项目中的防盗链处理,同样我们也可以在这个位置添加请求参数,设置header等等。接着,我们需要把这个ImageDownloader设置到ImageLoaderConfiguration中:
public static void displayImage(String uri, ImageView imageView, DisplayImageOptions options) {
ImageLoaderConfiguration configuration = new ImageLoaderConfiguration.Builder(imageView.getContext())
.imageDownloader(new SafeImageDownloader(imageView.getContext()))
.build();
ImageLoader imageLoader = ImageLoader.getInstance();
imageLoader.destroy();
imageLoader.init(configuration);
imageLoader.displayImage(uri, imageView, options);
}
OK,大功告成。