图库-由新增Selfies自拍文件夾探索数据图片加载
这篇文章是基于图库-数据部分概述上讨论的
确定AlbumSeTabPage对应的AlbumSet实例
AlbumSetTabPage的数据加载要从其initializeData()开始
private void initializeData() {
        String mediaPath = "/local/show_and_pack_system/all";
        mMediaSet = mActivity.getDataManager().getMediaSet(mediaPath);
        mSelectionManager.setSourceMediaSet(mMediaSet);
        mAlbumSetDataLoader = new AlbumSetDataLoader(
                mActivity, mMediaSet, DATA_CACHE_SIZE);
        mAlbumSetDataLoader.setLoadingListener(new MyLoadingListener());
        mAlbumSetRenderer.setModel(mAlbumSetDataLoader);
        //Gionee <liuyuankun> <2016-09-29> add for CR01764760 begin
        //Gionee hushengsong 2017-05-16 modify for 137083 begin
        if(Utils.gnVFflag&&StoryUtils.getAlbumSetFirstLoadFlag(mActivity.getAndroidContext())) {
            mAlbumSetDataLoader.resume();
        }
        //Gionee hushengsong 2017-05-16 modify for 137083 end
        //Gionee <liuyuankun> <2016-09-29> add for CR01764760 end
    }
其mediaPath是"/local/show_and_pack_system/all"
看DataManager#getMediaObject()
public MediaObject getMediaObject(Path path) {
             //... ...
            MediaSource source = mSourceMap.get(path.getPrefix());
            //... ...
           MediaObject object = source.createMediaObject(path);
    }
public synchronized void initializeSourceMap() {
        if (!mSourceMap.isEmpty()) return;
        // the order matters, the UriSource must come last
        addSource(new LocalSource(mApplication));
        addSource(new ComboSource(mApplication));
        addSource(new FilterSource(mApplication));
        addSource(new UriSource(mApplication));
        addSource(new StorySource(mApplication));
        addSource(new StoryCoverSource(mApplication));
        addSource(new PTSource(mApplication));
        addSource(new KidsModeSource(mApplication));
        addSource(new TrashSource(mApplication));
        addSource(new TidySource(mApplication));
        addSource(new PrivacySource(mApplication));
        addPluginSource();
        if (mActiveCount > 0) {
            for (MediaSource source : mSourceMap.values()) {
                source.resume();
            }
        }
    }
mediaPath匹配到了LocalSource,看LocalSource的构造方法:
public LocalSource(GalleryApp context) {
        super("local");
        mApplication = context;
        mMatcher = new PathMatcher();
        mMatcher.add("/local/image", LOCAL_IMAGE_ALBUMSET);
        mMatcher.add("/local/video", LOCAL_VIDEO_ALBUMSET);
        mMatcher.add("/local/all", LOCAL_ALL_ALBUMSET);
        mMatcher.add("/local/image/*", LOCAL_IMAGE_ALBUM);
        mMatcher.add("/local/video/*", LOCAL_VIDEO_ALBUM);
        mMatcher.add("/local/all/*", LOCAL_ALL_ALBUM);
        mMatcher.add("/local/image/item/*", LOCAL_IMAGE_ITEM);
        mMatcher.add("/local/video/item/*", LOCAL_VIDEO_ITEM);
        mMatcher.add("/local/image/*/*", LOCAL_IMAGE_LIMITED_ALBUM);
        mMatcher.add("/local/video/*/*", LOCAL_VIDEO_LIMITED_ALBUM);
        mMatcher.add("/local/all/*/*", LOCAL_ALL_LIMITED_ALBUM);
        mMatcher.add("/local/other/all", LOCAL_OTHER_ALL_ALBUMSET);
        mMatcher.add("/local/show/all", LOCAL_SHOW_ALL_ALBUMSET);
        mMatcher.add("/local/pt/other/all", LOCAL_PT_OTHER_ALL_ALBUMSET);
        mMatcher.add("/local/show_and_pack_system/all", LOCAL_SHOW_ALL_AND_PACK_SYSTEM);
        mMatcher.add("/local/show_and_filter_system/all", LOCAL_SHOW_ALL_AND_FILTER_SYSTEM);
        mUriMatcher.addURI(MediaStore.AUTHORITY,
                "external/images/media/#", LOCAL_IMAGE_ITEM);
        mUriMatcher.addURI(MediaStore.AUTHORITY,
                "external/video/media/#", LOCAL_VIDEO_ITEM);
        mUriMatcher.addURI(MediaStore.AUTHORITY,
                "external/images/media", LOCAL_IMAGE_ALBUM);
        mUriMatcher.addURI(MediaStore.AUTHORITY,
                "external/video/media", LOCAL_VIDEO_ALBUM);
        mUriMatcher.addURI(MediaStore.AUTHORITY,
                "external/file", LOCAL_ALL_ALBUM);
    }
