1. 程式人生 > >Android 7.0 Gallery圖庫源碼分析3 - 數據加載及顯示流程

Android 7.0 Gallery圖庫源碼分析3 - 數據加載及顯示流程

不為 isempty stat submit mode 準備工作 RKE xtu ida

前面分析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 - 數據加載及顯示流程