Gallery数据管理和数据加载分析

1.  我们在打开的Gallery时候,就会把相关的相册信息给我们看,其中的信息有:

 相册封面缩略图,相册的文件夹名称,相册包含的相片数量。如图:



Gallery是采用MVC框架,界面的显示和数据管理逻辑是分开的,那么这些数据是怎么获取和怎么管理的,本文为你揭晓相册的数据加载和管理。

2.  上文我们提到,界面包含有数据加载一般有AlbumSetPage 和AlbumPage这两个界面,所以会对这两个界面进行分析。

分析:

1.  AlbumSetPage界面数据加载分析

查看AlbumSetPage.java类文件,通常界面的显示一般会有一个适配器的,这里是AlbumSetDataLoader(适配器), 而且适配器一般是数据源关联的,这里的数据源是

MediaSet(数据的集合),好了有了数据之后,就要显示真正的界面,界面就是由AlbumSetSlotRenderer这类对界面进行沟通的桥梁。

活动图如下所示:



Gallery有各种各样的数据源:

例如:本地的(local)、gmail同步的(picasa)、连接电脑的媒体传输的(mtp)、混合各种类型的(combo)等。

   这些数据源是由DataManager来管理的。DataManager中初始化所有的数据源(LocalSource,PicasaSource, MtpSource, ComboSource, ClusterSource, FilterSource, UriSource,SnailSource), 将数据源放到一个Hash表中,提供存取操作,MediaSource负责管理数据集,以LoacalSource为例,从他的createMediaObject函数可以看出,根据路径他可以创建出LocalMediaSet,LocalMedia, LocalImage, LocalVideo等。

数据加载流程如图:



2.1 AlbumSetPage数据流程

1.  我们进入Gallery,首先会进入AlbumSetPage类,先从AlbumSetPage.java的onCreate方法入手。

public void onCreate(Bundle data, Bundle restoreState) {

 

        initializeViews();// 初始化view,AlbumSetSlotRenderer和SlotView初始化配置

        initializeData(data);

         ………

       

            }

private void initializeData(Bundle data) {

 

    String mediaPath = data.getString(AlbumSetPage.KEY_MEDIA_PATH);// 得到媒体路径,DataManager会根据路径生成相应数据源

     

;

      mMediaSet = mActivity.getDataManager().getMediaSet(mediaPath,

                mMtkInclusion); // 新建数据源

       

        mSelectionManager.setSourceMediaSet(mMediaSet);

        mAlbumSetDataAdapter = new AlbumSetDataLoader(

                mActivity, mMediaSet, DATA_CACHE_SIZE);// 新建适配器

             mAlbumSetView.setModel(mAlbumSetDataAdapter); // 适配器和数据源绑定

    }

上面提到数据源由很多种,如果是本地数据源他会新建一个LocalAlbumSet.

那么他是如何生成的。

这句mActivity.getDataManager()他会返回一个DataManager,我们进入这个方法看看:

这里的mActivityGalleryAppImpl类。

public synchronized DataManager getDataManager() {

        if (mDataManager == null) {

            mDataManager = new DataManager(this);

            mDataManager.initializeSourceMap(); //初始化DataManager

        }

        return mDataManager;

    }


public synchronized void initializeSourceMap() {

        if (!mSourceMap.isEmpty()) return;

 

        // the order matters, the UriSource must come last

        addSource(new LocalSource(mApplication)); //本地数据源

 

        addSource(new PicasaSource(mApplication)); // gmail同步数据源

        if (ApiHelper.HAS_MTP) {

            addSource(new MtpSource(mApplication)); // mtp传输数据源

        }

        addSource(new ComboSource(mApplication));  // 组合数据源

        addSource(new ClusterSource(mApplication)); 

     // 时间,地点排序后的数据源

        addSource(new FilterSource(mApplication));

        addSource(new SecureSource(mApplication));

        addSource(new UriSource(mApplication));

        addSource(new SnailSource(mApplication));

}   

上面把各种数据源存到一个Hash表,提供存取。

