您的位置:首页 > 其它

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

2016-03-27 17:39 441 查看
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,我们进入这个方法看看:

这里的mActivity为GalleryAppImpl类。
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();

调用AlbumSetDataLoader的onResume函数中,调用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);
}
解码后就可以生成缩略图了。
其中类的关系流程图如下:
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: