上次找到了缩略图的绘制的地方,但没有找到缩略图数据是如何来的,也不知道其他界面是怎么刷新的,更不知道界面是如何切换的。
让我们开始,先在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.youkuaiyun.com/philofly/article/details/8162259