圖1 頁面AlbumPage在Activity resume時,數據模型的時序圖數據庫
圖2 在AlbumPage滑動時數據模型的時序圖緩存
圖3 AlbumPage啓動時建立MediaSet實例的時序圖ide
圖4 數據模型類圖1spa
圖5 數據模型類圖2線程
Path設計
Path是MediaObject體系能成爲組合模式的關鍵。組合模式中,MediaObject體系依賴Path才能夠向上遍歷和向下遍歷,path的存在還容許用戶根據以String表明的路徑或者segment來尋找來找到對應的MediaObject。3d
mChildren : 以segment:String做爲key,Path做爲value。其實IdentityCache是一個HashMap。mChildren中的Path是該Path的子Path。code
mParent:是該Path的父Path。這決定了Path組成的樹是能夠向上遍歷的。server
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, "