而LocalSource#createMediaObject(path);返回的是什么子类型呢,看下面代码
@Override
    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);
            case LOCAL_IMAGE_ALBUM:
                return new LocalAlbum(path, app, mMatcher.getIntVar(0), true);
            case LOCAL_VIDEO_ALBUM:
                return new LocalAlbum(path, app, mMatcher.getIntVar(0), false);
            case LOCAL_ALL_ALBUM: {
                int bucketId = mMatcher.getIntVar(0);
                DataManager dataManager = app.getDataManager();
                MediaSet imageSet = (MediaSet) dataManager.getMediaObject(
                        LocalAlbumSet.PATH_IMAGE.getChild(bucketId));
                MediaSet videoSet = (MediaSet) dataManager.getMediaObject(
                        LocalAlbumSet.PATH_VIDEO.getChild(bucketId));
                Comparator<MediaItem> comp = DataManager.sDateTakenComparator;
                return new LocalMergeAlbum(
                        path, comp, new MediaSet[]{imageSet, videoSet}, bucketId);
            }
            case LOCAL_IMAGE_LIMITED_ALBUM:
                return new LocalAlbum(path, app, mMatcher.getIntVar(0), true, mMatcher.getLongVar(1));
            case LOCAL_VIDEO_LIMITED_ALBUM:
                return new LocalAlbum(path, app, mMatcher.getIntVar(0), false, mMatcher.getLongVar(1));
            case LOCAL_ALL_LIMITED_ALBUM: {
                int bucketId = mMatcher.getIntVar(0);
                long timeSince = mMatcher.getLongVar(1);
                DataManager dataManager = app.getDataManager();
                MediaSet imageSet = (MediaSet) dataManager.getMediaObject(Path.fromString("/local/image/"
                        + bucketId + "/" + timeSince));
                MediaSet videoSet = (MediaSet) dataManager.getMediaObject(Path.fromString("/local/video/"
                        + bucketId + "/" + timeSince));
                Comparator<MediaItem> comp = DataManager.sDateTakenComparator;
                return new LocalMergeAlbum(path, comp, new MediaSet[]{imageSet, videoSet}, bucketId);
            }
            case LOCAL_IMAGE_ITEM:
                return new LocalImage(path, mApplication, mMatcher.getIntVar(0));
            case LOCAL_VIDEO_ITEM:
                return new LocalVideo(path, mApplication, mMatcher.getIntVar(0));
            case LOCAL_OTHER_ALL_ALBUMSET:
                return new LocalOtherAlbumSet(path, mApplication);
            case LOCAL_SHOW_ALL_ALBUMSET:
                return new LocalShowAlbumSet(path, mApplication);
            case LOCAL_PT_OTHER_ALL_ALBUMSET:
                return new LocalPTOtherAlbumSet(path, mApplication);
            case LOCAL_SHOW_ALL_AND_PACK_SYSTEM:
                return new LocalShowAndPackSystemAlbumSet(path, mApplication);
            case LOCAL_SHOW_ALL_AND_FILTER_SYSTEM:
                return new LocalShowAndFilterSystemAlbumSet(path, mApplication);
            default:
                throw new RuntimeException("bad path: " + path);
        }
    }
mMatcher.add("/local/show_and_pack_system/all", LOCAL_SHOW_ALL_AND_PACK_SYSTEM);看这句就知道其mediaPath对应的是LocalShowAndPackSystemAlbumSet
看LocalShowAndPackSystemAlbumSet
public LocalShowAndPackSystemAlbumSet(Path path, GalleryApp application) {
        super(path, nextVersionNumber());
        mGalleryApp = application;
        DataManager dataManager = application.getDataManager();
        mLocalShowAllMediaSet = dataManager.getMediaSet("/local/show/all");
        mLocalShowAllMediaSet.addContentListener(this);
    }
@Override
    public MediaSet getSubMediaSet(int index) {
        MediaSet localShowAllMediaSet = mLocalShowAllMediaSet;
        int showCount = localShowAllMediaSet.getSubMediaSetCount();
       //... ...
    }
从上面方法结合LocalSource可以看出,LocalShowAndPackSystemAlbumSet不过是引用 LocalShowAlbumSet,下面看LocalShowAlbumSet的构造方法
public LocalShowAlbumSet(Path path, GalleryApp application) {
        super(path, nextVersionNumber());
        DataManager dataManager = application.getDataManager();
        mLocalAllMediaSet = dataManager.getMediaSet("/local/all");
        mLocalAllMediaSet.addContentListener(this);
        mLocalOtherAllMediaSet = dataManager.getMediaSet("/local/other/all");
        mLocalOtherAllMediaSet.addContentListener(this);
    }
@Override
    public MediaSet getSubMediaSet(int index) {
        int localAllCount = mLocalAllMediaSet.getSubMediaSetCount();
        int localOtherAllCount = mLocalOtherAllMediaSet.getSubMediaSetCount();
//没有隐藏和放在trash中的album
        if (localOtherAllCount == 0 && index < localAllCount) {
            return mLocalAllMediaSet.getSubMediaSet(index);
        }
        int count = -1;
//有隐藏或者在trash中的album
        for (int i = 0; i < localAllCount; i++) {
            MediaSet mediaSet = mLocalAllMediaSet.getSubMediaSet(i);
            int bucketId = ((LocalMergeAlbum) mediaSet).getBucketId();
            if (isShowBucketId(bucketId)) {
                count++;
            }
            if (count == index) {
                return mediaSet;
            }
        }
        return null;
    }
@Override
    public int getSubMediaSetCount() {
        int localAllCount = mLocalAllMediaSet.getSubMediaSetCount();
        int localOtherAllCount = mLocalOtherAllMediaSet.getSubMediaSetCount();
//数目等于总album数减去other,那么other应该是隐藏的album加上已删除的在trash中但还未彻底删除的album
        return Utils.clamp(localAllCount - localOtherAllCount, 0, localAllCount);
    }
