天天看点

09 Gallery 源码-相册AlbumPage的缩略图生成

0. 原文拜读

https://blog.csdn.net/lin20044140410/article/details/77454079

1 AlbumPage.AlbumSlotRenderer 缩略图的配置

AlbumPage数据的加载,跟AlbumSetPage的数据加载类似,不同的地方在于AlbumPage要把所有的图片都生成缩略图,而AlbumSetPage是取相册中的第一张图片作为封面。

package com.android.gallery3d.app;

import com.android.gallery3d.ui.AlbumSlotRenderer;

public class AlbumPage extends ActivityState implements GalleryActionBar.ClusterRunner,ButtonControlsHandler.Delegate,... {
    
    // AlbumSlotRenderer负责图片缩略图的生成工作
    private AlbumSlotRenderer mAlbumView;
    
    private void initializeViews() {
        ...
        // /相册界面的配置项,config定义了各个页面的配置,行、列、边距等。
        mConfig = Config.AlbumPage.get(mActivity);
        mConfigList = Config.AlbumPageList.get(mActivity);

        if (mViewType) {
            // 相册页
            mSlotView = new SlotView(mActivity, mConfig.slotViewSpec);
            
            //对AlbumSlotRenderer进行配置
            mAlbumView = new AlbumSlotRenderer(mActivity, mSlotView,
                    mConfig.labelSpec, mSelectionManager, mConfig.placeholderColor, mViewType);
        } else {
            mSlotView = new SlotView(mActivity, mConfigList.slotViewSpec);

            //对AlbumSlotRenderer进行配置
            mAlbumView = new AlbumSlotRenderer(mActivity, mSlotView,
                    mConfigList.labelSpec, mSelectionManager,
                    mConfig.placeholderColor, mViewType);
        }
        
        mSlotView.setSlotRenderer(mAlbumView);
        //添加相册页面到画布上
        mRootPane.addComponent(mSlotView);
    }
    
}

           

2. 数据源的加载 AlbumPage.initializeData

package com.android.gallery3d.app;

import com.android.gallery3d.ui.AlbumSlotRenderer;

public class AlbumPage extends ActivityState implements GalleryActionBar.ClusterRunner,ButtonControlsHandler.Delegate,... {

    // 缩略图适配器
    private AlbumDataLoader mAlbumDataAdapter;
    // 缩略图生成
    private AlbumSlotRenderer mAlbumView;

    private void initializeData(Bundle data) {
        ...
        mAlbumDataAdapter = new AlbumDataLoader(mActivity, mMediaSet);
        mAlbumDataAdapter.setLoadingListener(new MyLoadingListener());
        
        // 导入了数据源,mAlbumDataAdapter是加载数据的适配类对象
        mAlbumView.setModel(mAlbumDataAdapter);
    }
           

3. AlbumSlotRenderer(mAlbumView).setModel

package com.android.gallery3d.ui;

public class AlbumSlotRenderer extends AbstractSlotRenderer {
    
    // 装载适配器
    public void setModel(AlbumDataLoader model) {
        if (mDataWindow != null) {
            mDataWindow.setListener(null);
            mSlotView.setSlotCount(0);
            mDataWindow = null;
        }
        if (model != null) {
            // 创建缩略图窗口
            mDataWindow = new AlbumSlidingWindow(mActivity, model, CACHE_SIZE,
                    mLabelSpec, mIsGridViewShown);
            // 缩略图窗口事件监听
            mDataWindow.setListener(new MyDataModelListener());
            mSlotView.setSlotCount(model.size());

        }
    }
    
    private class MyDataModelListener implements AlbumSlidingWindow.Listener {
        @Override
        public void onContentChanged() {
            mSlotView.invalidate();

        }

        @Override
        public void onSizeChanged(int size) {
            mSlotView.setSlotCount(size);
            mSlotView.invalidate();

        }
    }
}
           

4. 点击某个相册事件

我们点击某个相册,窗口会被创建、状态会发生变化,然后更新窗口的可见区域,会间接调用到 AlbumSlotRenderer.onVisibleRangeChanged()。

package com.android.gallery3d.ui;

public class AlbumSlotRenderer extends AbstractSlotRenderer {

    private AlbumSlidingWindow mDataWindow;

    @Override
    public void onVisibleRangeChanged(int visibleStart, int visibleEnd) {
        if (mDataWindow != null) {
            mDataWindow.setActiveWindow(visibleStart, visibleEnd);
        }
    }
           

5. AlbumSlidingWindow.setActiveWindow

package com.android.gallery3d.ui;

public class AlbumSlidingWindow implements AlbumDataLoader.DataListener {


   public void setActiveWindow(int start, int end) {
        if (!(start <= end && end - start <= mData.length && end <= mSize)) {
            Utils.fail("%s, %s, %s, %s", start, end, mData.length, mSize);
        }
        AlbumEntry data[] = mData;

        // 这里的start值通常是0,end值根据窗口的可见区域有个计算,竖屏最多16张,横屏12张。
        mActiveStart = start;
        mActiveEnd = end;

        int contentStart = Utils.clamp((start + end) / 2 - data.length / 2, 0,
                Math.max(0, mSize - data.length));
        int contentEnd = Math.min(contentStart + data.length, mSize);
        //设置窗口,每张图片都对应一个窗口
        setContentWindow(contentStart, contentEnd);
        updateTextureUploadQueue();
        if (mIsActive)
            //更新所有图片生成缩略图的请求,即启动生成缩略图的任务。
            updateAllImageRequests();
    }
           
