上次找到了缩略图的绘制的地方,但没有找到缩略图数据是如何来的,也不知道其他界面是怎么刷新的,更不知道界面是如何切换的。
让我们开始,先在Gallery.java 里面的OnCreate中找到一个函数
sendInitialMessage();
顺着这个函数找下去
private void sendInitialMessage() { mNumRetries = 0; Message checkStorage = new Message(); checkStorage.what = CHECK_STORAGE; handler.sendMessage(checkStorage); }
消息发出去了,要求检查存储器,看下怎么处理的
private final Handler handler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { case CHECK_STORAGE: checkStorage(); break; case HANDLE_INTENT: initializeDataSource(); break; } } };
应该走到,checkStorage里面了
private void checkStorage() { mNumRetries++; mImageManagerHasStorageAfterDelay = ImageManager.hasStorage(); if (!mImageManagerHasStorageAfterDelay && mNumRetries < NUM_STORAGE_CHECKS) { if (mNumRetries == 1) { mApp.showToast(getResources().getString(R.string.no_sd_card), Toast.LENGTH_LONG); } handler.sendEmptyMessageDelayed(CHECK_STORAGE, 200); } else { handler.sendEmptyMessage(HANDLE_INTENT); } }
因为
private static final int NUM_STORAGE_CHECKS = 25;
所以这些意思就是如果没有T卡第一次就会报错,并检查25次,失败就结束,不报错,如果有就发送HANDLE_INTENT消息出来,这个消息看下是InitialDataSource()这个函数处理,看下这个函数,这个函数很长
private void initializeDataSource() { final boolean hasStorage = mImageManagerHasStorageAfterDelay; // Creating the DataSource objects. final PicasaDataSource picasaDataSource = new PicasaDataSource( Gallery.this); final LocalDataSource localDataSource = new LocalDataSource( Gallery.this, LocalDataSource.URI_ALL_MEDIA, false); final ConcatenatedDataSource combinedDataSource = new ConcatenatedDataSource( localDataSource, picasaDataSource); // Depending upon the intent, we assign the right dataSource. if (!isPickIntent() && !isViewIntent() && !isReviewIntent()) { localDataSource.setMimeFilter(true, true); if (hasStorage) { mGridLayer.setDataSource(combinedDataSource); } else { mGridLayer.setDataSource(picasaDataSource); } } else if (isPickIntent()) { final Intent intent = getIntent(); if (intent != null) { String type = intent.resolveType(Gallery.this); if (type == null) { // By default, we include images type = "image/*"; } boolean includeImages = isImageType(type); boolean includeVideos = isVideoType(type); localDataSource.setMimeFilter(includeImages, includeVideos); if (includeImages) { if (hasStorage) { mGridLayer.setDataSource(combinedDataSource); } else { mGridLayer.setDataSource(picasaDataSource); } } else { mGridLayer.setDataSource(localDataSource); } mGridLayer.setPickIntent(true); if (hasStorage) { mApp.showToast(getResources().getString( R.string.pick_prompt), Toast.LENGTH_LONG); } } } else { // view intent for images and review intent for images and videos final Intent intent = getIntent(); Uri uri = intent.getData(); boolean slideshow = intent.getBooleanExtra("slideshow", false); final LocalDataSource singleDataSource = new LocalDataSource( Gallery.this, uri.toString(), true); // Display both image and video. singleDataSource.setMimeFilter(true, true); if (hasStorage) { ConcatenatedDataSource singleCombinedDataSource = new ConcatenatedDataSource( singleDataSource, picasaDataSource); mGridLayer.setDataSource(singleCombinedDataSource); } else { mGridLayer.setDataSource(picasaDataSource); } mGridLayer.setViewIntent(true, Utils.getBucketNameFromUri( getContentResolver(), uri)); if (isReviewIntent()) { mGridLayer.setEnterSelectionMode(true); } if (singleDataSource.isSingleImage()) { mGridLayer.setSingleImage(false); } else if (slideshow) { mGridLayer.setSingleImage(true); mGridLayer.startSlideshow(); } } // We record the set of enabled accounts for picasa. mPicasaHandler.sendEmptyMessage(GET_PICASA_ACCOUNT_STATUS); } }
又遇到数据源了
PicasaDataSource 据说google 收购的一家公司做的毕加索数据源
LocalDataSource 本地T 卡数据
combinedDataSource 反正是组合的数据源 可以是 PicasaDataSource + LocalDataSource
singleDataSource 单张图片浏览数据源
里面的mGridLayer根据不同的情况,设置不同的数据源,都是通过一个接口SetDataSource.
我们看这个函数的内容
public void setDataSource(DataSource dataSource) { MediaFeed feed = mMediaFeed; mMediaFeed = new MediaFeed(mContext, dataSource, this); if (feed != null) { // Restore the slot state in the original feed before shutting it down. mMediaFeed.copySlotStateFrom(feed); feed.shutdown(); mDisplayList.clear(); mBackground.clear(); } mMediaFeed.start(); }
先做了些处理,防止先前已经设置过数据源出现问题,
最后都会调用mMediaFeed.start();
public final class MediaFeed implements Runnable {}
这个类继承于Runnable,并没有继承于Thread ,说明start是一个用户自己写的接口
看下都干了什么事情?
public void start() { final MediaFeed feed = this; onResume(); mLoading = true; mDataSourceThread = new Thread(this); mDataSourceThread.setName("MediaFeed"); mIsShutdown = false; mAlbumSourceThread = new Thread(new Runnable() { public void run() { if (mContext == null) return; Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); DataSource dataSource = mDataSource; // We must wait while the SD card is mounted or the MediaScanner // is running. if (dataSource != null) { loadMediaSets(); } mWaitingForMediaScanner = false; while (ImageManager.isMediaScannerScanning(mContext.getContentResolver())) { // MediaScanner is still running, wait if (Thread.interrupted()) return; mWaitingForMediaScanner = true; try { if (mContext == null) return; showToast(mContext.getResources().getString(Res.string.initializing), Toast.LENGTH_LONG); if (dataSource != null) { loadMediaSets(); } Thread.sleep(10000); } catch (InterruptedException e) { return; } } if (mWaitingForMediaScanner) { showToast(mContext.getResources().getString(Res.string.loading_new), Toast.LENGTH_LONG); mWaitingForMediaScanner = false; loadMediaSets(); } mLoading = false; } }); mAlbumSourceThread.setName("MediaSets"); mAlbumSourceThread.start(); }
首先调用了onResume方法,设置观察者看是否数据源有变化
final HashMap<String, ContentObserver> observers = mContentObservers;
然后启动线程等待数据源是否扫描好。
设置了两个线程一个是MediaSets,一个是MediaFeed,只启动了MediaSets这个线程,负责等待数据源是否扫描好。
另外一个线程做什么了,现在什么都没有做,只有当别人调用了一个接口才会启动
public MediaSet addMediaSet(final long setId, DataSource dataSource) { MediaSet mediaSet = new MediaSet(dataSource); mediaSet.mId = setId; mMediaSets.add(mediaSet); if (mDataSourceThread != null && !mDataSourceThread.isAlive()) { mDataSourceThread.start(); } mMediaFeedNeedsToRun = true; return mediaSet; }
我们后面再看谁在调用这个接口。
在很多数据源的刷新时都会调用这个接口,就会启动这个线程,我们可以看LocalDataSource的刷新的函数
public void refresh(final MediaFeed feed, final String[] databaseUris) { // We check to see what has changed. long[] ids = CacheService.computeDirtySets(mContext); int numDirtySets = ids.length; for (int i = 0; i < numDirtySets; ++i) { long setId = ids[i]; if (feed.getMediaSet(setId) != null) { MediaSet newSet = feed.replaceMediaSet(setId, this); newSet.generateTitle(true); } else { MediaSet mediaSet = feed.addMediaSet(setId, this); if (setId == CAMERA_BUCKET_ID) { mediaSet.mName = CAMERA_STRING; } else if (setId == DOWNLOAD_BUCKET_ID) { mediaSet.mName = DOWNLOAD_STRING; } mediaSet.generateTitle(true); } } }
先看已经运行的线程中的run 里面做了什么
private void loadMediaSets() { if (mDataSource == null) return; final ArrayList<MediaSet> sets = mMediaSets; synchronized (sets) { final int numSets = sets.size(); for (int i = 0; i < numSets; ++i) { final MediaSet set = sets.get(i); set.mFlagForDelete = true; } mDataSource.refresh(MediaFeed.this, mDataSource.getDatabaseUris()); mDataSource.loadMediaSets(MediaFeed.this); final ArrayList<MediaSet> setsToRemove = new ArrayList<MediaSet>(); for (int i = 0; i < numSets; ++i) { final MediaSet set = sets.get(i); if (set.mFlagForDelete) { setsToRemove.add(set); } } int numSetsToRemove = setsToRemove.size(); for (int i = 0; i < numSetsToRemove; ++i) { sets.remove(setsToRemove.get(i)); } setsToRemove.clear(); } mMediaFeedNeedsToRun = true; updateListener(false); }
里面会运行mDataSource.loadMediaSets()这个函数,但mDataSource实例对应的是一个接口,我们找它初始化的位置
public MediaFeed(Context context, DataSource dataSource, Listener listener) { mContext = context; mListener = listener; mDataSource = dataSource; mSingleWrapper.setNumExpectedItems(1); mLoading = true; }
MediaFeed实例化的时候第二个参数是什么类呢?就是我们传进去的数据源类型就会调用到什么类型的refresh和 LoadMediaSets函数
我们就以LocalDataSource为例来研究吧,而它的refresh函数刚才我们就看了,会触发启动线程MediaFeed,这个线程会做什么呢?
很多很多事情,先看下LoadMediaSets:
public void loadMediaSets(final MediaFeed feed) { MediaSet set = null; // Dummy set. boolean loadOtherSets = true; if (mSingleUri) { String name = Utils.getBucketNameFromUri(mContext.getContentResolver(), Uri.parse(mUri)); long id = Utils.getBucketIdFromUri(mContext.getContentResolver(), Uri.parse(mUri)); set = feed.addMediaSet(id, this); set.mName = name; set.mId = id; set.setNumExpectedItems(2); set.generateTitle(true); set.mPicasaAlbumId = Shared.INVALID; if (this.getThumbnailCache() != sThumbnailCache) { loadOtherSets = false; } } else if (mBucketId == null) { // All the buckets. if (mFlattenAllItems) { set = feed.addMediaSet(0, this); // Create dummy set. set.mName = Utils.getBucketNameFromUri(mContext.getContentResolver(), Uri.parse(mUri)); set.mId = getBucketId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI.toString() + "/" + set.mName); set.setNumExpectedItems(1); set.generateTitle(true); set.mPicasaAlbumId = Shared.INVALID; } else { CacheService.loadMediaSets(mContext, feed, this, mIncludeImages, mIncludeVideos, true); } } else { CacheService.loadMediaSet(mContext, feed, this, Long.parseLong(mBucketId)); ArrayList<MediaSet> sets = feed.getMediaSets(); if (sets.size() > 0) set = sets.get(0); } // We also load the other MediaSets if (!mAllItems && set != null && loadOtherSets) { final long setId = set.mId; if (!CacheService.isPresentInCache(setId)) { CacheService.markDirty(); } CacheService.loadMediaSets(mContext, feed, this, mIncludeImages, mIncludeVideos, false); // not re-ordering media sets in the case of displaying a single image if (!mSingleUri) { feed.moveSetToFront(set); } } }
那么我们现在知道了数据全部来源于数据源。
最后我们看刚才启动的线程做了什么,因为是用this做的构造函数的参数,而且这个类也是继承Runnable,所以线程起来后一定会跑run成员函数
public void run() { DataSource dataSource = mDataSource; int sleepMs = 10; Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); if (dataSource != null) { while (!Thread.interrupted() && !mIsShutdown) { String[] databaseUris = null; boolean performRefresh = false; synchronized (mRequestedRefresh) { if (mRequestedRefresh.size() > 0) { // We prune this first. int numRequests = mRequestedRefresh.size(); for (int i = 0; i < numRequests; ++i) { databaseUris = ArrayUtils.addAll(databaseUris, mRequestedRefresh.get(i)); } mRequestedRefresh.clear(); performRefresh = true; // We need to eliminate duplicate uris in this array final HashMap<String, String> uris = new HashMap<String, String>(); if (databaseUris != null) { int numUris = databaseUris.length; for (int i = 0; i < numUris; ++i) { final String uri = databaseUris[i]; if (uri != null) uris.put(uri, uri); } } databaseUris = new String[0]; databaseUris = (String[]) uris.keySet().toArray(databaseUris); } } boolean settingFeedAboutToChange = false; if (performRefresh) { if (dataSource != null) { if (mListener != null) { settingFeedAboutToChange = true; mListener.onFeedAboutToChange(this); } dataSource.refresh(this, databaseUris); mMediaFeedNeedsToRun = true; } } if (mListenerNeedsUpdate && !mMediaFeedNeedsToRun) { mListenerNeedsUpdate = false; if (mListener != null) synchronized (mMediaSets) { mListener.onFeedChanged(this, mListenerNeedsLayout); } try { Thread.sleep(sleepMs); } catch (InterruptedException e) { return; } } else { try { Thread.sleep(sleepMs); } catch (InterruptedException e) { return; } } sleepMs = 300; if (!mMediaFeedNeedsToRun) continue; App app = App.get(mContext); if (app == null || app.isPaused()) continue; if (settingFeedAboutToChange) { updateListener(true); } mMediaFeedNeedsToRun = false; ArrayList<MediaSet> mediaSets = mMediaSets; synchronized (mediaSets) { int expandedSetIndex = mExpandedMediaSetIndex; if (expandedSetIndex >= mMediaSets.size()) { expandedSetIndex = Shared.INVALID; } if (expandedSetIndex == Shared.INVALID) { // We purge the sets outside this visibleRange. int numSets = mediaSets.size(); IndexRange visibleRange = mVisibleRange; IndexRange bufferedRange = mBufferedRange; boolean scanMediaSets = true; for (int i = 0; i < numSets; ++i) { if (i >= visibleRange.begin && i <= visibleRange.end && scanMediaSets) { MediaSet set = mediaSets.get(i); int numItemsLoaded = set.mNumItemsLoaded; if (numItemsLoaded < set.getNumExpectedItems() && numItemsLoaded < 8) { synchronized (set) { dataSource.loadItemsForSet(this, set, numItemsLoaded, 8); set.checkForDeletedItems(); } if (set.getNumExpectedItems() == 0) { mediaSets.remove(set); break; } if (mListener != null) { mListenerNeedsUpdate = false; mListener.onFeedChanged(this, mListenerNeedsLayout); mListenerNeedsLayout = false; } sleepMs = 100; scanMediaSets = false; } if (!set.setContainsValidItems()) { mediaSets.remove(set); if (mListener != null) { mListenerNeedsUpdate = false; mListener.onFeedChanged(this, mListenerNeedsLayout); mListenerNeedsLayout = false; } break; } } } numSets = mediaSets.size(); for (int i = 0; i < numSets; ++i) { MediaSet set = mediaSets.get(i); if (i >= bufferedRange.begin && i <= bufferedRange.end) { if (scanMediaSets) { int numItemsLoaded = set.mNumItemsLoaded; if (numItemsLoaded < set.getNumExpectedItems() && numItemsLoaded < 8) { synchronized (set) { dataSource.loadItemsForSet(this, set, numItemsLoaded, 8); set.checkForDeletedItems(); } if (set.getNumExpectedItems() == 0) { mediaSets.remove(set); break; } if (mListener != null) { mListenerNeedsUpdate = false; mListener.onFeedChanged(this, mListenerNeedsLayout); mListenerNeedsLayout = false; } sleepMs = 100; scanMediaSets = false; } } } else if (!mListenerNeedsUpdate && (i < bufferedRange.begin || i > bufferedRange.end)) { // Purge this set to its initial status. MediaClustering clustering = mClusterSets.get(set); if (clustering != null) { clustering.clear(); mClusterSets.remove(set); } if (set.getNumItems() != 0) set.clear(); } } } if (expandedSetIndex != Shared.INVALID) { int numSets = mMediaSets.size(); for (int i = 0; i < numSets; ++i) { // Purge other sets. if (i != expandedSetIndex) { MediaSet set = mediaSets.get(i); MediaClustering clustering = mClusterSets.get(set); if (clustering != null) { clustering.clear(); mClusterSets.remove(set); } if (set.mNumItemsLoaded != 0) set.clear(); } } // Make sure all the items are loaded for the album. int numItemsLoaded = mediaSets.get(expandedSetIndex).mNumItemsLoaded; int requestedItems = mVisibleRange.end; // requestedItems count changes in clustering mode. if (mInClusteringMode && mClusterSets != null) { requestedItems = 0; MediaClustering clustering = mClusterSets.get(mediaSets.get(expandedSetIndex)); if (clustering != null) { ArrayList<Cluster> clusters = clustering.getClustersForDisplay(); int numClusters = clusters.size(); for (int i = 0; i < numClusters; i++) { requestedItems += clusters.get(i).getNumExpectedItems(); } } } MediaSet set = mediaSets.get(expandedSetIndex); if (numItemsLoaded < set.getNumExpectedItems()) { // We perform calculations for a window that gets // anchored to a multiple of NUM_ITEMS_LOOKAHEAD. // The start of the window is 0, x, 2x, 3x ... etc // where x = NUM_ITEMS_LOOKAHEAD. synchronized (set) { dataSource.loadItemsForSet(this, set, numItemsLoaded, (requestedItems / NUM_ITEMS_LOOKAHEAD) * NUM_ITEMS_LOOKAHEAD + NUM_ITEMS_LOOKAHEAD); set.checkForDeletedItems(); } if (set.getNumExpectedItems() == 0) { mediaSets.remove(set); mListenerNeedsUpdate = false; mListener.onFeedChanged(this, mListenerNeedsLayout); mListenerNeedsLayout = false; } if (numItemsLoaded != set.mNumItemsLoaded && mListener != null) { mListenerNeedsUpdate = false; mListener.onFeedChanged(this, mListenerNeedsLayout); mListenerNeedsLayout = false; } } } MediaFilter filter = mMediaFilter; if (filter != null && mMediaFilteredSet == null) { if (expandedSetIndex != Shared.INVALID) { MediaSet set = mediaSets.get(expandedSetIndex); ArrayList<MediaItem> items = set.getItems(); int numItems = set.getNumItems(); MediaSet filteredSet = new MediaSet(); filteredSet.setNumExpectedItems(numItems); mMediaFilteredSet = filteredSet; for (int i = 0; i < numItems; ++i) { MediaItem item = items.get(i); if (filter.pass(item)) { filteredSet.addItem(item); } } filteredSet.updateNumExpectedItems(); filteredSet.generateTitle(true); } updateListener(true); } } } } }
那么更多的问题来了,数据从数据源来的,那数据源的数据又是从哪里来的?
其他界面怎么切换的?其他的界面时如何刷新的?
参考文档
http://blog.csdn.net/philofly/article/details/8162259