从上面代码可以看出LocalShowAlbumSet是持有了一个LocalAlbumSet和一个LocalOtherAlbumSet。
而这两个AlbumSet是什么关系呢。从上面getSubMediaSetCount()可以看出,LocalOtherAlbumSet应该是隐藏的album加上已删除的在trash中但还未彻底删除的album,
而LocalAlbumSet是所有的包括隐藏和在trash中的album。
上面确定了albumset后,那么下面就要开始初始化所有album了
初始化albums
首先看MediaSet#getSubMediaSet()。
分析LocalAlbumSet从初始化album
1.LocalAlbumSet#getSubMediaSet()
@Override
    public MediaSet getSubMediaSet(int index) {
        return mAlbums.get(index);
    }
 @Override
    // synchronized on this function for
    //   1. Prevent calling reload() concurrently.
    //   2. Prevent calling onFutureDone() and reload() concurrently
    public synchronized long reload() {
        if (mNotifier.isDirty()) {
            if (mLoadTask != null) mLoadTask.cancel();
            mIsLoading = true;
            mLoadTask = mApplication.getThreadPool().submit(new AlbumsLoader(), this);
        }
        if (mLoadBuffer != null) {
            mAlbums = mLoadBuffer;
            mLoadBuffer = null;
            for (MediaSet album : mAlbums) {
                album.reload();
            }
            mDataVersion = nextVersionNumber();
        }
        return mDataVersion;
    }
@Override
    public synchronized void onFutureDone(Future<ArrayList<MediaSet>> future) {
        if (mLoadTask != future) return; // ignore, wait for the latest task
        mLoadBuffer = future.get();
        /// M: [BUG.MARK] @{
        /* mIsLoading = false; */
        /// @}
        if (mLoadBuffer == null) mLoadBuffer = new ArrayList<MediaSet>();
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                notifyContentChanged();
                /// M: [BUG.ADD] @{
                // To avoid timing issue of isLoading and notifyContentChanged,
                // set mIsLoading as false here.
                mIsLoading = false;
                /// @}
            }
        });
    }
mAlbums : ArrayList<MediaSet> 真正初始化入口是MediaSet#reload()
reload()中看这句:mLoadTask = mApplication.getThreadPool().submit(new AlbumsLoader(), this);
还有:mAlbums = mLoadBuffer;
AlbumLoader异步加载完成后,会调用onFutureDone,将创建好的ArrayList<MediaSet> 传给mLoadBuffer。
下面看
private class AlbumsLoader implements ThreadPool.Job<ArrayList<MediaSet>> {
        @Override
        @SuppressWarnings("unchecked")
        public ArrayList<MediaSet> run(JobContext jc) {
            /// M: [DEBUG.ADD] @{
            TraceHelper.traceBegin(">>>>LocalAlbumSet-AlbumsLoader");
            /// @}
            // Note: it will be faster if we only select media_type and bucket_id.
            //       need to test the performance if that is worth
            BucketEntry[] entries = BucketHelper.loadBucketEntries(
                    jc, mApplication.getContentResolver(), mType);
            /// M: [DEBUG.MODIFY] @{
            /*if (jc.isCancelled()) return null;*/
            if (jc.isCancelled()) {
                TraceHelper.traceEnd();
                return null;
            }
            /// @}
            int offset = 0;
            // Move camera and download bucket to the front, while keeping the
            // order of others.
            int index = findBucket(entries, MediaSetUtils.CAMERA_BUCKET_ID);
            if (index != -1) {
                circularShiftRight(entries, offset++, index);
            }
            
          //Gionee chenjs 2017-03-01 add for Selfie album begin
            if (com.gionee.gallery3d.util.Utils.isSupportedSelfieFile()) {
            	index = findBucket(entries, MediaSetUtils.SELFIE_BUCKET_ID);
            	if (index != -1) {
            		if (com.gionee.gallery3d.util.Utils.gnGIflag) {
            			entries[index].bucketName = mApplication.getResources().getString(R.string.selfiestan);
					} else {
						entries[index].bucketName = mApplication.getResources().getString(R.string.selfies);
					}
            		circularShiftRight(entries, offset++, index);
            	}
			}
            //Gionee chenjs 2017-03-01 add for Selfie album end
            index = findBucket(entries, MediaSetUtils.DOWNLOAD_BUCKET_ID);
            if (index != -1) {
                circularShiftRight(entries, offset++, index);
            }
            ArrayList<MediaSet> albums = new ArrayList<MediaSet>();
            DataManager dataManager = mApplication.getDataManager();
            for (BucketEntry entry : entries) {
                MediaSet album = getLocalAlbum(dataManager,
                        mType, mPath, entry.bucketId, entry.bucketName);
                albums.add(album);
            }
            /// M: [DEBUG.ADD] @{
            TraceHelper.traceEnd();
            /// @}
            return albums;
        }
    }
看这句:BucketEntry[] entries = BucketHelper.loadBucketEntries( jc, mApplication.getContentResolver(), mType);
创建Album的构造参数来自BucketEntry,其成员如下:
public static class BucketEntry {
        public String bucketName;
        public int bucketId;
        public int dateTaken;
        public BucketEntry(int id, String name) {
            bucketId = id;
            bucketName = Utils.ensureNotNull(name);
        }
        @Override
        public int hashCode() {
            return bucketId;
        }
        @Override
        public boolean equals(Object object) {
            if (!(object instanceof BucketEntry)) return false;
            BucketEntry entry = (BucketEntry) object;
            return bucketId == entry.bucketId;
        }
    }
