前面分析Gallery启动流程时,说了传给DataManager的data的key是AlbumSetPage.KEY_MEDIA_PATH,value值,是”/combo/{/local/all,/picasa/all}”,下面分析具体怎么加载数据的。

数据加载的准备阶段

数据初始化是在AlbumSetPage的initializeData方法中。

 private void initializeData(Bundle data) {
//mediaPath即为"/combo/{/local/all,/picasa/all}"
String mediaPath = data.getString(AlbumSetPage.KEY_MEDIA_PATH);
//获取MediaSet来管理一组媒体数据
mMediaSet = mActivity.getDataManager().getMediaSet(mediaPath);
/mSelectionManager用于管理选择事件
mSelectionManager.setSourceMediaSet(mMediaSet);
//mAlbumSetDataAdapter类似于桥梁来连接页面和数据源
mAlbumSetDataAdapter = new AlbumSetDataLoader(
mActivity, mMediaSet, DATA_CACHE_SIZE);
//设置数据加载的监听接口
mAlbumSetDataAdapter.setLoadingListener(new MyLoadingListener());
mAlbumSetView.setModel(mAlbumSetDataAdapter);
}

mActivity.getDataManager()就是获取Application(GalleryAppImpl)的DataManager,我们接着看getMediaSet方法,

 //根据路径获取MediaObject,s为"/combo/{/local/all,/picasa/all}"
