0. 原文拜读
- https://blog.csdn.net/lgglkk/article/details/54918441
- https://blog.csdn.net/yangguannan/article/details/17503393
1. 基本数据-MediaObject
1.1 MediaObject
数据渲染的最小单位,它包含丰富的衍生类。MediaObject定义了媒体数据最基本的信息,如SupportedOperations,SupportedOperations定义这个媒体文件支持的操作,如是否可以delete/share/rotate等。定义了最基本的Path路径,用于表示媒体对象的存储地址;
即通俗说,主要负责判断是否支持 剪切,放大,全屏显示,删除等操作以及相应的操作的声明
package com.android.gallery3d.data;
public abstract class MediaObject {
}
Gallery 的数据源都是基于 MediaObject 进行封装
查看所有 extends MediaObject,得到 MediaSet 和 MediaItem
packages/apps/Gallery/src/com/android/gallery3d/data/MediaSet.java:35:public abstract class MediaSet extends MediaObject {
packages/apps/Gallery/src/com/android/gallery3d/data/MediaItem.java:27:public abstract class MediaItem extends MediaObject {
1.2 MediaItem
MediaObject的衍生类,MediaObject的封装,是单个媒体的抽象,代表一张图片或者一个视频。在此抽象类中,定义getMimeType()/getWitdh()/getHeight()等抽象方法
即通俗说,增加了获取图片的名字,模式,旋转角度,大小等信息
package com.android.gallery3d.data;
public abstract class MediaItem extends MediaObject {
}
查看所有 extends MediaItem
grep -irn "extends MediaItem" packages/apps/Gallery/
packages/apps/Gallery/src/com/android/gallery3d/data/LocalMediaItem.java:30:public abstract class LocalMediaItem extends MediaItem {
packages/apps/Gallery/src/com/android/gallery3d/data/ActionImage.java:30:public class ActionImage extends MediaItem {
packages/apps/Gallery/src/com/android/gallery3d/data/UriImage.java:42:public class UriImage extends MediaItem {
packages/apps/Gallery/src/com/android/gallery3d/data/TimeLineTitleMediaItem.java:30:public class TimeLineTitleMediaItem extends MediaItem {
packages/apps/Gallery/src/com/android/gallery3d/data/SnailItem.java:30:public class SnailItem extends MediaItem {
当然我们可以继续查看对应的子类继承关系,不过这里我们暂时关注以下继承关系
1.2.1 MediaItem.LocalMediaItem
MediaItem的衍生类,对本地MediaItem的抽象,代表一张本地图片或者一个本地视频。在此抽象类中,添加定义了bucketId/dataDirty; bucketId由文件夹的绝对路径的hashCode来表示,代表一个专辑,是专辑的索引。通过GalleryUtils的getBucketId可以获得传入路径的bucketId。
package com.android.gallery3d.data;
public abstract class LocalMediaItem extends MediaItem {
}
当然我们可以继续查看对应的子类继承关系,不过这里我们暂时关注以下继承关系
grep -irn "extends LocalMediaItem" packages/apps/Gallery/
packages/apps/Gallery/src/com/android/gallery3d/data/LocalVideo.java:36:public class LocalVideo extends LocalMediaItem {
packages/apps/Gallery/src/com/android/gallery3d/data/LocalImage.java:51:public class LocalImage extends LocalMediaItem {
1.2.1.1 MediaItem.LocalMediaItem.LocalImage
LocalImage 是 LocalMediaItem的子类,表示一个本地存储的图片。内部定义了一个ITEM_PATH="/local/image/item"。首先LocalImage的初始化有两种,一种是通过直接传入cursor对象来初始化这个Image对象,另外一种是通过传入id的形式来查询外部存储的数据库,得到cursor,进而初始化这个Image对象。
package com.android.gallery3d.data;
public class LocalImage extends LocalMediaItem {
}
1.3 MediaSet
MediaObject的衍生类,是一个类目录的数据结构,是一组媒体文件的抽象。它提供的主要基础接口有getMediaItemCount, getMediaItem, getSubMediaSetCount, getSubMediaSet, getTotalMediaItemCount. 还定义了getCoverMediaItem来获得一组图片或视频的封面
即通俗说,可以包含 MediaItem也可以包含 MediaSet 除了管理 数据集应有的获取 包含的个数 等加入了 同步加载数据相关的处理操作
package com.android.gallery3d.data;
public abstract class MediaSet extends MediaObject {
}
查看所有 extends MediaSet
packages/apps/Gallery/src_pd/com/android/gallery3d/picasasource/PicasaSource.java:57: private static class EmptyAlbumSet extends MediaSet {
packages/apps/Gallery/src/com/android/gallery3d/data/SingleItemAlbum.java:21:public class SingleItemAlbum extends MediaSet {
packages/apps/Gallery/src/com/android/gallery3d/data/SecureAlbum.java:33:public class SecureAlbum extends MediaSet implements StitchingChangeListener {
packages/apps/Gallery/src/com/android/gallery3d/data/FilterDeleteSet.java:30:public class FilterDeleteSet extends MediaSet implements ContentListener {
packages/apps/Gallery/src/com/android/gallery3d/data/FilterEmptyPromptSet.java:21:public class FilterEmptyPromptSet extends MediaSet implements ContentListener
packages/apps/Gallery/src/com/android/gallery3d/data/FilterTypeSet.java:22:public class FilterTypeSet extends MediaSet implements ContentListener {
packages/apps/Gallery/src/com/android/gallery3d/data/LocalMergeAlbum.java:36:public class LocalMergeAlbum extends MediaSet implements ContentListener {
packages/apps/Gallery/src/com/android/gallery3d/data/LocalAlbum.java:42:public class LocalAlbum extends MediaSet {
packages/apps/Gallery/src/com/android/gallery3d/data/LocalAlbumSet.java:38:public class LocalAlbumSet extends MediaSet
packages/apps/Gallery/src/com/android/gallery3d/data/ClusterAlbum.java:22:public class ClusterAlbum extends MediaSet implements ContentListener {
packages/apps/Gallery/src/com/android/gallery3d/data/ClusterAlbumSet.java:28:public class ClusterAlbumSet extends MediaSet implements ContentListener {
packages/apps/Gallery/src/com/android/gallery3d/data/ComboAlbum.java:26:public class ComboAlbum extends MediaSet implements ContentListener {
packages/apps/Gallery/src/com/android/gallery3d/data/ComboAlbumSet.java:26:public class ComboAlbumSet extends MediaSet implements ContentListener {
重点查看下 LocalAlbum 和 LocalAlbumSet
1.3.1 MediaSet.LocalAlbum
继承于MediaSet,代表一个bucket(目录)下的所有的media items。提供MediaItem的查询,删除等操作。LocalAlbum是AlbumPage的单位
package com.android.gallery3d.data;
public class LocalAlbum extends MediaSet {
}
1.3.1 MediaSet.LocalAlbumSet
继承于MediaSet,是所有图片和视频专辑的集合。其内部定义了三个path,分别是PATH_ALL,PATH_IMAGE,PATH_VIDEO.其内部定义了mAlbums用来保存专辑列表。LocalAlbumSet是AlbumSetPage的单位
package com.android.gallery3d.data;
public class LocalAlbumSet extends MediaSet
implements FutureListener<ArrayList<MediaSet>> {
}
2. 数据源-MediaSource
Gallery2中引入数据源的概念,由DataManager负责管理,目的是在不同的显示界面,能通过DataManager获得一个合适的数据源来初始化自己的数据。Gallery2中主要定义的数据源有ComboSource(组合源), PicasaSource(Picasa源),LocalSource(本地源), ClusterSource(簇源), UriSource(URL源),FilterSource(过滤源)。这些数据源有一个共同的基类MediaSource, MediaSource是对数据源的抽象,它里面主要定义了数据源的基本组成,如定义了数据源的唯一标识prefix, prefix后面会讲到。
package com.android.gallery3d.data;
public abstract class MediaSource {
}
查看所有数据源
ytw012@rom:~/Android_Build_CS/android$ grep -irn "extends MediaSource" packages/apps/Gallery/
packages/apps/Gallery/src_pd/com/android/gallery3d/picasasource/PicasaSource.java:34:public class PicasaSource extends MediaSource {
packages/apps/Gallery/src/com/android/gallery3d/data/ComboSource.java:21:class ComboSource extends MediaSource {
packages/apps/Gallery/src/com/android/gallery3d/data/SnailSource.java:20:public class SnailSource extends MediaSource {
packages/apps/Gallery/src/com/android/gallery3d/data/FilterSource.java:21:public class FilterSource extends MediaSource {
packages/apps/Gallery/src/com/android/gallery3d/data/ClusterSource.java:21:class ClusterSource extends MediaSource {
packages/apps/Gallery/src/com/android/gallery3d/data/LocalSource.java:33:class LocalSource extends MediaSource {
packages/apps/Gallery/src/com/android/gallery3d/data/SecureSource.java:21:public class SecureSource extends MediaSource {
packages/apps/Gallery/src/com/android/gallery3d/data/UriSource.java:31:class UriSource extends MediaSource {
图库中支持的数据源有:
- LocalSource(本地数据)
- PicasaSource(Gmail同步)
- ComboSource(混合类型数据)
- FilterSource(过滤后的数据)
- SecureSource(安全数据)
- UriSource(Uri数据)
- ClusterSource(簇源)
- SnailSource
3.数据管理-DataManager
DataManager是用来管理整个系统中的所有media sets(集合)和media item。DataManager在Gallery Application启动时就创建并且初始化,可以通过GalleryAppImpl的getDataManager方法来获得DataManager实例,DataManager的初始化做了以下事情
package com.android.gallery3d.data;
public class DataManager implements StitchingChangeListener {
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同步,Picasa 源
addSource(new ComboSource(mApplication));// 组合源
addSource(new ClusterSource(mApplication));// 簇源
addSource(new FilterSource(mApplication));// 过滤源
addSource(new SecureSource(mApplication)); // 安全数据
addSource(new UriSource(mApplication)); // URL源
addSource(new SnailSource(mApplication));
if (mActiveCount > 0) {
for (MediaSource source : mSourceMap.values()) {
source.resume();
}
}
}
}
DataManager实例化的同时也创建了所有数据源实例,并把它们加入自身维护的一个SourceMap中,提供存取操作。SourceMap中保存的索引是上面讲到的prefix。Prefix是数据源的唯一标识,在数据源的构造方法中赋值。如LocalSource的prefix为”local”, ComboSource的prefix为”combo”。
package com.android.gallery3d.data;
public abstract class MediaSource {
protected MediaSource(String prefix) {
mPrefix = prefix;
}
}
package com.android.gallery3d.data;
class LocalSource extends MediaSource {
public LocalSource(GalleryApp context) {
super("local");
}
}
public class PicasaSource extends MediaSource {
public PicasaSource(GalleryApp application) {
super("picasa");
...
}
}
class ComboSource extends MediaSource {
public ComboSource(GalleryApp application) {
super("combo");
...
}
...
DataManager不仅提供了丰富的数据操作接口,同时定义了一组代表数据集合的PATH:
package com.android.gallery3d.data;
public class DataManager implements StitchingChangeListener {
TOP_SET_PATH = "/combo/{/local/all,/picasa/all}";//表示用户能看到的最顶端的数据集合
TOP_IMAGE_SET_PATH = "/combo/{/local/image,/picasa/image}";//表示用户能看到的最顶端的图片数据集合
TOP_VIDEO_SET_PATH = "/combo/{/local/video,/picasa/video}";//表示用户能看到的最顶端的视频数据集合
TOP_LOCAL_SET_PATH = "/local/all";//表示用户能看到的最顶端的本地数据集合
TOP_LOCAL_IMAGE_SET_PATH = "/local/image";//表示用户能看到的最顶端的本地图片集合
TOP_LOCAL_VIDEO_SET_PATH = "/local/video";//表示用户能看到的最顶端的本地视频集合
}
通过上述路径查看,数据范围的比较如下:
- TOP_SET_PATH > TOP_IMAGE_SET_PATH = TOP_VIDEO_SET_PATH > TOP_LOCAL_SET_PATH > TOP_LOCAL_IMAGE_SET_PATH = TOP_LOCAL_VIDEO_SET_PATH
3.1 以LocalSource本地数据源为例
LocalSource表示本地存储器中的所有Media数据源,负责管理Local Media数据集。从它的createMediaObject方法(继承于MediaSource)可以看出,它可以根据传入的path路径,创建出LocalAlbumSet,LocalAlbum,LocalMergeAlbum,LocalImage,LocalVideo所有本地媒体数据相关的数据集合以及单个媒体文件。
package com.android.gallery3d.data;
class LocalSource extends MediaSource {
@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,mApplication, comp, new MediaSet[] {imageSet, videoSet}, bucketId, false);
}
case LOCAL_IMAGE_ITEM:
return new LocalImage(path, mApplication, mMatcher.getIntVar(0));
case LOCAL_VIDEO_ITEM:
return new LocalVideo(path, mApplication, mMatcher.getIntVar(0));
default:
throw new RuntimeException("bad path: " + path);
}
}
}
那么LocalSource是怎样根据传入的path来生成AlbumSet,还是Album呢?首先我们先来看看LocalSource的构造方法:
答案在 xxx_ALBUMSET 和 xxx_IMAGE_ITEM 中
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);
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的构造方法中实例化了PathMatcher,并将所有代表local资源相关的path及其类型添加到PathMatcher实例中。这里PathMatcher的作用是维护一个树结构,用于保存path以及匹配path类型。PathMatcher类内部定义一个Node(节点),代表树的一个节点。Node由HashMap以及一个整型kind组成,其中HashMap用来保存路径子段和Node的映射,而整型kind用来保存该节点的类型,如(LOCAL_IMAGE_ALBUMSET/LOCAL_VIDEO_ALBUMSET)等。先来说一下PathMatcher的实现过程,在PathMatcher的构造方法中,首先创建了一个名为Root的树的根节点,这个Root的根节点作为match操作的入口。另外,PatchMatcher通过add方法,先将传入的path路径以”/”为分割符创建segments数组,然后通过segments数组的元素构造树结构,并给最后一个节点的kind类型赋值,表示从根节点到该节点生成的path代表哪个类型的媒体结构。
匹配的过程如下:
- path = “/local/image/item/10001”
序列为:
- [local][image][item][10001]
二叉树查询:
- Kind=LOCAL_IMAGE_ITEM, 生成LocalImage, id=10001
3.2 Media 数据的加载过程
我们从点击Gallery2图标进入图片专辑页面这个过程为例,描述一下Local数据的加载过程。
首先点击图库图标进入GalleryActivity(旧版本或者命名为Gallery),这个Activity是整个图库程序的入口,非外部ACTION_VIEW调用下,调用startDefaultPage启动AlbumSetPage(就是我们打开Gallery2后见到的第一个专辑页面),这时传入给AlbumSetPage一个名为media-path的参数,media-path值为"/combo/{/local/all,/picasa/all}",这个是一个combo类型的path,表示需要显示local以及picasa两个组合的所有的媒体文件.
public static final String KEY_MEDIA_PATH = "media-path";
public static final int INCLUDE_IMAGE = 1;
public static final int INCLUDE_VIDEO = 2;
public static final int INCLUDE_ALL = INCLUDE_IMAGE | INCLUDE_VIDEO;
public void startAlbumPage() {
PicasaSource.showSignInReminder(this);
Bundle data = new Bundle();
int clusterType = FilterUtils.CLUSTER_BY_ALBUM;
data.putString(AlbumSetPage.KEY_MEDIA_PATH, getDataManager()
.getTopSetPath(DataManager.INCLUDE_ALL));
...
getStateManager().startState(AlbumSetPage.class, data);
...
}
// This is the path for the media set seen by the user at top level.
private static final String TOP_SET_PATH = "/combo/{/local/all,/picasa/all}";
public String getTopSetPath(int typeBits) {
...
switch (typeBits) {
...
case INCLUDE_ALL:
// "/combo/{/local/all,/picasa/all}"
// combo 类型的 path,表示需要显示local以及picasa两个组合的所有的媒体文件
return TOP_SET_PATH;
}
...
}
具体的解析步骤如下:
- 在AlbumSetPage的 initializeData 方法取出 media-path,mediaPath = /combo/{/local/all,/picasa/all}
private void initializeData(Bundle data) {
// mediaPath = /combo/{/local/all,/picasa/all}
String mediaPath = data.getString(AlbumSetPage.KEY_MEDIA_PATH);
}
- AlbumSetPage通过DataManager实例解析出由两个segments组成的url
- Segments[0]:combo
- Segments[1]:{/local/all,/picasa/all}
mMediaSet = getDataManager().getMediaSet(mediaPath);
public MediaObject getMediaObject(String s) {
return getMediaObject(Path.fromString(s));
}
// /combo/{/local/all,/picasa/all}
public static Path fromString(String s) {
synchronized (Path.class) {
// Segments[0]:combo
// Segments[1]:{/local/all,/picasa/all}
String[] segments = split(s);
Path current = sRoot;
for (int i = 0; i < segments.length; i++) {
current = current.getChild(segments[i]);
}
return current;
}
}
- 第二部解析出来path的prefix(前缀)是 combo,DataManager通过这个prefix取得对应的数据源,这里获得的数据源是 ComboSource
public class Path {
private IdentityCache<String, Path> mChildren;
public Path getChild(String segment) {
synchronized (Path.class) {
if (mChildren == null) {
mChildren = new IdentityCache<String, Path>();
} else {
Path p = mChildren.get(segment);
if (p != null) return p;
}
Path p = new Path(this, segment);
mChildren.put(segment, p);
return p;
}
}
private Path(Path parent, String segment) {
mParent = parent;
mSegment = segment;
}
public String getPrefix() {
if (this == sRoot) return "";
return getPrefixPath().mSegment;
}
}
public MediaSet getMediaSet(Path path) {
return (MediaSet) getMediaObject(path);
}
public MediaObject getMediaObject(Path path) {
synchronized (LOCK) {
MediaObject obj = path.getObject();
if (obj != null) return obj;
// 解析出来path的prefix(前缀)是 combo,故 MediaSource 为 ComboSource
MediaSource source = mSourceMap.get(path.getPrefix());
...
// ComboSource 查找 {/local/all,/picasa/all},对应的数据
MediaObject object = source.createMediaObject(path);
...
}
}
- DataManager调用ComboSource的createMediaObject方法来初始化ComboAlbumSet实例返回到AlbumSetPage,与此同时,在构造ComboAlbumSet时,继续分拆大括号里的/local/all和/picasa/all,生成 LocalSource 实例和 EmptySource 实例。
class ComboSource extends MediaSource {
public ComboSource(GalleryApp application) {
super("combo");
mApplication = application;
mMatcher = new PathMatcher();
mMatcher.add("/combo/*", COMBO_ALBUMSET);
}
@Override
public MediaObject createMediaObject(Path path) {
// {/local/all,/picasa/all}
String[] segments = path.split();
if (segments.length < 2) {
throw new RuntimeException("bad path: " + path);
}
DataManager dataManager = mApplication.getDataManager();
switch (mMatcher.match(path)) {
case COMBO_ALBUMSET:
// 继续分拆大括号里的/local/all和/picasa/all
// LocalSource 实例和 EmptySource 实例
return new ComboAlbumSet(path, mApplication,
dataManager.getMediaSetsFromString(segments[1]));
....
}
...
}
4.1 分拆/local/all,创建 LocalSource 数据源,生成 LocalAlbumSet 实例。
Segments[0]:local
Segments[1]:all
- current path=/local/all Prefix=local
public LocalSource(GalleryApp context) {
super("local");
...
mMatcher.add("/local/all", LOCAL_ALL_ALBUMSET);
...
}
@Override
public MediaObject createMediaObject(Path path) {
GalleryApp app = mApplication;
switch (mMatcher.match(path)) {
case LOCAL_ALL_ALBUMSET:
return new LocalAlbumSet(path, mApplication);
...
}
4.2 分拆/picasa/all,创建 PicasaSource 数据源,生成EmptyAlbumSet实例
Segments[0]:picasa
Segments[1]:all
- current path=/picasa/all Prefix=picasa
public PicasaSource(GalleryApp application) {
super("picasa");
mApplication = application;
mMatcher = new PathMatcher();
mMatcher.add("/picasa/all", PICASA_ALBUMSET);
mMatcher.add("/picasa/image", PICASA_ALBUMSET);
mMatcher.add("/picasa/video", PICASA_ALBUMSET);
}
@Override
public MediaObject createMediaObject(Path path) {
switch (mMatcher.match(path)) {
case PICASA_ALBUMSET:
return new EmptyAlbumSet(path, MediaObject.nextVersionNumber());
default:
throw new RuntimeException("bad path: " + path);
}
}
(1)~(4)是AlbumSetPage中initializeData调用获取mMediaSet所做的事情,这里返回的mMediaSet实例就是指向一个由ComboSource数据源创建的ComboAlbumSet实例,后面这个mMediaSet的所有实现都可以在ComboAlbumSet中找到。获得mMediaSet后,initializeData里继续做的事情有,用mMediaSet初始化mSelectionManager来管理这个界面的所有选择操作;用mMediaSet初始化AlbumSetDataLoader实例,作为数据适配器,数据取自mMediaSet,这里生成的实例名叫mAlbumSetDataAdapter,也很形象,数据源和适配器都准备完毕后,mAlbumSetView通过setMode方法将适配器传入渲染器中。