mMediaSet =mActivity.getDataManager().getMediaSet(mediaPath,

                mMtkInclusion);

// 进入DataManager类的getMediaSet类中。

LocalSource.java

public MediaObject createMediaObject(Path path) {

        GalleryApp app = mApplication;

        switch (mMatcher.match(path)) {

            case LOCAL_ALL_ALBUMSET:

            case LOCAL_IMAGE_ALBUMSET:

            case LOCAL_VIDEO_ALBUMSET:

                return new LocalAlbumSet(path, mApplication);

     // 这里返回LocalAlbumSet的数据源,mMediaSet = LocalAlbumSet

               }


public MediaObject getMediaObject(Path path) {

        synchronized (LOCK) {

          

    ………

            MediaSource source = mSourceMap.get(path.getPrefix());

// 获取Hash表的数据源,这里架设获取的是LocalSource

            try {

                MediaObject object = source.createMediaObject(path);

                // 进入方法中

。。。。。。。。。。。

                   }

AlbumSetPage.onCreate() 之后进入onResume()方法中,mAlbumSetDataAdapter.resume();

调用AlbumSetDataLoaderonResume函数中,调用mReloadTask.start();

mReloadTask是一个线程,会启动线程到run方法中。

public void run() {

                updateLoading(true); // 发送消息更新开始

             ………

                long version = mSource.reload();

        // 调用各种数据源的 reload,这里架设是LocalAlbumSet             

                ………..

                if (info.version != version) { // 相册是否有更新

                    info.version = version;

                    info.size = mSource.getSubMediaSetCount();

 

                    // If the size becomes smaller after reload(), we may

                    // receive from GetUpdateInfo an index which is too

                    // big. Because the main thread is not aware of the size

                    // change until we call UpdateContent.

                。。。。。。。。。。。

if (info.index != INDEX_NONE) {

     info.item = mSource.getSubMediaSet(info.index);// 得到某个相册标识

    if (info.item == null) continue;

  info.cover = info.item.getCoverMediaItem(); // item是一个相册,得到相册集合中的第一张照片作为封面

    info.totalCount = info.item.getTotalMediaItemCount(); // 相册个数

     }

       executeAndWait(new UpdateContent(info)); // 发送加载成功

 }


1.  mSource.reload() ,   localAlbumSet.java

public synchronized long reload() {

      // 新建一个AlbumsLoader的任务,并添加线程池

            mLoadTask = mApplication.getThreadPool().submit(new AlbumsLoader(), this);

        }

        if (mLoadBuffer != null) { // mLoadBuffer就是相册集合,在onFutureDone方法里线程结束时返回

            mAlbums = mLoadBuffer;

            mLoadBuffer = null;

            for (MediaSet album : mAlbums) {

                album.reload();// 每个的相册都加载相片

            }

            mDataVersion = nextVersionNumber(); // 相册版本

        }

        return mDataVersion;

    }

AlbumsLoader相册加载任务,返回是一组相册

public ArrayList<MediaSet> run(JobContext jc) {

            BucketEntry[] entries = BucketHelper.loadBucketEntries(

                    jc, mApplication.getContentResolver(), mType, mPath);

            // 真正数据查询在这里,会从数据库加载数据,根据mtype 和mPath来划分image 和video是否同一个相册,BUCKET_ID字段为目录路径(path)的HASH值,不同BUCKET_ID代表一个相册

 

            int index = findBucket(entries, MediaSetUtils.CAMERA_BUCKET_ID);

            。。。。。

            index = findBucket(entries, MediaSetUtils.DOWNLOAD_BUCKET_ID);

            if (index != -1) {

                circularShiftRight(entries, offset++, index);

            }

         // 查询的DICM/Camera路径的mediasource

 

            ArrayList<MediaSet> albums = new ArrayList<MediaSet>();

            DataManager dataManager = mApplication.getDataManager();

            for (BucketEntry entry : entries) {

              //相册的一组数据

                MediaSet album = getLocalAlbum(dataManager,

                        mType, mPath, entry.bucketId, entry.bucketName);

                if(album.getMediaItemCount() > 0) {

                    albums.add(album); // 添加到相册集合中

                }

            }

            return albums;

        }

    }

private MediaSet getLocalAlbum(

            DataManager manager, int type, Path parent, int id, String name) ;

        。。。。。。。。。。。。

            if (object != null) return (MediaSet) object;

            switch (type) {

               // 包含图像和视频,标识一个相册

                case MEDIA_TYPE_IMAGE:

                    return new LocalAlbum(path, mApplication, id, true, name);

                case MEDIA_TYPE_VIDEO:

                    return new LocalAlbum(path, mApplication, id, false, name);

               case MEDIA_TYPE_ALL:

                      Comparator<MediaItem>comp=DataManager.sDateTakenComparator; // 两者组合

               ………

        }

    }

现在回到AlbumSetDataLoader.run方法中。

UpdateInfo info = executeAndWait(new GetUpdateInfo(version));

          

         ,,,,,

          //

                    info.item = mSource.getSubMediaSet(info.index);

                    if (info.item == null) continue;

                    info.cover = info.item.getCoverMediaItem();

                    info.totalCount = info.item.getTotalMediaItemCount();


info.item 就是一个相册,info.cover为封面的照片,info.item.getCoverMediaItem();

直接从数据库拉取图形或视频作为封面。

1.  info.totalCount =info.item.getTotalMediaItemCount(); 返回图形和视频总数

 

到这里相册已经完成了,具体的时序图:

2.2 AlbumPage数据加载

1.  Albumpage 和 AlbumSetPage的数据加载类似。

不同的地方就是在AlbumDataLoader.run的方法那里,AlbumSetPage是取相片中的第一张照片作为封面,而AlbumPage是把所有的照片生成缩略图的。

private class ReloadTask extends Thread {

        ……

        @Override

        public void run() {

            ……

                if (info.version != version) {

                    info.size = mSource.getMediaItemCount();//得到相册中相片个数

                    info.version = version;

                }

                if (info.reloadCount > 0) {

                    //得到reloadStart开始的reloadCount个媒体对象。

                    info.items = mSource.getMediaItem(info.reloadStart, info.reloadCount); // 一般start从0开始,显示多少个图片缩略图

                }              ……   }

这里的reloadCount只能显示64张照片,或者相片集没有64张,就显示多少张

public UpdateInfo call() throws Exception {

           ………

            for (int i = mContentStart, n = mContentEnd; i < n; ++i) {

                int index = i % DATA_CACHE_SIZE;

                if (setVersion[index] != version) {

                    info.reloadStart = i;

                    info.reloadCount = Math.min(MAX_LOAD_COUNT, n - i);

                     // MAX_LOAD_COUNT = 64,一次最多加载64张照片

                    return info;

                }

           }


2.3图片缩略图的生成


1.  数据加载成功之后,就要在界面上显示了,那么数据如何在界面灵活的显示出来的。Gallery的图片显示不是一个控件的VIEW来显示的,因为为了提高浏览图片的效率,他是通过OpenGl ES 把这些照片给画出来的,所以下面来分析我们点进相册之后会生成多张图片的缩略图,起到一个预浏览的效果。我们点击某张图片,他就会鲜活得出现在我们前面,他是怎么样来显示这些照片的,他对图片的解码又是在哪个地方的,下面具体分析。

2.  AlbumSlotRender负责数据的缩略图的加载工作,为了提高性能,数据加载使用了【线程池】,AlbumSlotRender从AlbumDataLoader获取要加载的数据MediaItem, 根据每一个MediaItem的状态确定是是否Bitmap缩略图的是需要加载、回收、还是等待等。对于需要加载的缩略图,提交到线程池中,所以首先第一个是怎么获得数据源。

3.  数据源的导入和对AlbumSlotRender配置

initializeViews

private void initializeViews() {

        …….

        Config.AlbumPage config = Config.AlbumPage.get(mActivity); // 相册页面的配         置项,config文件定义了各个页面的配置

 

        mSlotView = new SlotView(mActivity, config.slotViewSpec);

       // 加载配置项

        mAlbumView = new AlbumSlotRenderer(mActivity, mSlotView,

                mSelectionManager, config.placeholderColor);

        // 对AlbumSlotRenderer进行配置,用来显示相关的操作

        mSlotView.setSlotRenderer(mAlbumView);// 这里的mAlbumView相关于

          GLSurfaceview

        mRootPane.addComponent(mSlotView); // 增加到画布上

。。。。。。。。。。。。。。。。。。。

}

上面只是简单得对AlbumSlotRenderer进行了配置,要知道显示必须加载数据源,那么数据源加载在哪里?

在initializeData()方法中导入了数据源。mAlbumView.setModel(mAlbumDataAdapter)


public void setModel(AlbumDataLoader model) {

       

        if (model != null) {AlbumSlidingWindow = mDataWindow

            mDataWindow = new AlbumSlidingWindow(mActivity, model, CACHE_SIZE); // 缩略图的窗口

            mDataWindow.setListener(new MyDataModelListener()); // 监听

              }

2.3.1生成省略图的线程

上面窗口AlbumSlidingWindow类的监听器是MyDataModelListener类,所以我们点某个相册,窗口就会创建和状态发生改变,这里分析MyDataModelListener. onVisibleRangeChanged方法


AlbumSlotRenderer.java

public void onVisibleRangeChanged(int visibleStart, int visibleEnd) {

      // 两次进入这个方法,第一次是创建大窗口

        if (mDataWindow != null) {

            mDataWindow.setActiveWindow(visibleStart, visibleEnd);

           // 设置获得窗口,mDataWindow是窗口相关显示类

            /// M: Video thumbnail play @{

            mVideoThumbnailDirector.updateStage();

            /// @}

        }

    }


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

        }

// 这里的start 一般是0, end这里有个规则,如果相片个数< 16,则end= 相片数,

因为在竖屏模式下,最多屏幕能显示16组照片,横屏是12张

        。。。。。。。

        int contentEnd = Math.min(contentStart + data.length, mSize);// 相片的总数,如果有29张,则=29

        setContentWindow(contentStart, contentEnd); // 设置窗口,每个照片对应一个窗口

        updateTextureUploadQueue();

        if (mIsActive) updateAllImageRequests();// 会更新所有缩略图的请求

    }

setContentWindow会设置包含窗口,加载每个媒体数据


private void setContentWindow(int contentStart, int contentEnd) {

       ………….

        for (int i = contentStart; i < contentEnd; ++i) {

                prepareSlotContent(i); // 准备每个窗口的数据

            }

 

private void prepareSlotContent(int slotIndex) {

        AlbumEntry entry = new AlbumEntry();

        MediaItem item = mSource.get(slotIndex); //得到图片标识

        entry.item = item;

        entry.contentLoader = new ThumbnailLoader(slotIndex, entry.item);

        // ……. 图片的缩略图生成就在这个线程完成

       }


开始生成缩略图线程。

回到setActiveWindow的方法中,if(mIsActive) updateAllImageRequests()会更新缩略图的请求,并为每个mediaitem 调用startLoad()方法,接着mTask =submitBitmapTask(this);开始一个任务来生成缩略图。

AlbumSlidingWindow.java

 

  private class ThumbnailLoader extends BitmapLoader  {

       ……...

       @Override

        protected Future<Bitmap> submitBitmapTask(FutureListener<Bitmap> l) {

            return mThreadPool.submit(

                    mItem.requestImage(MediaItem.TYPE_MICROTHUMBNAIL), this);

               // 向线程池请求,生成缩略图

        }

}


这里向每个数据源提到submit线程生成缩略图要求,这里我们假设是localImage本地的数据处理。

public Job<Bitmap> requestImage(int type) { // 请求图片

        return new LocalImageRequest(mApplication, mPath, type, filePath,

                                     dateModifiedInSec);

    }

LocalImageRequest继承ImageCacheRequest类,第一次生成缩略图会有缓冲,下次就无需再次生成

public static class LocalImageRequest extends ImageCacheRequest { //

        private String mLocalFilePath;

        LocalImageRequest(GalleryApp application, Path path, int type,

                String localFilePath, long dateModifiedInSec) {

            // 生成缩略图

………………………………….

                   }

………………………

public Bitmap onDecodeOriginal(JobContext jc, final int type) { // 没有生成缩略图会走这里

               。。。。。。。。。。

            if (type == MediaItem.TYPE_MICROTHUMBNAIL) { // 小的省略图

                ExifInterface exif = null;

                byte [] thumbData = null;

                try {

                    exif = new ExifInterface(mLocalFilePath);

                    if (exif != null) {

                        thumbData = exif.getThumbnail();

               直接从jpeg的exif库函数里面

//得到缩略图,注意至于jpeg而且是exif的头的才可以进行此操作

// 不是很清楚那个类

                    }

                } catch (Throwable t) {

                    Log.w(TAG, "fail to get exif thumb", t);

                }

                if (thumbData != null) {

                    Bitmap bitmap = DecodeUtils.decodeIfBigEnough(

                            jc, thumbData, options, targetSize); // 生成bitmap

                    if (bitmap != null) return bitmap;

                }

            }

 

            Bitmap bitmap = decodeOriginEx(jc, mApplication, mLocalFilePath,

                    type, options, targetSize); //解码在此进行

            ………………………….

    }


ImageCacheRequest 继承自Job<Bitmap>类,我们来解析这个类

public Bitmap run(JobContext jc) {

        ImageCacheService cacheService = mApplication.getImageCacheService();

      // 得到 cache服务

        BytesBuffer buffer = MediaItem.getBytesBufferPool().get();

        try {

            //boolean found = cacheService.getImageData(mPath, mType, buffer);

            boolean found = cacheService.getImageData(mPath, mType, buffer, mDateModifiedInSec);   // 查询有没有缩略图,之前已经缓存过的放在buffer中

            // if support picture quality tuning, we decode bitmap from origin image

            // in order to apply picture quality every time

            if (MtkLog.SUPPORT_PQ) found = false;

            if (jc.isCancelled()) return null;

            if (found) { // 有,则构建bitmap

                BitmapFactory.Options options = new BitmapFactory.Options();

                options.inPreferredConfig = Bitmap.Config.ARGB_8888;

                Bitmap bitmap;

                if (mType == MediaItem.TYPE_MICROTHUMBNAIL) {

                   // 构建小缩略图

                    bitmap = DecodeUtils.decode(jc,

                            buffer.data, buffer.offset, buffer.length, options,

                            MediaItem.getMicroThumbPool());

                } else {

                    bitmap = DecodeUtils.decode(jc,

                            buffer.data, buffer.offset, buffer.length, options,

                            MediaItem.getThumbPool());

                }

                if (bitmap == null && !jc.isCancelled()) {

                    Log.w(TAG, "decode cached failed " + debugTag());

                }

                return bitmap;

            }

        } finally {

            MediaItem.getBytesBufferPool().recycle(buffer);

        }

        Bitmap bitmap = onDecodeOriginal(jc, mType);// 没有cache则跳到上面的onDecodeOriginal方法,进行解码

        。。。。。。。

 

        if (mType == MediaItem.TYPE_MICROTHUMBNAIL) {

//根据不同类型的缩略图转换解码后的bitmap(不是很理解,这里不是小缩略图)

 

            bitmap = BitmapUtils.resizeAndCropCenter(bitmap, mTargetSize, true);

        } else {

            bitmap = BitmapUtils.resizeDownBySideLength(bitmap, mTargetSize, true);

        }

        。。。。。。。。。

        return bitmap;

    }


上面的ImageCacheRequest的run方法中,一种是已经缓冲了缩略图,则直接拿出来,不需要解码,如果没有调用localImage. onDecodeOriginal来解码。来看看这个函数decodeOriginEx

private static Bitmap decodeOriginEx(JobContext jc, GalleryApp application,

            String filePath, int type, BitmapFactory.Options options, int targetSize) {

       ………

        DataBundle dataBundle = RequestHelper.requestDataBundle(jc, params,

                                    (Context)application, filePath, false);

// 上面调用解码请求

            }

 

public static DataBundle requestDataBundle(JobContext jc, Params params,

                      Context context, String filePath, boolean allowDefault) {

      

               IMediaRequest mediaRequest = null;

        //check if this file is drm and can get decrypted buffer

        byte[] buffer = DrmHelper.forceDecryptFile(filePath, false);

       // 解码drm

        if (null != buffer) {

            //for drm, we have to retrieve its mime type first.

            mimeType = DrmHelper.getOriginalMimeType(context, filePath);

Log.i(TAG, "requestOriginalBitmap:mimeType="+mimeType);

            mediaRequest = RequestManager.getMediaRequest(mimeType, true);

   // 根据数据源的mimeType来请求媒体,这里是imageReqult

        } else {

            mimeType = MediaFile.getMimeTypeForFile(filePath);

            mediaRequest = RequestManager.getMediaRequest(mimeType, allowDefault);

        }

 

       。。。。。。。。。。。。。

 

        if (null == buffer) {//除了drm部分处理,调用imageRequest

            return mediaRequest.request(jc, params, filePath);

        } else {

              // drm 处理

            return mediaRequest.request(jc, params, buffer, 0, buffer.length);

        }

    }

ImageRequest.java

public DataBundle request(JobContext jc, Params params, String filePath) {

           // 对图片进行解码

            originThumb = DecodeUtils.decodeThumbnail(

                jc, filePath, options,

                params.inOriginalTargetSize, params.inType);

       }

    }

 

我们再来看DecodeUtils.decodeThumbnail


public static Bitmap decodeThumbnail(

            JobContext jc, FileDescriptor fd, Options options, int targetSize, int type) {

        if (options == null) options = new Options();

        jc.setCancelListener(new DecodeCanceller(options));

 

        options.inJustDecodeBounds = true;

        BitmapFactory.decodeFileDescriptor(fd, null, options);

     // 最后使用BitmapFactory类对图片进行解码

        if (jc.isCancelled()) return null;

 

        int w = options.outWidth;

        int h = options.outHeight;

 

        if (type == MediaItem.TYPE_MICROTHUMBNAIL) {

            // We center-crop the original image as it's micro thumbnail. In this case,

            // we want to make sure the shorter side >= "targetSize".

            float scale = (float) targetSize / Math.min(w, h);

            options.inSampleSize = BitmapUtils.computeSampleSizeLarger(scale);

 

            // For an extremely wide image, e.g. 300x30000, we may got OOM when decoding

            // it for TYPE_MICROTHUMBNAIL. So we add a max number of pixels limit here.

            final int MAX_PIXEL_COUNT = 640000; // 400 x 1600

            if ((w / options.inSampleSize) * (h / options.inSampleSize) > MAX_PIXEL_COUNT) {

                options.inSampleSize = BitmapUtils.computeSampleSize(

                        FloatMath.sqrt((float) MAX_PIXEL_COUNT / (w * h)));

            }

        } else {

            // For screen nail, we only want to keep the longer side >= targetSize.

            float scale = (float) targetSize / Math.max(w, h);

            options.inSampleSize = BitmapUtils.computeSampleSizeLarger(scale);

        }

 

        options.inJustDecodeBounds = false;

        setOptionsMutable(options);

 

        Bitmap result = BitmapFactory.decodeFileDescriptor(fd, null, options);

        if (result == null) return null;

 

        // We need to resize down if the decoder does not support inSampleSize

        // (For example, GIF images)

        float scale = (float) targetSize / (type == MediaItem.TYPE_MICROTHUMBNAIL

                ? Math.min(result.getWidth(), result.getHeight())

                : Math.max(result.getWidth(), result.getHeight()));

 

        if (scale <= 0.5) result = BitmapUtils.resizeBitmapByScale(result, scale, true);

        return ensureGLCompatibleBitmap(result);

    }

解码后就可以生成缩略图了。

其中类的关系流程图如下:



  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值