前面7节我们讲了很多东西,我们再来回顾一下:
在程序还没有开始的时候,系统的一个程序com.android.providers.media就把内置存储器和外置存储器默默的扫描了一遍,建立了数据库和缓存缩略图,
我们还弄清楚了Gallery3D是通过RenderView通过OpenGL 刷的界面,而且界面分为很多层,而且将层分了5个层列表:刷新列表 不透明列表 半透明列表 触摸列表 系统列表。
按键都是转到Root层处理的,而触摸则是透过RenderView的持续刷新的函数里面分发给各个层的,由各个层自己处理,根层的触摸和按键最后都转到了叫做GridInputProcess的类里面处理了。
然后我们发现触摸长按没有层处理,而根层触摸处理都转到GridInputProcess处理,发现这里有OnLongPress的处理,这里的处理是透过Hud层来设置的,完成设置模式的切换。
触摸和按键的消息基本弄明白了,后面又对界面的刷新简单看了一下,发现在GridLayer不透明层的刷新中,发现了对缩略图界面的绘制
然后在主Activity里面发现了对SD卡的检测,最后是初始化数据源,设置对应的数据源,发现所有的数据都是来自数据源,并且其中先后开启了两个线程MediaSets 和MediaFeed,MediaSets只是等待数据源初始化结束。而MediaFeed这个线程我们还没有仔细的分析,我们现在看下。
在run里面的代码
while (!Thread.interrupted() && !mIsShutdown) {
基本上是个死循环,死循环在做什么呢
在检测是否又新的图片或者视频出现,如果有就会更新,怎么更新呢
// Start the thumbnailer.
CacheService.startCache(this, true);
至于他们为什么在onStop函数里面写,我理解是在Activity停止后,用户可能去拍摄新图片,为了对新的图片有Cache,在后台开启服务进行Cache
我们来看下CacheService这个类
public final class CacheService extends IntentService {}
说明什么?说明CacheService是一个IntentService,IntentService是什么,就是一个开启了一个线程的Service,(不清楚的自己看下源码)每次处理线程中的消息时都会调用一个函数叫做:OnHandleIntent(),我们看下里面的内容:
@Override
protected void onHandleIntent(final Intent intent) {
if (DEBUG)
Log.i(TAG, "Starting CacheService");
if (Environment.getExternalStorageState() == Environment.MEDIA_BAD_REMOVAL) {
sAlbumCache.deleteAll();
putLocaleForAlbumCache(Locale.getDefault());
}
Locale locale = getLocaleForAlbumCache();
if (locale != null && locale.equals(Locale.getDefault())) {
} else {
// The locale has changed, we need to regenerate the strings.
markDirty();
}
if (intent.getBooleanExtra("checkthumbnails", false)) {
startNewThumbnailThread(this);
} else {
final Thread existingThread = THUMBNAIL_THREAD.getAndSet(null);
if (existingThread != null) {
existingThread.interrupt();
}
}
}
.
里面有个Intent的参数获取,checkthumbnails,获取后开启的线程,先找下谁发出了带有这个参数的Intent,
public static final void startCache(final Context context, final boolean checkthumbnails) {
final Locale locale = getLocaleForAlbumCache();
final Locale defaultLocale = Locale.getDefault();
if (locale == null || !locale.equals(defaultLocale)) {
sAlbumCache.deleteAll();
putLocaleForAlbumCache(defaultLocale);
}
final Intent intent = new Intent(ACTION_CACHE, null, context, CacheService.class);
intent.putExtra("checkthumbnails", checkthumbnails);
context.startService(intent);
}
原来就是刚才我们看的的startCache()这个函数,我们再来看startNewThumbnailThread
public static final void startNewThumbnailThread(final Context context) {
restartThread(THUMBNAIL_THREAD, "ThumbnailRefresh", new Runnable() {
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
try {
// It is an optimization to prevent the thumbnailer from
// running while the application loads
Thread.sleep(THUMBNAILER_WAIT_IN_MS);
} catch (InterruptedException e) {
return;
}
CacheService.buildThumbnails(context);
}
});
}
这里面又开启了一个线程,主要工作就是buildThumbnails,建立缩略图的Cache
private static final void buildThumbnails(final Context context) {
if (DEBUG)
Log.i(TAG, "Preparing DiskCache for all thumbnails.");
ImageList list = getImageList(context);
final int size = (list.ids == null) ? 0 : list.ids.length;
final long[] ids = list.ids;
final long[] timestamp = list.timestamp;
final long[] thumbnailIds = list.thumbids;
final DiskCache thumbnailCache = LocalDataSource.sThumbnailCache;
for (int i = 0; i < size; ++i) {
if (Thread.interrupted()) {
return;
}
final long id = ids[i];
final long timeModifiedInSec = timestamp[i];
final long thumbnailId = thumbnailIds[i];
if (!isInThumbnailerSkipList(thumbnailId)) {
if (!thumbnailCache.isDataAvailable(thumbnailId, timeModifiedInSec * 1000)) {
byte[] retVal = buildThumbnailForId(context, thumbnailCache, thumbnailId, id, false, DEFAULT_THUMBNAIL_WIDTH,
DEFAULT_THUMBNAIL_HEIGHT, timeModifiedInSec * 1000);
if (retVal == null || retVal.length == 0) {
// There was an error in building the thumbnail.
// We record this thumbnail id
addToThumbnailerSkipList(thumbnailId);
}
}
}
}
thumbnailCache.flush();
if (DEBUG)
Log.i(TAG, "DiskCache ready for all thumbnails.");
}
主要的工作就是buildThumbnailForId做的,里面又调用了一个函数writeBitmapToCache,我们看下内容
public static final byte[] writeBitmapToCache(final DiskCache thumbnailCache, final long thumbId, final long origId,
final Bitmap bitmap, final int thumbnailWidth, final int thumbnailHeight, final long timestamp) {
final int width = bitmap.getWidth();
final int height = bitmap.getHeight();
// Detect faces to find the focal point, otherwise fall back to the
// image center.
int focusX = width / 2;
int focusY = height / 2;
// We have commented out face detection since it slows down the
// generation of the thumbnail and screennail.
// final FaceDetector faceDetector = new FaceDetector(width, height, 1);
// final FaceDetector.Face[] faces = new FaceDetector.Face[1];
// final int numFaces = faceDetector.findFaces(bitmap, faces);
// if (numFaces > 0 && faces[0].confidence() >=
// FaceDetector.Face.CONFIDENCE_THRESHOLD) {
// final PointF midPoint = new PointF();
// faces[0].getMidPoint(midPoint);
// focusX = (int) midPoint.x;
// focusY = (int) midPoint.y;
// }
// Crop to thumbnail aspect ratio biased towards the focus point.
int cropX;
int cropY;
int cropWidth;
int cropHeight;
float scaleFactor;
if (thumbnailWidth * height < thumbnailHeight * width) {
// Vertically constrained.
cropWidth = thumbnailWidth * height / thumbnailHeight;
cropX = Math.max(0, Math.min(focusX - cropWidth / 2, width - cropWidth));
cropY = 0;
cropHeight = height;
scaleFactor = (float) thumbnailHeight / height;
} else {
// Horizontally constrained.
cropHeight = thumbnailHeight * width / thumbnailWidth;
cropY = Math.max(0, Math.min(focusY - cropHeight / 2, height - cropHeight));
cropX = 0;
cropWidth = width;
scaleFactor = (float) thumbnailWidth / width;
}
final Bitmap finalBitmap = Bitmap.createBitmap(thumbnailWidth, thumbnailHeight, Bitmap.Config.RGB_565);
final Canvas canvas = new Canvas(finalBitmap);
final Paint paint = new Paint();
paint.setDither(true);
paint.setFilterBitmap(true);
canvas.drawColor(0);
canvas.drawBitmap(bitmap, new Rect(cropX, cropY, cropX + cropWidth, cropY + cropHeight), new Rect(0, 0, thumbnailWidth,
thumbnailHeight), paint);
bitmap.recycle();
// Store (long thumbnailId, short focusX, short focusY, JPEG data).
final ByteArrayOutputStream cacheOutput = new ByteArrayOutputStream(16384);
final DataOutputStream dataOutput = new DataOutputStream(cacheOutput);
byte[] retVal = null;
try {
dataOutput.writeLong(origId);
dataOutput.writeShort((int) ((focusX - cropX) * scaleFactor));
dataOutput.writeShort((int) ((focusY - cropY) * scaleFactor));
dataOutput.flush();
finalBitmap.compress(Bitmap.CompressFormat.JPEG, 80, cacheOutput);
retVal = cacheOutput.toByteArray();
synchronized (thumbnailCache) {
thumbnailCache.put(thumbId, retVal, timestamp);
}
cacheOutput.close();
finalBitmap.recycle();
} catch (Exception e) {
;
}
return retVal;
}
将图片缩略图写到了Cache中。好,马上我们就要弄清楚数据源的数据从什么地方来了。
我们看DataSource类中有个关键的函数loadMedaSets
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);
}
}
}
数据源的数据有两部分,其中一部分是开机时mediaprovider提供的T卡和内置存储器的数据,另外在开启时,会启动一个MediaFeed的线程检测是否有改变,新发现的数据将由CacheService将其处理变成Cache,最后,通过CacheService.loadMediaSets来加载数据。
到现在我们只清楚用户消息的处理和数据的流向的过程,下次我们开始将各个界面的刷新和切换,再会。