  • 5.1 查看下 AlbumSlidingWindow.setContentWindow()
private void setContentWindow(int contentStart, int contentEnd) {
        if (contentStart == mContentStart && contentEnd == mContentEnd)
            return;

        if (!mIsActive) {
            mContentStart = contentStart;
            mContentEnd = contentEnd;
            mSource.setActiveWindow(contentStart, contentEnd);
            return;
        }

        if (contentStart >= mContentEnd || mContentStart >= contentEnd) {
            for (int i = mContentStart, n = mContentEnd; i < n; ++i) {
                freeSlotContent(i);
            }
            mSource.setActiveWindow(contentStart, contentEnd);
            for (int i = contentStart; i < contentEnd; ++i) {
                //准备每个窗口的数据
                prepareSlotContent(i);
            }
        } else {
            for (int i = mContentStart; i < contentStart; ++i) {
                freeSlotContent(i);
            }
            for (int i = contentEnd, n = mContentEnd; i < n; ++i) {
                freeSlotContent(i);
            }
            mSource.setActiveWindow(contentStart, contentEnd);
            for (int i = contentStart, n = mContentStart; i < n; ++i) {
                prepareSlotContent(i);
            }
            for (int i = mContentEnd; i < contentEnd; ++i) {
                prepareSlotContent(i);
            }
        }

        mContentStart = contentStart;
        mContentEnd = contentEnd;
    }
           
  • 5.2 查看 AlbumSlidingWindow.prepareSlotContent()
package com.android.gallery3d.ui;

public class AlbumSlidingWindow implements AlbumDataLoader.DataListener {

    private void prepareSlotContent(int slotIndex) {
        AlbumEntry entry = new AlbumEntry();
        //获取图片标识
        MediaItem item = mSource.get(slotIndex); // item could be null;
        entry.item = item;
        entry.name = (item == null) ? null : item.getName();
        entry.mediaType = (item == null) ? MediaItem.MEDIA_TYPE_UNKNOWN
                : entry.item.getMediaType();
        entry.path = (item == null) ? null : item.getPath();
        entry.isFavorite = item != null && item.isFavorite; //ZDQ 2018/12/28 赋值
        entry.rotation = (item == null) ? 0 : item.getRotation();
        //创建生成缩略图的任务 
        entry.contentLoader = new ThumbnailLoader(slotIndex, entry.item);
        if (!mViewType) {
            if (entry.labelLoader != null) {
                entry.labelLoader.recycle();
                entry.labelLoader = null;
                entry.labelTexture = null;
            }
            if (entry.name != null) {
                entry.labelLoader = new AlbumLabelLoader(slotIndex, entry.name);
            }
        }
        mData[slotIndex % mData.length] = entry;
    }
           
  • 5.3 开始生成缩略图 ThumbnailLoader(slotIndex, entry.item)
package com.android.gallery3d.ui;

public class AlbumSlidingWindow implements AlbumDataLoader.DataListener {

    private class ThumbnailLoader extends BitmapLoader {
        private final int mSlotIndex;
        private final MediaItem mItem;

        public ThumbnailLoader(int slotIndex, MediaItem item) {
            mSlotIndex = slotIndex;
            mItem = item;
        }

        @Override
        protected Future<Bitmap> submitBitmapTask(FutureListener<Bitmap> l) {
            //开始生成缩略图的任务
            return mThreadPool.submit(
                    mItem.requestImage(MediaItem.TYPE_MICROTHUMBNAIL), this);
        }
           
  • 5.4 这里会为每个数据源创建生成缩略图的的请求,并提交到线程池,

    根据 继承关系 LocalImage extends LocalMediaItem extends MediaItem,这里假设是LocalImage本地数据的处理

package com.android.gallery3d.data;

// LocalImage represents an image in the local storage.
public class LocalImage extends LocalMediaItem {
    
    // LocalImageRequest 的父类 ImageCacheRequest,是一个类似于callable的异步任务
    public static class LocalImageRequest extends ImageCacheRequest {
        private String mLocalFilePath;
        ...
    }
    
    @Override
    public Job<Bitmap> requestImage(int type) {
        return new LocalImageRequest(mApplication, mPath, dateModifiedInSec,
                type, filePath, mimeType);
    }
    
}
           

查看下 ImageCacheRequest

package com.android.gallery3d.data;

abstract class ImageCacheRequest implements Job<Bitmap> {

