剛寫(xiě)完這篇文章突然想起來(lái)是不是可以使用接口來(lái)代替Picture這樣可以更加通用,于是試了一下果然可以.所以讀者們可以將Picture替換成IPicture接口.
public interface IPicture {
String getFileName();
}
本項(xiàng)目使用的Model是Picture,使用Glide.with().from(Picture.class).load()加載圖片的話,網(wǎng)絡(luò)圖片等都將通過(guò)這個(gè)自定義GlideModel加載,所以加載網(wǎng)絡(luò)圖片時(shí)請(qǐng)繼續(xù)使用Glide.with().load()
在寫(xiě)本文章的時(shí)候Glide版本還是3.7.x,Glide 4.x 實(shí)現(xiàn)同樣的功能.將使用另一篇博文.
發(fā)現(xiàn)問(wèn)題
由于Glide相對(duì)于picasso有更多的優(yōu)勢(shì),所以最近把項(xiàng)目圖片加載庫(kù)切換到了Glide,本地圖片存放在外置SD卡中,使用的是Facebook的conceal進(jìn)行加密. 接下來(lái)就悲劇了,Glide 只能加載File,byte[],url,等類型的數(shù)據(jù),但是conceal 只能的到解密數(shù)據(jù)流, 所以必須將InputStream解析成byte[]才能被Glide加載.
解析字節(jié)流在UI線程(當(dāng)然你也可以將它放到非UI線程)中執(zhí)行,每次刷新數(shù)據(jù)(如選中操作,上下滑動(dòng)等)都會(huì)導(dǎo)致所有圖片的數(shù)據(jù)重新從文件系統(tǒng)(加載-解密-解析-顯示)整個(gè)過(guò)程,導(dǎo)致用戶界面卡頓.
代碼如下 :
InputStream inputStream = new FileInputStream(file);
// 解密并轉(zhuǎn)化為字節(jié)數(shù)組,由于這一段在UI線程中所以卡頓很嚴(yán)重,而且每次刷新數(shù)據(jù)都會(huì)重新加載-解密-解析-顯示
byte[] data = input2byte(crypto.getCipherInputStream(inputStream, entity));
// 加載字節(jié)數(shù)組到ImageView
Glide.with(Context).load(data).into(image);
解決問(wèn)題
怎么樣才能做到像Glide加載普通文件一樣,在異步的線程里加載圖片,而且只需要一次加載解析,其他情況在緩存中查找呢? 查找了相關(guān)資料發(fā)現(xiàn)Glide 還能夠加載ModelType類型數(shù)據(jù),ModelType類型的加載需要自定義GlideModule并在清單文件中注冊(cè),首先來(lái)定義我們的GlideModule并注冊(cè).
- 自定義
GlideModule,由于我的數(shù)據(jù)的存儲(chǔ)路徑是可以直接拿到的,因此我的modelClass類型為Picture類型,也可以定義為其他類型 比如Picture等等
public class CustomGlideModule implements GlideModule {
@Override public void applyOptions(Context context, GlideBuilder builder) {
// 設(shè)置別的get/set tag id,以免占用View默認(rèn)的
ViewTarget.setTagId(R.id.glide_tag_id);
// 圖片質(zhì)量低,夠用就行
builder.setDecodeFormat(DecodeFormat.PREFER_RGB_565);
}
@Override public void registerComponents(Context context, Glide glide) {
// 注冊(cè)我們的ImageLoader,定義modelClass類型為Picture因?yàn)榧用芪募穆窂侥軌蛑苯幽玫?也可以定義為其他類型 比如Picture等等
// 第二個(gè)參數(shù)是我們要將第一個(gè)參數(shù)數(shù)據(jù)映射到的類型,這里的意思是(將字符串類型的文件地址,轉(zhuǎn)化為輸入流)
// 第三個(gè)參數(shù)是我們的Loader(加載器)的工廠方法,決定我們使用什么加載器來(lái)加載文件
glide.register(Picture.class, InputStream.class, new ImageLoader.Factory());
}
}
- 在清單文件中注冊(cè)我們自定義GlideModule
<meta-data
android:name="xxx.glide.CustomGlideModule"
android:value="GlideModule" />
- 自定義的加密文件加載類,用于加載加密文件,這樣自定義的數(shù)據(jù)加載類我們不需要管理緩存問(wèn)題,Glide會(huì)為我們處理好.
public class ImageDataFetcher implements DataFetcher<InputStream> {
private volatile boolean mIsCanceled;
private final Picture mFilePath;
private InputStream mInputStream;
public ImageDataFetcher(Picture filePath) {
mFilePath = filePath;
}
/**
* 這個(gè)方法是在非UI線程中執(zhí)行,我們利用此方法來(lái)加載我們的加密數(shù)據(jù)
*
* @param priority
* @throws Exception
*/
@Override public InputStream loadData(Priority priority) throws Exception {
if (mIsCanceled) {
return null;
}
mInputStream = fetchStream(mFilePath.getFileName());
return mInputStream;
}
/**
* 返回解密后的數(shù)據(jù)流
*
* @param file 文件名
* @return inputStream
*/
private InputStream fetchStream(String file) {
InputStream inputStream = new FileInputStream(new File(file));
// 返回解密數(shù)據(jù)流
return ConcealUtil.getCipherInputStream(inputStream);
}
/**
* 處理完成之后清理工作
*/
@Override public void cleanup() {
if (mInputStream != null) {
try {
mInputStream.close();
} catch (IOException e) {
Log.e("Glide", "Glide", e);
} finally {
mInputStream = null;
}
}
}
/**
* 該文件的唯一ID
*/
@Override public String getId() {
return mFilePath.getFileName();
}
/**
* 在UI線程中調(diào)用,取消加載任務(wù)
*/
@Override public void cancel() {
mIsCanceled = true;
}
}
- 圖片加載器,重載getResourceFetcher方法,返回我們剛剛定義好的圖片加載類.
public class ImageLoader implements ModelLoader<Picture, InputStream> {
public ImageLoader() {
}
@Override
public DataFetcher<InputStream> getResourceFetcher(Picture model, int width, int height) {
return new ImageDataFetcher(model);
}
/**
* ModelLoader工廠,在向Glide注冊(cè)自定義ModelLoader時(shí)使用到
*/
public static class Factory implements ModelLoaderFactory<Picture, InputStream> {
@Override
public ModelLoader<Picture, InputStream> build(Context context, GenericLoaderFactory factories) {
// 返回ImageLoader對(duì)象
return new ImageLoader();
}
@Override public void teardown() {
}
}
}
- 最后我們可以這樣使用了
Glide.with(mActivity)
.from(Picture.class) // 設(shè)置數(shù)據(jù)源類型為我們的ImageFid
.fitCenter()
.diskCacheStrategy(DiskCacheStrategy.RESULT) // 設(shè)置本地緩存,緩存源文件和目標(biāo)圖像
.placeholder(R.drawable.ic_default_image)
.load(pictures.get(position).getFilePath())
.into(photoView);
- 祭出我們的Util代碼
public class ConcealUtil {
private static Crypto crypto = null;
private static Entity entity = null;
/**
* 初始化
*
* @param context context
* @param e 密碼
*/
public static void init(Context context, String e) {
entity = Entity.create(e);
crypto = new Crypto(new SharedPrefsBackedKeyChain(context, CryptoConfig.KEY_256),
new SystemNativeCryptoLibrary(), CryptoConfig.KEY_256);
if (!crypto.isAvailable()) {
destroy();
}
}
public static void destroy() {
crypto = null;
entity = null;
}
private static void check() {
if (crypto == null || entity == null) {
throw new RuntimeException("請(qǐng)初始化.....");
}
}
public static OutputStream getCipherOutputStream(File file) {
if (file.exists()) return null;
try {
OutputStream fileStream = new FileOutputStream(file);
return getCipherOutputStream(fileStream);
} catch (IOException e) {
Timber.e(e);
}
return null;
}
public static OutputStream getCipherOutputStream(OutputStream fileStream) {
check();
try {
return crypto.getCipherOutputStream(fileStream, entity);
} catch (IOException e) {
Timber.e(e);
} catch (CryptoInitializationException e) {
Timber.e(e);
} catch (KeyChainException e) {
Timber.e(e);
}
return null;
}
public static InputStream getCipherInputStream(String file) {
return getCipherInputStream(new File(file));
}
public static InputStream getCipherInputStream(File file) {
check();
if (!file.exists()) return null;
try {
InputStream inputStream = new FileInputStream(file);
return crypto.getCipherInputStream(inputStream, entity);
} catch (FileNotFoundException e) {
Timber.e(e);
} catch (KeyChainException e) {
Timber.e(e);
} catch (CryptoInitializationException e) {
Timber.e(e);
} catch (IOException e) {
Timber.e(e);
}
return null;
}
/**
* 保存字節(jié)流
*
* @param data 數(shù)據(jù)
*/
public static void saveFile(byte data[], String path) {
String fileName = System.currentTimeMillis() + ".jpg";
File image = new File(path, fileName);
try {
OutputStream outStream = getCipherOutputStream(image);
if (outStream != null) {
outStream.write(data);
outStream.flush();
outStream.close();
}
} catch (IOException e) {
Timber.e(e);
}
}
}
注意緩存策略如果對(duì)數(shù)據(jù)保密要求嚴(yán)格的話請(qǐng)?jiān)O(shè)置為NONE
別忘了引入響應(yīng)的庫(kù)文件
dependencies {
...
compile 'com.facebook.conceal:conceal:1.1.2@aar'
compile 'com.github.bumptech.glide:glide:+'
}