图库-由新增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