public MediaSet getMediaSet(String s) {
return (MediaSet) getMediaObject(s);
} public MediaObject getMediaObject(String s) {
return getMediaObject(Path.fromString(s));
} //进到PATH类中
private WeakReference<MediaObject> mObject;
private IdentityCache<String, Path> mChildren; public static Path fromString(String s) {
synchronized (Path.class) {
String[] segments = split(s);
//segments为["combo", "{/local/all,/picasa/all}"]
Path current = sRoot;
for (int i = 0; i < segments.length; i++) {
current = current.getChild(segments[i]);
}
//经过for循环,current会持有两条路径,"combo"为父PATH,"{/local/all,/picasa/all}"为子PATH
return current;
}
} //获取PATH对应得MediaObject
public MediaObject getMediaObject(Path path) {
synchronized (LOCK) {
//根据PATH获取MediaObject,不为空直接返回
MediaObject obj = path.getObject();
if (obj != null) return obj; //根据PATH的前缀获取mSourceMap对应的MediaSource,mSourceMap初始化在源码分析2中讲过,这里返回的就是ComboSource
MediaSource source = mSourceMap.get(path.getPrefix());
...... try {
//走到这里说明MediaObject为空,所以需要创建MediaObject
MediaObject object = source.createMediaObject(path);
return object;
......
}
}

我们接着看下ComboSource的createMediaObject方法

 public MediaObject createMediaObject(Path path) {
//segments为["combo", "{/local/all,/picasa/all}"]
String[] segments = path.split();
......
//match结果为COMBO_ALBUMSET
switch (mMatcher.match(path)) {
//创建一个ComboAlbumSet并返回,dataManager.getMediaSetsFromString(segments[1])
         //这个方法就是根据"{/local/all,/picasa/all}"创建LocalSource实例和PicasaSource实例以及对应的LocalAlbumSet实例和EmptyAlbumSet实例,这个过程就是重复上述步骤
case COMBO_ALBUMSET:
return new ComboAlbumSet(path, mApplication,
dataManager.getMediaSetsFromString(segments[1]));
......
}

创建好后,最终返回给AlbumSetPage的initializeData方法中的mMediaSet

 private void initializeData(Bundle data) {
......
//mMediaSet就是ComboAlbumSet,也就是数据源,它管理着LocalAlbumSet和EmptyAlbumSet
mMediaSet = mActivity.getDataManager().getMediaSet(mediaPath); mSelectionManager.setSourceMediaSet(mMediaSet);
//mAlbumSetDataAdapter类似于桥梁来连接页面和数据源
mAlbumSetDataAdapter = new AlbumSetDataLoader(
mActivity, mMediaSet, DATA_CACHE_SIZE);
mAlbumSetDataAdapter.setLoadingListener(new MyLoadingListener());
将mAlbumSetDataAdapter传给界面显示的渲染器
mAlbumSetView.setModel(mAlbumSetDataAdapter);
}

setModel这个方法挺重要的,它在AlbumSetSlotRenderer中,我们具体看一下

 public void setModel(AlbumSetDataLoader model) {
......
if (model != null) {
//根据model创建AlbumSetSlidingWindow,它是负责滑动显示图片的,比如解码专辑缩略图等
mDataWindow = new AlbumSetSlidingWindow(
mActivity, model, mLabelSpec, CACHE_SIZE);
//设置监听接口,处理尺寸改变或内容改变的事件
mDataWindow.setListener(new MyCacheListener());
mSlotView.setSlotCount(mDataWindow.size());
}
}

到这里数据源和数据源适配器都创建好了,并且也传给了AlbumSetPage页面,这样数据加载的准备工作就做好了,也就是onCreate方法执行结束,下面分析onResume方法,这里完成数据的实际加载过程。

数据加载过程

首先查看GalleryActivity的OnResume方法,

 protected void onResume() {
//调用其父类的OnResume方法
super.onResume();
}
}

我们接着查看AbstractGalleryActivity的的OnResume方法

 protected void onResume() {
......
try {
//数据加载的核心在这里
getStateManager().resume();
//这个方法只有LocalSource获取ContentProvider,别的都是什么操作都没有
getDataManager().resume();
}
mGLRootView.onResume();
mOrientationManager.resume();
}

StateManager().resume的方法如下:

 public void resume() {
//我们是从桌面图标进的应用,所以getTopState获取的是AlbumSetPage
if (!mStack.isEmpty()) getTopState().resume();
}

我们看一下AlbumSetPage的resume方法,AlbumSetPage没有重写resume方法,所以调用的是其父类ActivityState的resume方法,我们先看一下

 void resume() {
......
//这里就是调用AlbumSetPage的onResume方法
onResume();
......
} public void onResume() {
......
//数据加载就是这一步完成的
mAlbumSetDataAdapter.resume();
......

前面讲了mAlbumSetDataAdapter是一个AlbumSetDataLoader类,所以我们去看AlbumSetDataLoader的resume方法

 public void resume() {
//这个接口是数据变化的监听接口,当完成数据加载时会回调mSourceListener的onContentDirty方法
mSource.addContentListener(mSourceListener);
//ReloadTask就是完成数据加载任务的子线程
mReloadTask = new ReloadTask();
mReloadTask.start();
}

我们看一下ReloadTask的run方法

 public void run() {
......
//这里执行数据加载
long version = mSource.reload();
......
} mSource是new AlbumSetDataLoader(
mActivity, mMediaSet, DATA_CACHE_SIZE)
//传入的mMediaSet,前面讲了mMediaSet就是ComboAlbumSet,
//它包含一个LocalAlbumSet和EmptyAlbumSet

我们去ComboAlbumSet类中查看它的reload方法

  public long reload() {
//mSets即为ComboAlbumSet所包含的LocalAlbumSet和EmptyAlbumSet,这里也就是分别调用LocalAlbumSet和EmptyAlbumSet的reload方法
for (int i = 0, n = mSets.length; i < n; ++i) {
long version = mSets[i].reload();
......

因为EmptyAlbumSet的reload方法就是返回数据版本,所以暂且不管它。下面只分析LocalAlbumSet的reload方法。

 public synchronized long reload() {
......
//通过ThreadPool线程池执行专辑数据的加载,AlbumsLoader方法看下面讲述
mLoadTask = mApplication.getThreadPool().submit(new AlbumsLoader(), this);
//这里就是对每个专辑进行数据加载,这之后的就不讲了
for (MediaSet album : mAlbums) {
album.reload();
}

submit方法就是把job和listener封装成一个Worker,然后传给ThreadPoolExecutor执行

 public <T> Future<T> submit(Job<T> job, FutureListener<T> listener) {
Worker<T> w = new Worker<T>(job, listener);
mExecutor.execute(w);
return w;
}

ThreadPoolExecutor的execute方法最终也是执行Worker的run方法,现在看下Worker的run方法

 public void run() {
......
//mJob就是submit传进来的new AlbumsLoader()
result = mJob.run(this);
......
//mListener是FutureListener接口,这里也就是LocalAlbumSet自身
if (mListener != null) mListener.onFutureDone(this);
}

接着看下AlbumsLoader的run方法,这里主要是获取专辑的信息

 private class AlbumsLoader implements ThreadPool.Job<ArrayList<MediaSet>> {

         @Override
@SuppressWarnings("unchecked")
public ArrayList<MediaSet> run(JobContext jc) {
......
//通过BucketHelper获取所有的专辑信息
BucketEntry[] entries = BucketHelper.loadBucketEntries(
jc, mApplication.getContentResolver(), mType);
......
for (BucketEntry entry : entries) {
//获取LocalAlbum并保存到ArrayList(albums)中,albums对应于每个专辑
MediaSet album = getLocalAlbum(dataManager,
mType, mPath, entry.bucketId, entry.bucketName);
albums.add(album);
}

当AlbumsLoader的run方法执行完后,接着执行mListener.onFutureDone回调接口,通过这个接口通知MediaSet内容有变化。最终会走到AlbumSetDataLoader的onContentDirty方法

 public void onContentDirty() {
//这个方法会唤醒所以wait的线程
mReloadTask.notifyDirty();
}

现在我们回到AlbumSetDataLoader的ReloadTask中,reload方法执行之后会通过updateLoading发送MSG_LOAD_FINISH消息

 while (mActive) {
......
//这一块很重要,用来更新界面的
//获取需要更新的数据信息,包括专辑数量等,这里我不细讲了,自己看代码
UpdateInfo info = executeAndWait(new GetUpdateInfo(version));
......
//根据数据信息更新界面,这个方法最终会执行UpdateContent的call方法
executeAndWait(new UpdateContent(info));
} //这个方法发送 MSG_LOAD_FINISH消息通知数据加载完成,这里不细讲了
updateLoading(false);

更新界面

 private class UpdateContent implements Callable<Void> {
public Void call() {
//这里是更新Slot数目
if (mDataListener != null) mDataListener.onSizeChanged(mSize);
......
//更新内容
mDataListener.onContentChanged(info.index);
}
}

mDataListener是实例化AlbumSetSlidingWindow是设置的,也就是AlbumSetSlidingWindow自身

source.setModelListener(this);

接着看AlbumSetSlidingWindow的onSizeChanged和onContentChanged方法

 public void onSizeChanged(int size) {
if (mIsActive && mSize != size) {
mSize = size;
//mListener是AlbumSetSlotRenderer的,MyCacheListener,onSizeChanged就是执行mSlotView.setSlotCount(size)
if (mListener != null) mListener.onSizeChanged(mSize);
if (mContentEnd > mSize) mContentEnd = mSize;
if (mActiveEnd > mSize) mActiveEnd = mSize;
}
} public void onContentChanged(int index) {
//更新图像
AlbumSetEntry entry = mData[index % mData.length];
updateAlbumSetEntry(entry, index);
updateAllImageRequests();
updateTextureUploadQueue();
//onContentChanged方法就是执行mSlotView.invalidate()刷新界面
if (mListener != null && isActiveSlot(index)) {
mListener.onContentChanged();
}
}

到这里就完成了SlotView的渲染准备工作,至于怎么渲染到屏幕上见Gallery图库源码分析6

Android 7.0 Gallery图库源码分析3 - 数据加载及显示流程的更多相关文章

  1. Android 7.0 Gallery图库源码分析2 - 分析启动流程

    前面一讲解了Gallery启动Activity以及界面如何绘制,现在开始讲解启动流程的代码逻辑. GalleryActivity的onCreate方法中调用initializeByIntent()方法 ...

  2. Android 7.0 Gallery图库源码分析1 - 初识Gallery源码

    分析一个项目的源代码时,第一件事就是查看清单文件,找到程序入口,我们从Gallery2源码的清单文件中可以看到GalleryActivity是此应用的启动Activity. <activity ...

  3. Android 7.0 Gallery图库源码分析4 - SlotView手势监听及页面跳转

    上篇文章讲了初始化View时会实例化一个SlotView并监听其事件,至于它是怎么实现的,用的是Android自带的GestureDetector. GestureDetector是Android自带 ...

  4. Android7.0 Phone应用源码分析(三) phone拒接流程分析

    本文主要分析Android拒接电话的流程,下面先来看一下拒接电话流程时序图 步骤1:滑动按钮到拒接图标,会调用到AnswerFragment的onDecline方法 com.android.incal ...

  5. Android7.0 Phone应用源码分析(四) phone挂断流程分析

    电话挂断分为本地挂断和远程挂断,下面我们就针对这两种情况各做分析 先来看下本地挂断电话的时序图: 步骤1:点击通话界面的挂断按钮,会调用到CallCardPresenter的endCallClicke ...

  6. 从SpringBoot源码分析 配置文件的加载原理和优先级

    本文从SpringBoot源码分析 配置文件的加载原理和配置文件的优先级     跟入源码之前,先提一个问题:   SpringBoot 既可以加载指定目录下的配置文件获取配置项,也可以通过启动参数( ...

  7. 【Spring源码分析】Bean加载流程概览

    代码入口 之前写文章都会啰啰嗦嗦一大堆再开始,进入[Spring源码分析]这个板块就直接切入正题了. 很多朋友可能想看Spring源码,但是不知道应当如何入手去看,这个可以理解:Java开发者通常从事 ...

  8. 【Spring源码分析】Bean加载流程概览(转)

    转载自:https://www.cnblogs.com/xrq730/p/6285358.html 代码入口 之前写文章都会啰啰嗦嗦一大堆再开始,进入[Spring源码分析]这个板块就直接切入正题了. ...

  9. Dubbo源码分析之ExtensionLoader加载过程解析

    ExtensionLoader加载机制阅读: Dubbo的类加载机制是模仿jdk的spi加载机制:  Jdk的SPI扩展加载机制:约定是当服务的提供者每增加一个接口的实现类时,需要在jar包的META ...

随机推荐

  1. 洛谷P2827 蚯蚓 队列 + 观察

    我们不难发现先被切开的两半一定比后被切开的两半大,这样就天然的生成了队列的单调性,就可以省去一个log.所以,我们开三个队列,分别为origin,big,smallorigin, big, small ...

  2. Pyhton学习——Day3

    # li = [1,12,9,"age","alex"]# #列表表示为[]括起来# #列表用","分割每个元素# #列表中的元素可以是数字 ...

  3. 发现被坑了,从来没看到说java的Date一旦实例化时间就不会变了

    java中使用Date对象获取系统当前时间,然而我就没看到哪篇教程告诉我说Date创建对象之后其中的时间是不会变的!!! 一开始我写了类似于下边这样的代码,希望每隔一段时间显示一次时间 Date d= ...

  4. navicat Premium远程链接mysql报错

    1,报错1057,原来是没有远程权限连接mysql 2.打开my.ini文件,添加skip-grant-tables跳过验证 3.添加到path环境变量,前面是英文下的分号 4.切换到cmd,输入my ...

  5. python笔记4----字典

    1.哈希: 输入任意长度,输出固定长度. 即判断是否哈希,即判断可不可变. 2.创建字典 (1)直接创建:dic={1:'a',2:'b',3:'c'} (2)dict函数创建: list=[(1,' ...

  6. git 教程2 (git常用命令解说)

    <1>$ git -- help (调出git的帮助文档) <2>$ git +命令 --help (查看某个具体命令的帮助文档) <3>$ git --versi ...

  7. 通过腾讯地图api获取用户位置限制在指定位置区域

    <!--在微信中获取用户位置--><script src="http://res.wx.qq.com/open/js/jweixin-1.0.0.js">& ...

  8. 关于java发送email

    转载:https://blog.csdn.net/qq_32371887/article/details/72821291 1:使用JavaMail发送邮件 // 1.创建一个程序与邮件服务器会话对象 ...

  9. 【ASP】在特定的范围内产生N个不同的随机数

    ASP产生一个随机数不难.就两条特定语句: <% Randomize x=int(20*rnd+1) %> 以上的两条语句.表示从1~20这个范围内产生随机数,而且这些随机数都是整数. 那 ...

  10. Oracle 中UNDO与REDO的差别具体解释

    一 为了更清楚的看出2者差别,请看下表:                                               UNDO                             ...