前面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来加载数据。
到现在我们只清楚用户消息的处理和数据的流向的过程,下次我们开始将各个界面的刷新和切换,再会。