图库的数据管理模型

图1 页面AlbumPage在Activity resume时,数据模型的时序图

 图2 在AlbumPage滑动时数据模型的时序图

 图3 AlbumPage启动时创建MediaSet实例的时序图

 图4 数据模型类图1

 图5 数据模型类图2

Path

Path是MediaObject体系能成为组合模式的关键。组合模式中,MediaObject体系依赖Path才可以向上遍历和向下遍历,path的存在还允许用户根据以String代表的路径或者segment来寻找来找到对应的MediaObject。

mChildren : 以segment:String作为key,Path作为value。其实IdentityCache是一个HashMap。mChildren中的Path是该Path的子Path。

mParent:是该Path的父Path。这决定了Path组成的树是可以向上遍历的。

mSegment:就是该Path的在父Path中的唯一标志,该Path在全局中的唯一标识则由父Path和segment组成。

sRoot:这是个final静态变量。是Path树的根。

fromString( String ) : 将路径从根部sRoot开始向下找到对应的Path,如果没有就根据这个String,新建这个路径,就像文件一样,文件的路径是由文件名和文件夹名组成的,如果该路径中的文件夹不存在,会先去新建

                                 文件夹,根据String去新间Path也是同样的道理。

getChild( String segment) : 根据直接下级的名字去获取该Path下的child。没有就new一个放到mChildren中。

getPrefix() : 获取这个路径的根路径,就是sRoot的直接下级。每个MediaSource都对应一个特定prefix。每个MediaSource都是一个工厂模式,根据Path或者Path的String生产一类产品,这些产品就是MediaObject的子类。

setObject (MediaObject): 为该Path节点设置节点数据,就是对应的MediaObject,可以是MediaItem(如:LocalImage,LocalVideo),可以是MediaSet(如:LocalAlbum),也可以是MediaSet的集合(如: LocalAlbumSet)

 

MediaSource

MediaSource体系组成了参数化工厂方法模式

MediaSource的持有者是Datamanager

mPrefix : String