    @Override
    public Bitmap run(JobContext jc) {

        //先获取cache服务,从buffer中查询是否已经缓存过缩略图,因为生成过一次后会添
        //加到缓存,下次不需要再生成。
        ImageCacheService cacheService = mApplication.getImageCacheService();

        BytesBuffer buffer = MediaItem.getBytesBufferPool().get();
        try {
            boolean found = cacheService.getImageData(mPath, mTimeModified, mType, buffer);
            if (jc.isCancelled()) return null;
            if (found) {
                BitmapFactory.Options options = new BitmapFactory.Options();
                options.inPreferredConfig = Bitmap.Config.ARGB_8888;
                Bitmap bitmap;
                
                //如果查到了,直接构建bitmap
                if (mType == MediaItem.TYPE_MICROTHUMBNAIL) {
                    bitmap = DecodeUtils.decodeUsingPool(jc,
                            buffer.data, buffer.offset, buffer.length, options);
                } else {
                    bitmap = DecodeUtils.decodeUsingPool(jc,
                            buffer.data, buffer.offset, buffer.length, options);
                }
                if (bitmap == null && !jc.isCancelled()) {
                    Log.w(TAG, "decode cached failed " + debugTag());
                }
                return bitmap;
            }
        } finally {
            MediaItem.getBytesBufferPool().recycle(buffer);
        }

        //没有查到,会调用子类的onDecodeOriginal()方法进行解码
        Bitmap bitmap = onDecodeOriginal(jc, mType);
        if (jc.isCancelled()) return null;

        if (bitmap == null) {
            Log.w(TAG, "decode orig failed " + debugTag());
            return null;
        }

        //把缩略图添加到缓存,把bitmap压缩成byte数组,存储到cacheService
        if (mType == MediaItem.TYPE_MICROTHUMBNAIL) {
            bitmap = BitmapUtils.resizeAndCropCenter(bitmap, mTargetSize, true);
        } else {
            bitmap = BitmapUtils.resizeDownBySideLength(bitmap, mTargetSize, true);
        }
        if (jc.isCancelled()) return null;

        byte[] array = BitmapUtils.compressToBytes(bitmap);
        if (jc.isCancelled()) return null;

        cacheService.putImageData(mPath, mTimeModified, mType, array);
        return bitmap;
    }

}
           
  • 5.5 调用onDecodeOriginal()解码生成缩略图

    Bitmap bitmap = onDecodeOriginal(jc, mType)

package com.android.gallery3d.data;

// LocalImage represents an image in the local storage.
public class LocalImage extends LocalMediaItem {

    public static class LocalImageRequest extends ImageCacheRequest {
        private String mLocalFilePath;
        
        @Override
        public Bitmap onDecodeOriginal(JobContext jc, final int type) {
            ....
            // try to decode from JPEG EXIF
            if (type == MediaItem.TYPE_MICROTHUMBNAIL) {
                //直接从jpeg的exif库函数里面获取缩略图
                ExifInterface exif = new ExifInterface();
                byte[] thumbData = null;
                try {
                    exif.readExif(mLocalFilePath);
                    thumbData = exif.getThumbnail();
                } catch (FileNotFoundException e) {
                    Log.w(TAG, "failed to find file to read thumbnail: " + mLocalFilePath);
                } catch (IOException e) {
                    Log.w(TAG, "failed to get thumbnail from: " + mLocalFilePath);
                }
                if (thumbData != null) {
                    //获取成功,就直接生成bitmap
                    Bitmap bitmap = DecodeUtils.decodeIfBigEnough(
                            jc, thumbData, options, targetSize);
                    if (bitmap != null) return bitmap;
                }
            }
            //最后拿不到现成的,才调用方法进行解码
            return DecodeUtils.decodeThumbnail(jc, mLocalFilePath, options, targetSize, type);
        }
        ...
    }
           
  • 5.6 缩略图解码方法

    DecodeUtils.decodeThumbnail

package com.android.gallery3d.data;

public class DecodeUtils {

    public static Bitmap decodeThumbnail(
            JobContext jc, String filePath, Options options, int targetSize, int type) {
        FileInputStream fis = null;
        ...
        return decodeThumbnail(jc, fd, options, targetSize, type);
        ...
    }
    
    public static Bitmap decodeThumbnail(
        ...
        Bitmap result = BitmapFactory.decodeFileDescriptor(fd, null, options);
        ...
        BitmapUtils.resizeBitmapByScale(result, scale, true)
        ...
        // 绘制缩略图
        if (scale <= 0.5) result = BitmapUtils.resizeBitmapByScale(result, scale, true);
    )
           

绘制缩略图

package com.android.gallery3d.common;

public class BitmapUtils {

    public static Bitmap resizeBitmapByScale(
            Bitmap bitmap, float scale, boolean recycle) {
        int width = Math.round(bitmap.getWidth() * scale);
        int height = Math.round(bitmap.getHeight() * scale);
        if (width == bitmap.getWidth()
                && height == bitmap.getHeight()) return bitmap;
        Bitmap target = Bitmap.createBitmap(width, height, getConfig(bitmap));
        Canvas canvas = new Canvas(target);
        //这里写到buffer后,就返回了,整个过程没有看到再去写文件流
        canvas.scale(scale, scale);
        Paint paint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.DITHER_FLAG);
        canvas.drawBitmap(bitmap, 0, 0, paint);
        if (recycle) bitmap.recycle();
        return target;
    }
           

继续阅读