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

数据加载的准备阶段

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

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

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

  1. //根据路径获取MediaObject,s为"/combo/{/local/all,/picasa/all}"
  2. public MediaSet getMediaSet(String s) {
  3. return (MediaSet) getMediaObject(s);
  4. }
  5.  
  6. public MediaObject getMediaObject(String s) {
  7. return getMediaObject(Path.fromString(s));
  8. }
  9.  
  10. //进到PATH类中
  11. private WeakReference<MediaObject> mObject;
  12. private IdentityCache<String, Path> mChildren;
  13.  
  14. public static Path fromString(String s) {
  15. synchronized (Path.class) {
  16. String[] segments = split(s);
  17. //segments为["combo", "{/local/all,/picasa/all}"]
  18. Path current = sRoot;
  19. for (int i = 0; i < segments.length; i++) {
  20. current = current.getChild(segments[i]);
  21. }
  22. //经过for循环,current会持有两条路径,"combo"为父PATH,"{/local/all,/picasa/all}"为子PATH
  23. return current;
  24. }
  25. }
  26.  
  27. //获取PATH对应得MediaObject
  28. public MediaObject getMediaObject(Path path) {
  29. synchronized (LOCK) {
  30. //根据PATH获取MediaObject,不为空直接返回
  31. MediaObject obj = path.getObject();
  32. if (obj != null) return obj;
  33.  
  34. //根据PATH的前缀获取mSourceMap对应的MediaSource,mSourceMap初始化在源码分析2中讲过,这里返回的就是ComboSource
  35. MediaSource source = mSourceMap.get(path.getPrefix());
  36. ......
  37.  
  38. try {
  39. //走到这里说明MediaObject为空,所以需要创建MediaObject
  40. MediaObject object = source.createMediaObject(path);
  41. return object;
  42. ......
  43. }
  44. }

我们接着看下ComboSource的createMediaObject方法

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

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

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

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

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

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

数据加载过程

首先查看GalleryActivity的OnResume方法,

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

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

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

StateManager().resume的方法如下:

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

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

  1. void resume() {
  2. ......
  3. //这里就是调用AlbumSetPage的onResume方法
  4. onResume();
  5. ......
  6. }
  7.  
  8. public void onResume() {
  9. ......
  10. //数据加载就是这一步完成的
  11. mAlbumSetDataAdapter.resume();
  12. ......

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

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

我们看一下ReloadTask的run方法

  1. public void run() {
  2. ......
  3. //这里执行数据加载
  4. long version = mSource.reload();
  5. ......
  6. }
  7.  
  8. mSourcenew AlbumSetDataLoader(
  9. mActivity, mMediaSet, DATA_CACHE_SIZE)
  10. //传入的mMediaSet,前面讲了mMediaSet就是ComboAlbumSet,
  11. //它包含一个LocalAlbumSet和EmptyAlbumSet

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

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

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

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

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

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

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

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

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

  1. private class AlbumsLoader implements ThreadPool.Job<ArrayList<MediaSet>> {
  2.  
  3. @Override
  4. @SuppressWarnings("unchecked")
  5. public ArrayList<MediaSet> run(JobContext jc) {
  6. ......
  7. //通过BucketHelper获取所有的专辑信息
  8. BucketEntry[] entries = BucketHelper.loadBucketEntries(
  9. jc, mApplication.getContentResolver(), mType);
  10. ......
  11. for (BucketEntry entry : entries) {
  12. //获取LocalAlbum并保存到ArrayList(albums)中,albums对应于每个专辑
  13. MediaSet album = getLocalAlbum(dataManager,
  14. mType, mPath, entry.bucketId, entry.bucketName);
  15. albums.add(album);
  16. }

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

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

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

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

更新界面

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

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

  1. source.setModelListener(this);

接着看AlbumSetSlidingWindow的onSizeChanged和onContentChanged方法

  1. public void onSizeChanged(int size) {
  2. if (mIsActive && mSize != size) {
  3. mSize = size;
  4. //mListener是AlbumSetSlotRenderer的,MyCacheListener,onSizeChanged就是执行mSlotView.setSlotCount(size)
  5. if (mListener != null) mListener.onSizeChanged(mSize);
  6. if (mContentEnd > mSize) mContentEnd = mSize;
  7. if (mActiveEnd > mSize) mActiveEnd = mSize;
  8. }
  9. }
  10.  
  11. public void onContentChanged(int index) {
  12. //更新图像
  13. AlbumSetEntry entry = mData[index % mData.length];
  14. updateAlbumSetEntry(entry, index);
  15. updateAllImageRequests();
  16. updateTextureUploadQueue();
  17. //onContentChanged方法就是执行mSlotView.invalidate()刷新界面
  18. if (mListener != null && isActiveSlot(index)) {
  19. mListener.onContentChanged();
  20. }
  21. }

到这里就完成了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. K3 新单到老单关联字段的添加

    新单到老单字段的添加分为两种: 一种为文本字段信息的关联,新单与老单字段的信息均为文本字段: 另一种为基础资料信息的关联,新单与老单均为基础资料字段信息.       K3 WISE 11.0中存储老 ...

  2. mysql修改时区的几种方法(转载自https://www.cnblogs.com/shiqiangqiang/p/8393662.html)

    说明: 以下记录修改mysql时区的几种方法. 具体: 方法一:通过mysql命令行模式下动态修改 1.1 查看mysql当前时间,当前时区 select curtime(); #或select no ...

  3. Node-Blog整套前后端学习记录

    Node-Blog 后端使用node写的一个一整套的博客系统 #### 主要功能 登录 注册 发表文章 编辑/删除文章 添加/删除/编辑文章分类 账号的管理 评论功能 ... 所用技术 node ex ...

  4. BZOJ 4367 [IOI2014]holiday (决策单调DP+主席树+分治)

    题目大意:略 题目传送门 神题,不写长题解简直是浪费了这道题 贪心 考虑从0节点出发的情况,显然一直往前走不回头才是最优策略 如果起点是在中间某个节点$s$,容易想到,如果既要游览$s$左边的某些景点 ...

  5. vue 如何动态切换组件,使用is进行切换

    日常项目中需要动态去切换组件进行页面展示. 例如:登陆用户是“管理员”或者“普通用户”,需要根据登陆的用户角色切换页面展示的内容.则需要使用 :is 属性进行绑定切换 <template> ...

  6. 【python正则】工作中常用的python正则代码

    工作中常用的一些正则代码: 01.用户名正则 import re # 4到16位(字母,数字,下划线,减号)if re.match(r'^[a-zA-Z0-9_-]{4,16}$', "ab ...

  7. UVA11752 The Super Powers

    /* UVA11752 The Super Powers https://vjudge.net/contest/153365#problem/Y 数论 注意a^n=b要用除法求,并且求得的n比实际大1 ...

  8. nodejs-路由(待补充)

    path Router 1 2 3 4 5 var express = require('express'); var Router = express.Router(); Router.get('/ ...

  9. BA-通讯总线-百通1419a和9841

    百通1419A线缆的简单介绍: Belden1419A- Belden电缆线1419A 多股导体—低容计算机电缆 FOR EIA RS-232/422 Belden 1419A是24 AWG(7*32 ...

  10. [SharePoint][SharePoint Designer 入门经典]Chapter8 XSLT数据试图和表单

    本章概要: 1.不是利用XSLT web部件 2.使用XSLT web部件创建数据试图 3.使用XSLT表单web部件创建自定义表单 4.利用自定义动作执行列表表单