ImageLoader加密解密共存

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],發現記錄一下,會有所作用,因爲好記性不如爛筆頭。
發佈了39 篇原創文章 · 獲贊 20 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章