看获取BucketEntry[] entries的过程:
public static BucketEntry[] loadBucketEntries(
            JobContext jc, ContentResolver resolver, int type) {
        if (ApiHelper.HAS_MEDIA_PROVIDER_FILES_TABLE) {
            //主要分析该方法
            return loadBucketEntriesFromFilesTable(jc, resolver, type);
        } else {
            return loadBucketEntriesFromImagesAndVideoTable(jc, resolver, type);
        }
    }
private static BucketEntry[] loadBucketEntriesFromFilesTable(
            JobContext jc, ContentResolver resolver, int type) {
        Uri uri = getFilesContentUri();
        /// M: [FEATURE.MARK] @{
        /*Cursor cursor = resolver.query(uri,
         PROJECTION_BUCKET, BUCKET_GROUP_BY,
         null, BUCKET_ORDER_BY);*/
        String whereGroup = MediaFilterSetting.getExtWhereClause(null);
        if (null == whereGroup || whereGroup.equals("")) {
            whereGroup = VIDEO_IMAGE_CLAUSE + PURE_BUCKET_GROUP_BY;
        } else {
            whereGroup = "(" + VIDEO_IMAGE_CLAUSE + ") AND (" + whereGroup + ")" +
                         PURE_BUCKET_GROUP_BY;
        }
        //查询数据库,从上面看,projection是ImageColumns.BUCKET_ID, FileColumns.MEDIA_TYPE, 
        //ImageColumns.BUCKET_DISPLAY_NAME;  BUCKET_ORDER_BY = "MAX(datetaken) DESC";
        //VIDEO_IMAGE_CLAUSE = "media_type=1 OR media_type=3"  ,  PURE_BUCKET_GROUP_BY = ") GROUP BY 1,(2"
        //uri = Files.getContentUri("external"); 这是一个外部存储卡查询Android外部存储器的多媒体数据库的uri
                Cursor cursor = resolver.query(uri,
                PROJECTION_BUCKET, whereGroup,
                null, BUCKET_ORDER_BY);
        /// @}
        if (cursor == null) {
            Log.w(TAG, "cannot open local database: " + uri);
            return new BucketEntry[0];
        }
        ArrayList<BucketEntry> buffer = new ArrayList<BucketEntry>();
        int typeBits = 0;
        if ((type & MediaObject.MEDIA_TYPE_IMAGE) != 0) {
            typeBits |= (1 << FileColumns.MEDIA_TYPE_IMAGE);
        }
        if ((type & MediaObject.MEDIA_TYPE_VIDEO) != 0) {
            typeBits |= (1 << FileColumns.MEDIA_TYPE_VIDEO);
        }
        try {
           //根据查询到的列数据初始化entries
            while (cursor.moveToNext()) {
                if ((typeBits & (1 << cursor.getInt(INDEX_MEDIA_TYPE))) != 0) {
                    BucketEntry entry = new BucketEntry(
                            cursor.getInt(INDEX_BUCKET_ID),
                            cursor.getString(INDEX_BUCKET_NAME));
                    if (!buffer.contains(entry)) {
                        buffer.add(entry);
                    }
                }
                if (jc.isCancelled()) return null;
            }
          
          //因为自拍文件夹selfies是虚拟的,并没有真正的存在一个文件夹。只是根据前置摄像标志位camera_id==1在all页面中单独显示成一个集合。
          //所以查询Android多媒体数据库是查不到的,需要自己建一个BucketEntry,下面加上的就是初始化selfies的过程
          //Gionee chenjs 2017-03-01 add for Selfie album begin
            if (com.gionee.gallery3d.util.Utils.isSupportedSelfieFile()) {
                //是DCIM/Camera文件夹隐藏标志,一个是内置内存卡DCIM/Camera文件夹隐藏标志,一个是可移除的内存卡DCIM/Camera文件夹隐藏标志。隐藏了的话对应的自拍图片就不会显示出来。
            	boolean[] hasHide = {false, false};
                //查询content://com.gionee.gallery3d.data.StoryProvider/other_folders。这是图库自带的数据库,可以查询到
            	cursor = resolver.query(StoryContract.OtherFolder.OTHER_FOLDER_URI,
                        new String[]{StoryContract.OtherFolder.COLUMN_BUCKET_ID}, null, null, null);
                if (cursor != null) {
    				while (cursor.moveToNext()) {
    					if (cursor.getInt(0) == MediaSetUtils.CAMERA_BUCKET_ID) {
    						hasHide[0] = true;
    						//continue;
    					}
    					if (cursor.getInt(0) == com.gionee.gallery3d.util.MediaSetUtils.getExternalBucketID(GalleryAppImpl.getGalleryApp().getAndroidContext())) {
    						hasHide[1] = true;
    					}
    					
    					/*if (cursor.getInt(0) == MediaSetUtils.getExternalBucketID(GalleryAppImpl.getGalleryApp().getAndroidContext())) {
							hasHide[1] = true;
						}*/
    					
    				}
    			}
            	for (BucketEntry entry : buffer) {
                                       //查看可移除的内存卡DCIM/Camera文件夹的自拍照片数目
					if (entry.bucketId == MediaSetUtils.CAMERA_BUCKET_ID
							|| entry.bucketId == com.gionee.gallery3d.util.MediaSetUtils.getExternalBucketID(GalleryAppImpl.getGalleryApp().getAndroidContext())) {
						cursor = resolver.query(uri, 
								LocalImage.PROJECTION, 
								ImageColumns.DATA + " like? and camera_id=? and " + 
								ImageColumns.MIME_TYPE + " =?", 
								new String[]{"%"+Environment.getExternalStorageDirectory().toString()
            					+ "/DCIM/Camera" + "%", String.valueOf(1), "image/jpeg"}, 
            					ImageColumns.DATE_TAKEN + " DESC, "
            		                    + ImageColumns._ID + " DESC");
						
						int count = 0;
						if (cursor != null) {
							count = cursor.getCount();
						}
						//查看内置的内存卡DCIM/Camera文件夹的自拍照片数目
						cursor = resolver.query(uri, 
								LocalImage.PROJECTION, 
								ImageColumns.DATA + " like? and camera_id=? and " + 
								ImageColumns.MIME_TYPE + " =?", 
								new String[]{"%"+ com.gionee.gallery3d.util.MediaSetUtils.getExternalPath()
            					+ "/DCIM/Camera" + "%", String.valueOf(1), "image/jpeg"}, 
            					ImageColumns.DATE_TAKEN + " DESC, "
            		                    + ImageColumns._ID + " DESC");
						int count1 = 0;
						if (cursor != null) {
							count1 = cursor.getCount();
						}
                                                //如果存在一个有自拍照片且没有隐藏的DCIM/Camera文件夹就创建这个对应Selfies的BucketEntry
						if (!(hasHide[0] || count == 0) || !(hasHide[1] || count1 == 0)) {
							buffer.add(new BucketEntry(MediaSetUtils.SELFIE_BUCKET_ID, "selfie"));
							break;
						}
					}
				}
			}
            //Gionee chenjs 2017-03-01 add for Selfie album end
        } finally {
            Utils.closeSilently(cursor);
        }
        return buffer.toArray(new BucketEntry[buffer.size()]);
    }
