使用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卡中,使用的是Facebook的conceal进行加密. 接下来就悲剧了,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

  1. 自定义的加密文件加载类,用于加载加密文件,这样自定义的数据加载类我们不需要管理缓存问题,Glide会为我们处理好.
public class ImageDataFetcher implements DataFetcher {

  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 {

  public ImageLoader() {
  }

  @Override
  public DataFetcher getResourceFetcher(Picture model, int width, int height) {
    return new ImageDataFetcher(model);
  }

  /**
   * ModelLoader工厂,在向Glide注册自定义ModelLoader时使用到
   */
  public static class Factory implements ModelLoaderFactory {

    @Override
    public ModelLoader 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

你可能感兴趣的:(使用Glide 3.x 自定义GlideModel和 conceal 1.x 对Android[图片]文件加密与解密)