Android 7.0 Gallery圖庫源碼分析3 - 數據加載及顯示流程
前面分析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 mSource是new 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自身
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 - 數據加載及顯示流程