每个MediaSource子类都一个特殊前缀,如:LocalSource的前缀是“local”,而传入工厂方法的参数(path)的首segment都是local才能正确匹配,如:/local/image/item/*

createMediaObject(Path) : MediaObject

根据Path对应String路径去匹配,然后返回不同的MediaObject,可能是MediaItem(代表单张照片),或者MediaSet(代表一个相册),又或是AlbumSet(代表一个相册集合)。

由于MediaSource是MediaObject的制造工厂,所以MediaObject的的构造方法不应该是public,而是应该是默认权限,这样更能对MediaObject的实例化进行监控起来。

 

DataManager

mSourceMap : HashMap<String, MediaSource>key是MediaSource的prefix; 保存了图库的所有MediaSource子类的实例,这是MediaSource唯一被初始化的i地方,而且MediaSource各子类的实例也之应只有一个,然后却没有设计成单例模式。

mNotifierMap : HashMap<Uri, NotifyBroker>

NotifyBroker继承了ContentObserver,用于监听key中的Uri。每个MediaSet持有一个NotifyBroker。就是说每个MediaSet都会独立去监听感兴趣的数据的状态

getMediaObject(Path) : MediaObject

尝试从Path中获取MediaObject,没有的话就根据Path的prefix去选择对应的MediaSource去createMediaObject(path),此时Path就和这个MediaObject绑定在一起了。

registerChangeNotifier(Uri, ChangeNotifier)

向ContentResolver注册ContentObserver,而ChangeNotifier并不是ContentObserver,算是一个被适配的对象(adaptee)。

 

DataLoader(以AlbumDataLoader为例)

|

|---------------------------------------------------------------|A

|Dataloader的content部分                                     |

|在AlbumLoader中为mData.length=1000     |

|------------------------------------------|                         |B        

| slidingwindow的 content和         |                        |

|-------------------------|                    |                         |C

|可见部分 (active) |                     |                        |

|-------------------------|                     |                        |D

|dataloader的active部分               |                        |

|------------------------------------------|                        |E

|                                                                             |

|                                                                             |

|--------------------------------------------------------------|F

|

 

mActiveStart : int

DataLoader的活跃段的起始index(在对应MediaSet中的index),由SlidingWindow#setContentWindow(int, int)调用DataLoader的setActiveWindow(int, int)时设置

mActiveEnd : int

和mActiveStart相对

mContentStart : int

DataLoader的数据段的起始index,数据段包含了活跃段,活跃段在数据段的中心段,DataLoader的活跃段SlidingWindow的数据段是一致的,而SlidingWindow的活跃段就是可见的数据。SlidingWindow的

的数据段和DataLoader的数据段都是以可见部分为中心,随着可见部分的不断移动而变化。

mContentEnd

和mContentStart相对

mData : MediaItem[ ]

所有MediaItem都会在以Path#sRoot : Path为根的树中。可通过路径快速查找到。但是只有在DataLoader中的MediaItem会被及时的更新对应图片数据的元数据,如大小,宽高。这涉及到了数据的版本管理。

每个MediaSet都会监听其对应的Uri,但是不会去更新MediaItem,只是会通知对其感兴趣的DataLoader,然后DataLoader会对ReloadTask的mDirty置为true。

mItemVersion : int[ ]

对应mData中的MediaItem的version。MediaItem有一个Item属性。mItemVersion主要用于和MediaItem中的version属性对比,如果mItemVersion中的值和MediaItem的不等,则说明这个MediaItem刚更新过,需要通知

SlidingWindow,然后SlidingWindow根据是否其content范围的数据决定是否更新其mData中的图片数据(注意和图片数据的元数据的区分)。

mSetVersion : int[ ]

对应mData中的MediaItem最近被更新时对应的mSouce(LocalAlbum)的version,每次更新mData前,都会调用MediaSet#reload()获取新的版本号。只有其ChangeNotifier#mDirty为true时才会返回新版本号

 

————————————————————————————————————————————————————————————————————————

mReloadTask : DataLoader$ReloadTask

ReloadTask extends Thread

更新mData中MediaItem的核心内部类,mActive : boolean

调用resume后就为true,调用pause后就为false。控制ReloadTask#run()的主循环。

mDirty : boolean

控制着ReloadTask#run()的主循环是否进入等待队列,为false则进入等待。

notifyDirty()

将mDirty设为true,然后中断主循环。

terminate()

将mActive置为false,然后中断主循环。

run() : 依赖DataLoader$UpdateContent、DataLoader$UpdateInfo、LocalAlbum(DataLoader#mSource : MediaSet 对应的实例)去更新mData中MediaItem的信息或更换mData中的MediaItem,并更新对应mItemVersion和mSetVersion中的version,获取更新MediaItem是调用LocalAlbum#getMediaItem(start, count) : List,其中就去查询了数据库,后面说MediaObject体系时再详细说。

—————————————————————————————————————————————————————————————————————————

 

 

——————————————————————————————————————————————————————————————————————————

DataLoader$UpdateInfo

version : long

这是DataLoad#mSource : LocalAlbum的version

reloadStart: int

从哪个index开始更新

reloadCount : int

需要更新多少个MediaItem

size : int

表示mSource:MediaSet(LocalAlbum)的getCount()

items :ListArray<MediaItem>

指向已经更新的MediaItem,是ReloadTask#run()中调用(LocalAlbum)mSource#getMediaItem(reloadStart, reloadCount)获取的

++++++++++++++++++++++++++++++++

DataLoader$GetUpdateInfo

mVersion : int

call() : UpdateInfo

主要就是确定上面UpdateInfo的几个成员参数。i的选定范围是mContentStart至mContentEnd,从mContentStart开始扫描,条件是对应mSetVersion[ i ] ≠ version;count = min(MAX_LOAD_COUNT, mContentEnd - i)

———————————————————————————————————————————————————————————————————————————

 

————————————————————————————————————————————————————————————————————————————

DataLoader$UpdateContent

mUpdateInfo UpdateInfo

就是已经初始化完成UpdateInfo

call() :Void

根据已有的mUpdateInfo去更新reloadStart~reloadStart+reloadCount  的 mData[]、mItemVersion、mSetVersion[].如果是在DataLoader的Active范围,即SlidingWindow的Content范围

则通过DataLoader$DataListener#onContentChanged(int)通知SlidingWindow,当然只有itemversion改变时(即对应index指向的图片数据发生了变化),才去通知SlidingWindow,因为SlidingWindow无法根据version判断数据是否发生改变,如果SlidingWindow去重新加载没有更新图片则是浪费资源。

 

——————————————————————————————————————————————————————————————————————————————

 

SlidingWindow(以AlbumSlidingWindow为例)

mActiveStart : int

屏幕可见的起始item的index

mActivEnd : int

屏幕可见的最后item的index

mContentStart : int

缓存texture的起始index

mContentEnd:int

缓存texture的最后的index

mData :AlbumEntry[ ]

是SlidingWindow的数据集合,mData.length == mContentEnd - mContentStart

mSource : AlbumDataLoader

mThreadPool : JobLimiter

其中JobLimiter是封装了ThreadPool,是执行图片请求的

mVideoMicroThumbDecoder : JobLimiter

这是执行video请求的

mTileUploader : TiledTexture.Uploader

用于将AlbumEntry中TiledTexture上传到GLOpen可以使用的地方

——————————————————————————————————————————————————————————————

AlbumSlidingWindow$AlbumEntry

item : MediaItem

isWaitDisplayed : boolean

mBitmapTexture : TiledTexture

content : Texture

contentLoader : BitmapLoader

_____________________________________________________________________________________________________________________

 

——————————————————————————————————————————————————————————————

BitmapLoader implements FutureTask<Bitmap>

mState : int

描述任务的状态

mTask : Future<Bitmap>

mBitmap : Bitmap

startLoad()

将自己提交到TheadPool中

recycle()

将mBitmap放入缓存GalleryBitmapPool中,其中GalleryBitmapPool是一个单例

 

+++++++++++++++++++++++++++++++++++++++++++++

AlbumSlidingWindow$ThumbnailLoader extends BitmapLoader

mItem : MediaItem

mSlotIndex

updateEntry()

利用mBitmap去实例化Texture并更新mData[mSlotIndex]

onLoadComplete(Bitmap)

在子线程中调用该方法,然后使用Handler,然后在主线程中调用updateEntry()

 

 

++++++++++++++++++++++++++++++++++++++++++++++

AlbumSlidingWindow$Listener

这是一个interface,用于让Render实现并监听AlbumSlidingWindow。

onSizeChanged(int)

AlbumSlidingWindow在size发生改变时通知Renderer

onContentChanged()

AlbumSlidingWindow在其onContentChanged(int)就会调用该方法通知Renderer去invalidate

——————————————————————————————————————————————————————————————

 

setActiveWindow(int, int)

在图片的滑动过程中,active index必然会改变。需要调用这方法去带动一连串的更新逻辑。带动content index的改变还有带动DataLoader的active index 和 content index的改变。

将需要显示的新的图片的texture通过mTileUploader上传,更新active范围内需要更新的AlbumEntry,更新AlbumEntry中的成员就需要使用updateAllImageRequests()。如果没有active范围内需要更新的,就去更新content范围的,

 

setContentWindow(int, int)

在setActiveWindow(int, int)中被调用,clear mData 中滑出content范围的,调用DataLoader#setActiveWindow(int, int)启动DataLoader的mData更新数据,然后为滑进content范围的MediaItem创建并初始化AlbumEntry。

 

updateAllImageRequests()

更新AlbumEntry中的需要更新的Entry。如果没有active范围内需要更新的,就去更新content范围的。因为这样就可以在快速滑动时,从content中加入active的item不用去加载图片,马上就可以显示出来。

freeSlotContent(int)

清空mData中滑出content范围的MediaItem,并调用的AlbumEntry#contentLoader#recycle()将AlbumEntry对应的Bitmap放入GalleryBitmapPool。GalleryBitmapPool是一个以图片宽和高作为数据唯一性的依据,不过普通图片(相机拍出来的)

是不会放进缓存区的。其中int参数就是滑出content范围的index。然后调用TiledTexture#recycle(),这将TiledTexture的Tile[]放入一个sFreeTileHeader : Tile的链表中。在创建TiledTexture需要Tile[]时就去这个链表拿,并从链表中移除该Tile,

如果链表中没有则实例化一个Tile。存入和获取分别对应freeTile(Tile)和 obtainTile():Tile。然后一个Bitmap对应的TiledTexture的Tile[]保存下来了,但是Tile#bitmap被置空,即并没有保存图片数据。Tile没有Bitmap是不能绘制到屏幕上的。

 

prepareSlotContent(int)

和freeSlotContent(int)对应

 

 

MediaItem(以LocaAlbum为例)

sThumbnailTargetSize : int

一般缩略图的目标大小,即最终会压缩到的大小

sMicrothumbnailTargetSize : int

最小缩略图的目标大小

get 各种 detail的方法,这里就不一一列举了

requestImage(int) : Job<Bitmap>

返回一个类似runnable的接口类型的对象,调用该对象的run()方法可以返回一个已经处理好的Bitmap(包括按指定规格压缩,裁剪等),返回的是缩略图。这个requestImage(int)方法和Job都由各个子类去实现。

requestLargeImage() : Job<BitmapRegionDecoder>

和requestImage(int)不同的是,这个Job的run()方法会返回一个BitmapRegionDecoder,而不是一个直接的Bitmap,因为这是用于加载原图的方法。由于一般原图比较大,需要将一个图片分成几个Region来加载。

 

MediaSet(以LocalAlbum为例)

每个Album都有一个ChangeNotifier,但是MediaSet这个类并没有。这个ChangeNotifier是用于监听该Album对应的Uri的。这个也可以看DataManager

isLeafAlbum() : boolean

是否Album,否的话就是AlbumSet了

getIndexOf(Path path, ArrayList<MediaItem> list) : int

获取对应item的index

getMediaItemCount() : int

获取给MediaSet的item数

getMediaItem(int start, int count) : ArrayList<MediaItem>

从start开始获取,获取count个item

getSubMediaSet(int index) : MediaSet

获取子MediaSet的数目

getSubMediaSet(int index) : MediaSet

获取index对应的MediaSet

addContentListener(ContentListener listener)

设置监听器,上面说到了,每个Album都有一个ChangeNotifier,当有变化时这个Notifier回去调用这些被add进来的ContentListener的onContentDirty()方法,都是DataLoader监听MediaSet的,也就是说是DataLoader add 进来的。

reload() : long

返回一个版本好。根据是否dirty(即监听到了Uri指向的数据改变了),重载一些更新查询MediaItem需要用到的参数,如果where字句,bucketId等。不同MediaSet子类有不同的需要。

reloadWhereClause() : 更新查询数据库时需要用到的where子句

而作为MediaSet重要子类,LocalAlbum还有以下一些重要成员:

mItemPath : Path

就是该LocalAlbum的Path,分为Video和Image

mBaseUri : Uri

也是分别有Video和Image的

mBucketId : int

BucketId是MediaStore中的一个column字段,大概每个包含图片的文件夹都会对应一个BucketId,即你去查询“bucket_id”=mBucketId时,会返回mBucketId对应文件的所有图片在多媒体数据库中的信息

mProjection : String[ ]

感兴趣的列

mOrderClause : String

按时间递减,ImageColumns.DATE_TAKEN + " DESC, "

 

posted on 2017-10-13 20:30  淡淡的宁静  阅读(690)  评论(0)    收藏  举报

导航