具体代码解析看注释。BucketEntry数组就初始化完成了。那BucketEntry的构造参数是Bucketid和name。那么Bucketid怎么和album对应的呢?
怎么根据Bucketid知道Album中有哪些MediaItem的呢?
看LocalAlbumSet.AlbumsLoader#run()中的这几句,在entries初始化完后开始创建albums(MediaSet)
for (BucketEntry entry : entries) {
                MediaSet album = getLocalAlbum(dataManager,
                        mType, mPath, entry.bucketId, entry.bucketName);
                albums.add(album);
            }
看LocalAlbumSet#getLocalAlbum()
private MediaSet getLocalAlbum(
            DataManager manager, int type, Path parent, int id, String name) {
        synchronized (DataManager.LOCK) {
            Path path = parent.getChild(id);
            MediaObject object = manager.peekMediaObject(path);
            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;
                    return new LocalMergeAlbum(path, comp, new MediaSet[] {
                            getLocalAlbum(manager, MEDIA_TYPE_IMAGE, PATH_IMAGE, id, name),
                            getLocalAlbum(manager, MEDIA_TYPE_VIDEO, PATH_VIDEO, id, name)}, id);
            }
            throw new IllegalArgumentException(String.valueOf(type));
        }
    }
最终初始化的album子类型又创建LocalAlbumSet时传入的Path有关,就是该与具体的bucketid无关。
跟踪代码分别有一下几种,来自MediaObject:
public static final String MEDIA_TYPE_IMAGE_STRING = "image";
public static final String MEDIA_TYPE_VIDEO_STRING = "video";
public static final String MEDIA_TYPE_ALL_STRING = "all";
public static int getTypeFromString(String s) {
//字段分别对应 if (MEDIA_TYPE_ALL_STRING.equals(s)) return MediaObject.MEDIA_TYPE_ALL; if (MEDIA_TYPE_IMAGE_STRING.equals(s)) return MediaObject.MEDIA_TYPE_IMAGE; if (MEDIA_TYPE_VIDEO_STRING.equals(s)) return MediaObject.MEDIA_TYPE_VIDEO; throw new IllegalArgumentException(s); }
而从之前的分析可以知道,LocalAlbumSet对应的是/local/all;LocalOtherAlbumSet对应的是/local/other/all;
所以该流程中,LocalAlbumSet实例化的LocalMergeAlbum。可以看出,LocalMergeAlbum的构造参数包括了
new MediaSet[] { getLocalAlbum(manager, MEDIA_TYPE_IMAGE, PATH_IMAGE, id, name), getLocalAlbum(manager,MEDIA_TYPE_VIDEO, PATH_VIDEO, id, name)}
就是LocalMergeAlbum封装了两个LocalAlbum,一个是所有MediaItem都是image类型的,一个所有MediaItem都是video类型的。
LocalAlbum根据媒体类型和bucketid去初始化MediaItem
album根据bucketId初始化MediaItem
下面看LocalALbum是如何初始化MediaItem的,下面看LocalAlbum的构造方法:
public LocalAlbum(Path path, GalleryApp application, int bucketId,
            boolean isImage, String name) {
        super(path, nextVersionNumber());
        mApplication = application;
        mResolver = application.getContentResolver();
        mBucketId = bucketId;
        /// M: [BUG.MODIFY] While this is no photo in Camera folder. should set the folder name @{
        /* mName = name;*/
        if (isCameraRoll() && name.equals("")) {
            mName = application.getResources().getString(R.string.folder_camera);
            Log.d(TAG, "<LocalAlbum> mName = " + mName);
        } else {
            mName = name;
        }
        /// @}
        mIsImage = isImage;
        //初始化查询参数,可以看到,都是去查询多媒体数据库,mBaseUri = Images.Media.EXTERNAL_CONTENT_URI或者mBaseUri = Video.Media.EXTERNAL_CONTENT_URI;
        if (isImage) {
            mWhereClause = ImageColumns.BUCKET_ID + " = ?";
            mOrderClause = ImageColumns.DATE_TAKEN + " DESC, "
                    + ImageColumns._ID + " DESC";
            mBaseUri = Images.Media.EXTERNAL_CONTENT_URI;
            mProjection = LocalImage.PROJECTION;
            mItemPath = LocalImage.ITEM_PATH;
        } else {
            mWhereClause = VideoColumns.BUCKET_ID + " = ?";
            mOrderClause = VideoColumns.DATE_TAKEN + " DESC, "
                    + VideoColumns._ID + " DESC";
            mBaseUri = Video.Media.EXTERNAL_CONTENT_URI;
            mProjection = LocalVideo.PROJECTION;
            mItemPath = LocalVideo.ITEM_PATH;
        }
        /// M: [FEATURE.ADD] @{
        exInitializeWhereClause();
        /// @}
       //这里的uri是用于监听媒体数据库变化的。而不是用于获取数据
      //Gionee chenjs 2017-03-01 add for Selfie album begin
        Uri[] uris;
        if (com.gionee.gallery3d.util.Utils.isSupportedSelfieFile()) {
			uris = new Uri[]{mBaseUri, StoryContract.OtherFolder.OTHER_FOLDER_URI};
		} else {
			uris = new Uri[]{mBaseUri};
		}
        //Gionee chenjs 2017-03-01 add for Selfie album end
        //Gionee <zhangjinbiao> modify for <150516> begin
//        mNotifier = new ChangeNotifier(this, mBaseUri, application);
        //如果是Selfies文件夹,还需监听多一个uri
        mNotifier = new ChangeNotifier(this, uris, application);
      //Gionee <zhangjinbiao> modify for <150516> end
    }
获取MediaItem的入口函数LocalAlbum#getMediaItem()
@Override
    public ArrayList<MediaItem> getMediaItem(int start, int count) {
        DataManager dataManager = mApplication.getDataManager();
        //结合构造时初始化的uri和该方法指定的范围构造一个新的uri
        Uri uri = mBaseUri.buildUpon()
                .appendQueryParameter("limit", start + "," + count).build();
        ArrayList<MediaItem> list = new ArrayList<MediaItem>();
        GalleryUtils.assertNotInRenderThread();
        /// M: [DEBUG.ADD] @{
        TraceHelper.traceBegin(">>>>LocalAlbum-query");
        /// @}
       //从上面的分析可以看到,bucketid是从Android多媒体数据库中查询出来,而selfies文件夹的bucketid在多媒体数据库是没有对应的,是在
       //BucketHelper#loadBucketEntriesFromFilesTable()中根据是否有自拍照片和是否隐藏来考虑是否加入这个对应selfies的bucketid的。
       //所以在获取构造MediaItem需要的信息使,其他bucketid对应的集合是可以根据bucketid通过数据查到对应的信息,而selfies则需要间接的去获取
       //需要通过数据库中查询有自拍照片,而且必须是DCIM/Camera路径的。
      //Gionee chenjs 2017-03-01 add for Selfie album begin
        Cursor cursor = null;
        if (com.gionee.gallery3d.util.Utils.isSupportedSelfieFile()) {
        	//mark if selfie folder has been hided;
        	boolean[] hasHide = {false, false};
            //查询图库自带的数据库,Camera文件夹的隐藏情况,如果隐藏了,则selfies就不显示了
            cursor = mApplication.getContentResolver().query(StoryContract.OtherFolder.OTHER_FOLDER_URI,
                    new String[]{StoryContract.OtherFolder.COLUMN_BUCKET_ID}, null, null, null);
            if (cursor != null) {
				while (cursor.moveToNext()) {
					if (cursor.getInt(0) == MediaSetUtils.CAMERA_BUCKET_ID) {
						hasHide[0] = true;
					}
					
					if (cursor.getInt(0) == com.gionee.gallery3d.util.MediaSetUtils.getExternalBucketID(mApplication.getAndroidContext())) {
						hasHide[1] = true;
					}
				}
			}
            //bucketid为SELFIE_BUCKET_ID时走该if
            if (mBucketId == MediaSetUtils.SELFIE_BUCKET_ID && mIsImage) {
            	//有且只有一个不隐藏的DCIM/Camera文件夹
            	if (!(hasHide[0] && hasHide[1]) && (hasHide[0] || hasHide[1])) {
					
            		try {
            			if (hasHide[0]) {
            				cursor = mResolver.query(uri, mProjection, 
            						ImageColumns.DATA + " like? and " + "camera_id=?", new String[]{
            						"%" + Environment.getExternalStorageDirectory().toString()
            						+ "/DCIM/Camera" + "%", String.valueOf(1)}, mOrderClause);
						} else if (com.gionee.gallery3d.util.MediaSetUtils.getExternalBucketID(mApplication.getAndroidContext()) != 0 && hasHide[1]) {
							cursor = mResolver.query(uri, mProjection, 
            						ImageColumns.DATA + " like? and " + "camera_id=?", new String[]{
            						"%" + com.gionee.gallery3d.util.MediaSetUtils.getExternalPath()
            						+ "/DCIM/Camera" + "%", String.valueOf(1)}, mOrderClause);
						}
            		} catch (Exception e) {
            			// TODO: handle exception
            		}
            		
            		ArrayList<Integer> ids = new ArrayList<>();
            		if (cursor != null) {//收集隐藏了的DCIM/Camera文件夹中的自拍照片的id。下面会过滤掉这些id,剩下其他存储器中DCIM/Camera文件夹中的自拍照片
						while (cursor.moveToNext()) {
							ids.add(cursor.getInt(0)); //id must at the first column
						}
					}
        			StringBuilder whereClause = new StringBuilder();
        			int length = ids.size();
        			if (length > 0) {
                                //构造过滤隐藏的DCIM/Camera文件夹的自拍照片的查询子句
        				whereClause.append(ImageColumns._ID + " not in (");
        				for (int i = 0; i < length; i++) {
        					whereClause.append(String.valueOf(ids.get(i)));
        					if (i == (length -1)) {
								whereClause.append(")");
							} else {
								whereClause.append(",");
							}
        				}
        				whereClause.append(" and ");
					}
        			//路径中含/DCIM/Camera且camera_id=1(前置摄像头拍出来的)
        			whereClause.append("(");
        			whereClause.append(ImageColumns.DATA + " like ");
        			whereClause.append("'%/DCIM/Camera%'");
        			whereClause.append(" and " + "camera_id=1");
        			whereClause.append(")");
        			
        			cursor = mResolver.query(
        					uri, mProjection, 
        					whereClause.toString(), 
        					null, mOrderClause);
        			
				} else {
                                 //不符合有且只有一个不隐藏的DCIM/Camera文件夹的,就来到这里
					try {
	    				cursor = mResolver.query(
	    						uri,
	    						mProjection,
	    						ImageColumns.DATA + " like? and " + "camera_id=?",
	    						new String[] { "%"
	    						+ "/DCIM/Camera" + "%", String.valueOf(1) },
	    						mOrderClause);
	    			} catch (Exception e) {
	    				// TODO: handle exception
	    			}
				}
			} else {
                                //如果bucketid不是对应selfies的,就走这里。直接用传入的bucketid去查多媒体数据库
				//Gionee <GN_Oversea_Bug> <rench> <20170428> <modify> for 126488 beign
				try {
				cursor = mResolver.query(
	    				uri, mProjection, mWhereClause,
	    				new String[]{String.valueOf(mBucketId)},
	    				mOrderClause);
				} catch (Exception e) {
	    				// TODO: handle exception
				}
				//Gionee <GN_Oversea_Bug> <rench> <20170428> <modify> for 126488 end
			}
		} else {
                      //如果不支持selfies文件夹的项目,直接走这里
			cursor = mResolver.query(
    				uri, mProjection, mWhereClause,
    				new String[]{String.valueOf(mBucketId)},
    				mOrderClause);
		}
        //Gionee chenjs 2017-03-01 add for Selfie album end
        /// M: [DEBUG.ADD] @{
        TraceHelper.traceEnd();
        /// @}
        if (cursor == null) {
            Log.w(TAG, "query fail: " + uri);
            return list;
        }
        try {
            //构造MediaItem的最后一步。使用cursor去初始化MediaItem
            while (cursor.moveToNext()) {
                int id = cursor.getInt(0);  // _id must be in the first column
                Path childPath = mItemPath.getChild(id);
                /// M: [DEBUG.ADD] @{
                TraceHelper.traceBegin(">>>>LocalAlbum-loadOrUpdateItem");
                /// @}
                MediaItem item = loadOrUpdateItem(childPath, cursor,
                        dataManager, mApplication, mIsImage);
                /// M: [DEBUG.ADD] @{
                TraceHelper.traceEnd();
                /// @}
                list.add(item);
            }
        } finally {
            cursor.close();
        }
        return list;
    }
分析LocalOtherAlbumSet从初始化album
看LocalShowAlbumSet构造方法中的这句:mLocalOtherAllMediaSet = dataManager.getMediaSet("/local/other/all");
LocalShowAlbumSet#getSubMediaSet(int index)
@Override
    public MediaSet getSubMediaSet(int index) {
        int localAllCount = mLocalAllMediaSet.getSubMediaSetCount();
        int localOtherAllCount = mLocalOtherAllMediaSet.getSubMediaSetCount();
//如果没有的话,mLocalAllMediaSet中所有album都是可以显示,进入下面的if
        if (localOtherAllCount == 0 && index < localAllCount) {
            return mLocalAllMediaSet.getSubMediaSet(index);
        }
        int count = -1;
//如果有些隐藏的则要执行下面的代码
        for (int i = 0; i < localAllCount; i++) {
            MediaSet mediaSet = mLocalAllMediaSet.getSubMediaSet(i);
            int bucketId = ((LocalMergeAlbum) mediaSet).getBucketId();
            if (isShowBucketId(bucketId)) {
                count++;
            }
            if (count == index) {
                return mediaSet;
            }
        }
        return null;
    }
可以看到mLocalOtherAllMediaSet在LocalShowAlbumSet中的价值只是看下是否有隐藏的文件夹
加入selfies,在其他地方加入的代码解析
在StoryAlbumSet加入的代码
private ArrayList<LocalMediaItem> refreshExistingItems(JobContext jc) {
            String whereClause = getWhereClause();
            final ArrayList<LocalMediaItem> itemsToMakeStory = new ArrayList<>();
            DataManager dataManager = mApp.getDataManager();
            Cursor imageCursor = mApp.getContentResolver().query(
                    MediaStore.Images.Media.EXTERNAL_CONTENT_URI, LocalImage.PROJECTION, whereClause, null,
                    MediaStore.Images.ImageColumns.DATE_TAKEN + " DESC, "
                            + MediaStore.Images.ImageColumns._ID + " DESC");
           //加入selfies相册隐藏时,过滤掉。因为selfies比较特别,不是一个实际存在的文件夹。
          //Gionee chenjs 2017-03-01 add for Selfie album begin
            final ArrayList<Integer> hideId = new ArrayList<>();
            if (Utils.isSupportedSelfieFile()) {
            	boolean hasHide = false;
            	Cursor cursor = mApp.getContentResolver().query(StoryContract.OtherFolder.OTHER_FOLDER_URI,
            			new String[]{StoryContract.OtherFolder.COLUMN_BUCKET_ID}, null, null, null);
            	
            	if (cursor != null) {
            		while (cursor.moveToNext()) {
            			if (cursor.getInt(0) == MediaSetUtils.SELFIE_BUCKET_ID) {
            				hasHide = true;
            				break;
            			}
            			
            		}
            	}
            	if (hasHide) {
            		cursor = mApp.getContentResolver().query(Images.Media.EXTERNAL_CONTENT_URI, 
            				LocalImage.PROJECTION, 
            				//Gionee <GN_Oversea_Bug> <rench> <20170316> <modify> for 84148 beign
            				//ImageColumns.DATA + " like? and " + "camera_id=?", new String[]{"%"+Environment.getExternalStorageDirectory().toString()
            				ImageColumns.DATA + " like? and " + "camera_id=?", new String[]{"%"
            				//Gionee <GN_Oversea_Bug> <rench> <20170316> <modify> for 84148 end
            				+ "/DCIM/Camera" + "%", String.valueOf(1)}, null);
            	}
            	
            	if (cursor != null) {
            		while (cursor.moveToNext()) {
            			hideId.add(cursor.getInt(0));
            		}
            		cursor.close();
            	}
			}
            //Gionee chenjs 2017-03-01 add for Selfie album end
            
            int imageCount = 0;
            if (imageCursor != null) {
                try {
                    imageCount = imageCursor.getCount();
                    if (imageCursor.getCount() > 0) {
                        itemsToMakeStory.ensureCapacity(imageCount);
                        while (imageCursor.moveToNext()) {
                            if (jc.isCancelled()) {
                                return null;
                            }
                            int id = imageCursor.getInt(0);  // _id must be in the first column
                            Path childPath = mImageBasePath.getChild(id);
                            MediaItem item = LocalAlbum.loadOrUpdateItem(childPath, imageCursor,
                                    dataManager, mApp, true);
                            
                            //Gionee chenjs 2017-03-01 add for Selfie album begin
                            if (Utils.isSupportedSelfieFile()) {
                            	if (!hideId.contains(item.getId())) {
                            		itemsToMakeStory.add((LocalMediaItem) item);
                            	}
                            //Gionee chenjs 2017-03-01 add for Selfie album end
							} else {
								itemsToMakeStory.add((LocalMediaItem) item);
							}
                        }
                    }
                } finally {
                    Utils.closeSilently(imageCursor);
                }
            }
            if (jc.isCancelled()) {
                return null;
            }
            Cursor videoCursor = mApp.getContentResolver().query(
                    MediaStore.Video.Media.EXTERNAL_CONTENT_URI, LocalVideo.PROJECTION, whereClause, null,
                    MediaStore.Video.VideoColumns.DATE_TAKEN + " DESC, "
                            + MediaStore.Video.VideoColumns._ID + " DESC");
            if (videoCursor != null) {
                try {
                    int videoCount = videoCursor.getCount();
                    if (videoCount > 0) {
                        itemsToMakeStory.ensureCapacity(imageCount + videoCount);
                        while (videoCursor.moveToNext()) {
                            if (jc.isCancelled()) {
                                return null;
                            }
                            int id = videoCursor.getInt(0);  // _id must be in the first column
                            Path childPath = mVideoBasePath.getChild(id);
                            MediaItem item = LocalAlbum.loadOrUpdateItem(childPath, videoCursor,
                                    dataManager, mApp, false);
                            itemsToMakeStory.add((LocalMediaItem) item);
                        }
                    }
                } finally {
                    Utils.closeSilently(videoCursor);
                }
            }
            return itemsToMakeStory;
        }
还需在TrashMediaItem,TrashMediaInfo,TrashDB中加入camera_id字段相关的代码
在ThemeBitmapLoader,StoryCoverALbum中加入相关代码。
在ActionMdoeHandler#computeMenuOptions()中加入以下代码,指定selfies文件夹不允许删除,重命名,移到其他专辑操作。
//Gionee chenjs 2017-03-01 add for Selfie album begin
        if (Utils.isSupportedSelfieFile()) {
        	if (setPaths != null) {
        		for (Path path : setPaths) {
        			if (path.toString().equals("/local/all/" + MediaSetUtils.SELFIE_BUCKET_ID)) {
        				operation &= ~MediaObject.SUPPORT_DELETE;
        				operation &= ~MediaObject.SUPPORT_RENAME_FOLDER;
        				operation &= ~MediaObject.SUPPORT_MOVE_TO_OTHER;
        			}
        		}
        	}
		}
        //Gionee chenjs 2017-03-01 add for Selfie album end
                    
                
                
            
        
浙公网安备 33010602011771号