使用Glide 3.x 自定义GlideModel和 conceal 1.x 对Android[图片]文件加密与解密

项目地址GlideV3

刚写完这篇文章突然想起来是不是可以使用接口来代替Picture这样可以更加通用,于是试了一下果然可以.所以读者们可以将Picture替换成IPicture接口.

public interface IPicture {
  String getFileName();
}

本项目使用的Model是Picture,使用Glide.with().from(Picture.class).load()加载图片的话,网络图片等都将通过这个自定义GlideModel加载,所以加载网络图片时请继续使用Glide.with().load()
在写本文章的时候Glide版本还是3.7.x,Glide 4.x 实现同样的功能.将使用另一篇博文.

发现问题

由于Glide相对于picasso有更多的优势,所以最近把项目图片加载库切换到了Glide,本地图片存放在外置SD卡中,使用的是Facebookconceal进行加密. 接下来就悲剧了,Glide 只能加载File,byte[],url,等类型的数据,但是conceal 只能的到解密数据流, 所以必须将InputStream解析成byte[]才能被Glide加载.
解析字节流在UI线程(当然你也可以将它放到非UI线程)中执行,每次刷新数据(如选中操作,上下滑动等)都会导致所有图片的数据重新从文件系统(加载-解密-解析-显示)整个过程,导致用户界面卡顿.

代码如下 :

InputStream inputStream = new FileInputStream(file);
// 解密并转化为字节数组,由于这一段在UI线程中所以卡顿很严重,而且每次刷新数据都会重新加载-解密-解析-显示
byte[] data = input2byte(crypto.getCipherInputStream(inputStream, entity));
// 加载字节数组到ImageView
Glide.with(Context).load(data).into(image);

解决问题

怎么样才能做到像Glide加载普通文件一样,在异步的线程里加载图片,而且只需要一次加载解析,其他情况在缓存中查找呢? 查找了相关资料发现Glide 还能够加载ModelType类型数据,ModelType类型的加载需要自定义GlideModule并在清单文件中注册,首先来定义我们的GlideModule并注册.

  1. 自定义GlideModule,由于我的数据的存储路径是可以直接拿到的,因此我的modelClass类型为Picture类型,也可以定义为其他类型 比如Picture等等
public class CustomGlideModule implements GlideModule {

  @Override public void applyOptions(Context context, GlideBuilder builder) {
    // 设置别的get/set tag id,以免占用View默认的
    ViewTarget.setTagId(R.id.glide_tag_id);
    
   // 图片质量低,够用就行
   builder.setDecodeFormat(DecodeFormat.PREFER_RGB_565);
  }

  @Override public void registerComponents(Context context, Glide glide) {
    // 注册我们的ImageLoader,定义modelClass类型为Picture因为加密文件的路径能够直接拿到,也可以定义为其他类型 比如Picture等等
    // 第二个参数是我们要将第一个参数数据映射到的类型,这里的意思是(将字符串类型的文件地址,转化为输入流)
    // 第三个参数是我们的Loader(加载器)的工厂方法,决定我们使用什么加载器来加载文件
    glide.register(Picture.class, InputStream.class, new ImageLoader.Factory());
  }
}
  1. 在清单文件中注册我们自定义GlideModule
<meta-data
    android:name="xxx.glide.CustomGlideModule"
        android:value="GlideModule" />
  1. 自定义的加密文件加载类,用于加载加密文件,这样自定义的数据加载类我们不需要管理缓存问题,Glide会为我们处理好.
public class ImageDataFetcher implements DataFetcher<InputStream> {

  private volatile boolean mIsCanceled;
  private final Picture mFilePath;
  private InputStream mInputStream;

  public ImageDataFetcher(Picture filePath) {
    mFilePath = filePath;
  }

  /**
   * 这个方法是在非UI线程中执行,我们利用此方法来加载我们的加密数据
   *
   * @param priority
   * @throws Exception
   */
  @Override public InputStream loadData(Priority priority) throws Exception {
    if (mIsCanceled) {
      return null;
    }
    mInputStream = fetchStream(mFilePath.getFileName());
    return mInputStream;
  }

  /**
   * 返回解密后的数据流
   *
   * @param file 文件名
   * @return inputStream
   */
  private InputStream fetchStream(String file) {
    InputStream inputStream = new FileInputStream(new File(file));
    // 返回解密数据流
    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线程中调用,取消加载任务
   */
  @Override public void cancel() {
    mIsCanceled = true;
  }
}
  1. 图片加载器,重载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注册自定义ModelLoader时使用到
   */
  public static class Factory implements ModelLoaderFactory<Picture, InputStream> {

    @Override
    public ModelLoader<Picture, InputStream> build(Context context, GenericLoaderFactory factories) {
      // 返回ImageLoader对象
      return new ImageLoader();
    }

    @Override public void teardown() {

    }
  }
}
  1. 最后我们可以这样使用了
Glide.with(mActivity)
        .from(Picture.class) // 设置数据源类型为我们的ImageFid
        .fitCenter()
        .diskCacheStrategy(DiskCacheStrategy.RESULT) // 设置本地缓存,缓存源文件和目标图像
        .placeholder(R.drawable.ic_default_image)
        .load(pictures.get(position).getFilePath())
        .into(photoView);
  1. 祭出我们的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("请初始化.....");
    }
  }

  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;
  }

  /**
   * 保存字节流
   *
   * @param data 数据
   */
  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);
    }
  }
}
注意缓存策略如果对数据保密要求严格的话请设置为NONE

别忘了引入响应的库文件

dependencies {
...
    compile 'com.facebook.conceal:conceal:1.1.2@aar'
    compile 'com.github.bumptech.glide:glide:+'
}

项目地址GlideV3

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,392评论 25 707
  • 一、简介 在泰国举行的谷歌开发者论坛上,谷歌为我们介绍了一个名叫Glide的图片加载库,作者是bumptech。这...
    天天大保建阅读 7,450评论 2 28
  • 做生意一忌:没胆量 俗话说,只要有七分把握便可行动,余下的三分把握靠你争取遇事下不了决心,错过时机不得利,要知道经...
    时间轴的猜想阅读 1,189评论 0 2
  • 杭州银行成立于1996年9月,总部位于杭州。目前,全行拥有189家分支机构,网点覆盖长三角、珠三角、环渤海湾等...
    HZbank阅读 2,247评论 0 0
  • 到底什么才是对妈妈最好的?妈妈到底想要什么?是呆在老家看孙子吗?我的想法是什么?我的想法是一:八宝留在我们身边。二...
    八宝妈阅读 107评论 0 0