12 Gallery 源码-AlbumSetPage 数据加载和渲染过程分析
0. 原文拜读
https://blog.****.net/lgglkk/article/details/54918441
1. AlbumSetPage 界面
AlbumSetPage 是我们进入Gallery2后的第一个页面,作用是显示所有相册专辑。其结构主要由一个 SlotView 和Slot组成(如下图)。一个 Slot 代表一个相册专辑。Slot中包含的内容有:一张封面图片(一般是相册中首张图片资源),相册标题,相册大小等,如果这是一个特殊的相册,例如是一个保存Camera拍照后的相册,在相册封面上还会有Camera图标表示。
package com.android.gallery3d.app;
public class AlbumSetPage extends ActivityState implements OnClickListener,..{
private SlotView mSlotView;
}
package com.android.gallery3d.ui;
public class SlotView extends GLView {
...
}
2. AlbumSetPage.initializeViews
初始化 mConfig 获得有关 AlbumSetPage 的运行参数,包括 Slot 的组成元素的颜色,初始化每个 Slot 在 SlotView 中的大小,间隔等参数;
初始化 AlbumSetSlotRenderer,SlotView 的渲染器,实例名为 mAlbumSetView;
根据 mConfig 和 AlbumSetSlotRenderer 初始化 mSlotView,并给 mSlotView 注册事件监听; 初始化 mActionModeHandler 处理 ActionBar 事件;
最终将 SlotView 添加到 RootPane 中;
private void initializeViews() {
// mActivity -> mFragment
if (null != mFragment && mFragment.isAdded()) {
...
} else {
...
//mConfig是用来设置SlotView的参数,而SlotView就是一个相册
mConfig = Config.AlbumSetPage.get(mActivity);
mSlotView = new SlotView(mActivity, mConfig.slotViewSpec);
//mAlbumSetView是mSlotView的渲染器,控制mSlotView的显示
mAlbumSetView = new AlbumSetSlotRenderer(
mActivity, mSelectionManager, mSlotView, mConfig.labelSpec,
mConfig.slotViewSpec,
mConfig.placeholderColor);
//将mAlbumSetView设置给mSlotView
mSlotView.setSlotRenderer(mAlbumSetView);
//监听SlotView事件,根据手势操作做出相应的响应,这个监听事件的原理后面再分析
mSlotView.setListener(new SlotView.SimpleListener() {
...
});
mActionModeHandler = new ActionModeHandler(mActivity, mSelectionManager);
mActionModeHandler.setActionModeListener(new ActionModeListener() {
@Override
public boolean onActionItemClicked(MenuItem item) {
return onItemSelected(item);
}
});
//把这个SlotView作为一个子控件传给GLView,mRootPane是GLView类
mRootPane.addComponent(mSlotView);
}
}
3. AlbumSetPage.initializeData 目的是实例化数据适配器,并为渲染器提供数据接口
获得 GalleryActivity 传过来的 mediaPath,并通过 DataManager 的 getMediaSet 方法获得对应的数据源,由于传过来的 mediaPath 为
/combo/{/local/all,/picasa/all},因此得到的是一个 ComboSource 数据源,此数据源另外包含一个 LocalSource 和 EmptySource,后面这 个mMediaSet 所有的实现都可以在 ComboAlbumSet 中找到;
根据获得的数据源(mediaSet)来实例化名为 mAlbumSetDataAdapter 的 AlbumSetDataLoader 对象,作为数据适配器。数据源和适配 器都准备完毕后,调用 setMode 方法将数据适配器添加到 SlotView 的渲染器 mAlbumSetView 中;
public static final String KEY_MEDIA_PATH = "media-path";
private void initializeData(Bundle data) {
// 获取data传入的value
// /combo/{/local/all,/picasa/all}
String mediaPath = data.getString(AlbumSetPage.KEY_MEDIA_PATH);
//获取MediaSet,前面讲了每个ActivityState页面都需要一个数据源MediaSource来获取显示数据,而MediaSource是由MediaObject组成,MediaObject相当于MediaSource的单位,MediaSet就是一个MediaObject的子类,管理一组媒体数据
//获取MediaSet来管理一组媒体数据
mMediaSet = getDataManager().getMediaSet(mediaPath);
//mSelectionManager用于管理选择事件
mSelectionManager.setSourceMediaSet(mMediaSet);
//mAlbumSetDataAdapter类似于桥梁来连接页面和数据源
// 装载数据源
// mActivity -> mFragment
if (null != mFragment && mFragment.isAdded()) {
mAlbumSetDataAdapter = new AlbumSetDataLoader(
mFragment, mMediaSet, DATA_CACHE_SIZE);
} else {
mAlbumSetDataAdapter = new AlbumSetDataLoader(
mActivity, mMediaSet, DATA_CACHE_SIZE);
}
mSelectionManager.setAlbumSetDataLoader(mAlbumSetDataAdapter);
//设置数据加载的监听接口
mAlbumSetDataAdapter.setLoadingListener(new MyLoadingListener());
// 将数据适配器添加到 SlotView 的渲染器 mAlbumSetView 中
mAlbumSetView.setModel(mAlbumSetDataAdapter);
}
4. 调用数据适配器加载媒体数据,为后面渲染提供数据支持
AlbumSetPage 的 onCreate 方法执行完后,在随后执行 onResume 方法中,首先调用数据适配器(AlbumSetDataLoader)的 resume 方法开始执行数据加载操作。AlbumSetDataLoader 开启一个 ReloadTask 执行数据源 reload 工作。通过层层的 reload,并调用 BucketHelper 查询 MediaProvider 数据库,获得专辑数据并返回。
ReloadTask 的主要功能是循环调用数据源的 reload 方法,进而更新获得专辑数据,一旦获得专辑数量变化后,通过监听器回调的 方式通知 SlotView,告知专辑数,创建相应数量的 slot,这也是我们平时看到的专辑。
package com.android.gallery3d.app;
public class AlbumSetPage extends ActivityState implements OnClickListener,
SelectionManager.SelectionListener, GalleryActionBar.ClusterRunner,
EyePosition.EyePositionListener, MediaSet.SyncListener, ButtonControlsHandler.Delegate {
private AlbumSetDataLoader mAlbumSetDataAdapter;
@Override
public void onResume() {
super.onResume();
...
//数据加载就是这一步完成的
mAlbumSetDataAdapter.resume();
...
}
public void resume() {
//这个接口是数据变化的监听接口,当完成数据加载时会回调mSourceListener的onContentDirty方法
mSource.addContentListener(mSourceListener);
//ReloadTask就是完成数据加载任务的子线程,将onCreate中initData的数据加载上
mReloadTask = new ReloadTask();
mReloadTask.start();
}
// TODO: load active range first
private class ReloadTask extends Thread {
// 上述 getDataManager().getMediaSet(path) 中获取
private final MediaSet mSource;
@Override
public void run() {
......
//这里执行数据加载.多态
long version = mSource.reload();
......
}
}
package com.android.gallery3d.data;
public class LocalAlbumSet extends MediaSet
implements FutureListener<ArrayList<MediaSet>> {
@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;
//通过ThreadPool线程池执行专辑数据的加载
mLoadTask = mApplication.getThreadPool().submit(new AlbumsLoader(), this);
}
if (mLoadBuffer != null) {
// mLoadBuffer就是相册的集合,是AlbumsLoader这个callable执行结束后,在//onFutureDone()方法里,通过mLoadBuffer = future.get()返回的。
mAlbums = mLoadBuffer;
mLoadBuffer = null;
//这里就是对每个专辑进行数据加载
for (MediaSet album : mAlbums) {
album.reload();
}
mDataVersion = nextVersionNumber();
}
return mDataVersion;
}
5. 获得 AlbumSet 数据后,渲染器开始渲染 SlotView
数据适配器 AlbumSetDataLoader 中的 ReloadTask 执行完后,发送 MSG_LOAD_FINISH 广播通知到 AlbumSetPage 更新界面,并完成
package com.android.gallery3d.app;
public class AlbumSetDataLoader {
@Override
public void run() {
while (mActive) {
......
//这一块很重要,用来更新界面的
//获取需要更新的数据信息,包括专辑数量等
UpdateInfo info = executeAndWait(new GetUpdateInfo(version));
......
//根据数据信息更新界面,这个方法最终会执行UpdateContent的call方法
executeAndWait(new UpdateContent(info));
.....
}
// 这个方法发送 MSG_LOAD_FINISH消息通知数据加载完成
updateLoading(false);
}
private LoadingListener mLoadingListener;
case MSG_LOAD_FINISH:
if (mLoadingListener != null) mLoadingListener.onLoadingFinished(false);
return;
package com.android.gallery3d.app;
public class AlbumSetDataLoader {
@Override
public void run() {
...
//根据数据信息更新界面,这个方法最终会执行UpdateContent的call方法
executeAndWait(new UpdateContent(info));
...
private class UpdateContent implements Callable<Void> {
private final UpdateInfo mUpdateInfo;
@Override
public Void call() {
...
for (DataListener l : mDataListener) {
//更新内容
l.onContentChanged(info.index);
}
return null;
}
}
@Override
public void onContentChanged(int index) {
if (!mIsActive) {
// paused, ignore slot changed event
return;
}
// If the updated content is not cached, ignore it
if (index < mContentStart || index >= mContentEnd) {
Log.w(TAG, String.format(
"invalid update: %s is outside (%s, %s)",
index, mContentStart, mContentEnd) );
return;
}
// 更新图像
AlbumSetEntry entry = mData[index % mData.length];
updateAlbumSetEntry(entry, index);
updateAllImageRequests();
updateTextureUploadQueue();
if (mListener != null && isActiveSlot(index)) {
//onContentChanged方法就是执行mSlotView.invalidate()刷新界面
mListener.onContentChanged();
}