前言

时光飞逝,从事Android系统开发已经两年了,总想写点什么来安慰自己。思考了非常久总是无法下笔,认为没什么好写的。如今最终决定写一些符合大多数人需求的东西,想必使用过Android手机的人们一定对“图库”(下面简称Gallery)这个应用非常熟悉。在Android市场里面有各种关于图库的应用,他们的最初原型事实上就是Android系统原生“图库”,仅仅是做了不同的差异化而已(UI差异化)。在研究Gallery源代码之前,我们须要对设计模式有一定的了解,依据自己对Gallery的了解,Gallery的设计就好比是一座设计精良的而且高效运转的机器(32个攒)。毫不夸张地说,在Android市场里,没有一款“图库”应用的设计设计可以和Gallery媲美。接下来的一段时间,就让我们共同来揭开Gallery的神奇面纱。

数据载入

在研究Gallery之前,我们还是来赞赏一下Gallery的总体效果,详细见图1-1所看到的:

图1-1

首先我们先来看一下Gallery的发展历史,在Android2.3之前Android系统的“图库”名为Gallery3D,在Android2.3之后系统将之前的Gallery3D更改为Gallery2,一直沿用到眼下最新版本号(4.4),Gallery2在UI和功能上面做了质的飞跃,是眼下Android源代码中很优秀的模块,对于Android应用开发人员来说是很好的开源项目,当中的设计新思想和设计模式都值得我们借鉴。

如今回到我们研究的主题-数据载入,我们先来看一下Gallery2在源代码中的路径(package/app/Gallery2/),在该路径下包括了“图库”使用的资源和源代码。我们在设计一款软件的时候首先考虑的是数据的存储和訪问,因此我们也依照这种设计思路来探究Gallery2的数据载入过程。讲到这儿略微提一下我分析源代码的方式,可能大家对Android源代码略微有一点了解的同学应该知道,Android源代码是很庞大的,因此选择分析程序的切入点大致能够分为两类:第一种是依照操程序操作步骤分析源代码——适用于界面跳转清晰的程序;另外一种是依据打印的Log信息分析程序的执行逻辑——适用于复杂的操作逻辑。

首先我们先来看一下BucketHelper.java类(/src/com/android/gallery3d/data/BucketHelper.java),该类主要是负责读取MediaProvider数据库中Image和Video数据,详细代码例如以下所看到的:

package com.android.gallery3d.data;

import android.annotation.TargetApi;
import android.content.ContentResolver;
import android.database.Cursor;
import android.net.Uri;
import android.provider.MediaStore.Files;
import android.provider.MediaStore.Files.FileColumns;
import android.provider.MediaStore.Images;
import android.provider.MediaStore.Images.ImageColumns;
import android.provider.MediaStore.Video;
import android.util.Log; import com.android.gallery3d.common.ApiHelper;
import com.android.gallery3d.common.Utils;
import com.android.gallery3d.util.ThreadPool.JobContext; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap; class BucketHelper { private static final String TAG = "BucketHelper";
private static final String EXTERNAL_MEDIA = "external"; // BUCKET_DISPLAY_NAME is a string like "Camera" which is the directory
// name of where an image or video is in. BUCKET_ID is a hash of the path
// name of that directory (see computeBucketValues() in MediaProvider for
// details). MEDIA_TYPE is video, image, audio, etc.
// BUCKET_DISPLAY_NAME字段为文件文件夹名称 BUCKET_ID字段为文件夹路径(path)的HASH值
// The "albums" are not explicitly recorded in the database, but each image
// or video has the two columns (BUCKET_ID, MEDIA_TYPE). We define an
// "album" to be the collection of images/videos which have the same value
// for the two columns.
// "专辑"的划分方式为:当文件具有同样的文件夹(BUCKET_ID)和多媒体类型(MEDIA_TYPE)即属于同一专辑
// The goal of the query (used in loadSubMediaSetsFromFilesTable()) is to
// find all albums, that is, all unique values for (BUCKET_ID, MEDIA_TYPE).
// In the meantime sort them by the timestamp of the latest image/video in
// each of the album.
//
// The order of columns below is important: it must match to the index in
// MediaStore.
private static final String[] PROJECTION_BUCKET = {
ImageColumns.BUCKET_ID,
FileColumns.MEDIA_TYPE,
ImageColumns.BUCKET_DISPLAY_NAME}; // The indices should match the above projections.
private static final int INDEX_BUCKET_ID = 0;
private static final int INDEX_MEDIA_TYPE = 1;
private static final int INDEX_BUCKET_NAME = 2; // We want to order the albums by reverse chronological order. We abuse the
// "WHERE" parameter to insert a "GROUP BY" clause into the SQL statement.
// The template for "WHERE" parameter is like:
// SELECT ... FROM ... WHERE (%s)
// and we make it look like:
// SELECT ... FROM ... WHERE (1) GROUP BY 1,(2)
// The "(1)" means true. The "1,(2)" means the first two columns specified
// after SELECT. Note that because there is a ")" in the template, we use
// "(2" to match it.
private static final String BUCKET_GROUP_BY = "1) GROUP BY 1,(2"; private static final String BUCKET_ORDER_BY = "MAX(datetaken) DESC"; // Before HoneyComb there is no Files table. Thus, we need to query the
// bucket info from the Images and Video tables and then merge them
// together.
//
// A bucket can exist in both tables. In this case, we need to find the
// latest timestamp from the two tables and sort ourselves. So we add the
// MAX(date_taken) to the projection and remove the media_type since we
// already know the media type from the table we query from.
private static final String[] PROJECTION_BUCKET_IN_ONE_TABLE = {
ImageColumns.BUCKET_ID,
"MAX(datetaken)",
ImageColumns.BUCKET_DISPLAY_NAME}; // We keep the INDEX_BUCKET_ID and INDEX_BUCKET_NAME the same as
// PROJECTION_BUCKET so we can reuse the values defined before.
private static final int INDEX_DATE_TAKEN = 1; // When query from the Images or Video tables, we only need to group by BUCKET_ID.
private static final String BUCKET_GROUP_BY_IN_ONE_TABLE = "1) GROUP BY (1"; public static BucketEntry[] loadBucketEntries(
JobContext jc, ContentResolver resolver, int type) {
if (ApiHelper.HAS_MEDIA_PROVIDER_FILES_TABLE) {//当API1>= 11(即Android3.0版本号之后)
return loadBucketEntriesFromFilesTable(jc, resolver, type);//获取MediaScanner数据库中多媒体文件(图片和视频)的文件夹路径和文件夹名称
} else {//Android3.0之前版本号
return loadBucketEntriesFromImagesAndVideoTable(jc, resolver, type);
}
} private static void updateBucketEntriesFromTable(JobContext jc,
ContentResolver resolver, Uri tableUri, HashMap<Integer, BucketEntry> buckets) {
Cursor cursor = resolver.query(tableUri, PROJECTION_BUCKET_IN_ONE_TABLE,
BUCKET_GROUP_BY_IN_ONE_TABLE, null, null);
if (cursor == null) {
Log.w(TAG, "cannot open media database: " + tableUri);
return;
}
try {
while (cursor.moveToNext()) {
int bucketId = cursor.getInt(INDEX_BUCKET_ID);
int dateTaken = cursor.getInt(INDEX_DATE_TAKEN);
BucketEntry entry = buckets.get(bucketId);
if (entry == null) {
entry = new BucketEntry(bucketId, cursor.getString(INDEX_BUCKET_NAME));
buckets.put(bucketId, entry);
entry.dateTaken = dateTaken;
} else {
entry.dateTaken = Math.max(entry.dateTaken, dateTaken);
}
}
} finally {
Utils.closeSilently(cursor);
}
} private static BucketEntry[] loadBucketEntriesFromImagesAndVideoTable(
JobContext jc, ContentResolver resolver, int type) {
HashMap<Integer, BucketEntry> buckets = new HashMap<Integer, BucketEntry>(64);
if ((type & MediaObject.MEDIA_TYPE_IMAGE) != 0) {
updateBucketEntriesFromTable(
jc, resolver, Images.Media.EXTERNAL_CONTENT_URI, buckets);
}
if ((type & MediaObject.MEDIA_TYPE_VIDEO) != 0) {
updateBucketEntriesFromTable(
jc, resolver, Video.Media.EXTERNAL_CONTENT_URI, buckets);
}
BucketEntry[] entries = buckets.values().toArray(new BucketEntry[buckets.size()]);
Arrays.sort(entries, new Comparator<BucketEntry>() {
@Override
public int compare(BucketEntry a, BucketEntry b) {
// sorted by dateTaken in descending order
return b.dateTaken - a.dateTaken;
}
});
return entries;
} private static BucketEntry[] loadBucketEntriesFromFilesTable(
JobContext jc, ContentResolver resolver, int type) {
Uri uri = getFilesContentUri(); Cursor cursor = resolver.query(uri,
PROJECTION_BUCKET, BUCKET_GROUP_BY,
null, BUCKET_ORDER_BY);
if (cursor == null) {
Log.w(TAG, "cannot open local database: " + uri);
return new BucketEntry[0];
}
ArrayList<BucketEntry> buffer = new ArrayList<BucketEntry>();
int typeBits = 0;
if ((type & MediaObject.MEDIA_TYPE_IMAGE) != 0) {
typeBits |= (1 << FileColumns.MEDIA_TYPE_IMAGE);
}
if ((type & MediaObject.MEDIA_TYPE_VIDEO) != 0) {
typeBits |= (1 << FileColumns.MEDIA_TYPE_VIDEO);
}
try {
while (cursor.moveToNext()) {
if ((typeBits & (1 << cursor.getInt(INDEX_MEDIA_TYPE))) != 0) {
BucketEntry entry = new BucketEntry(
cursor.getInt(INDEX_BUCKET_ID),
cursor.getString(INDEX_BUCKET_NAME));//构造元数据BucketEntry if (!buffer.contains(entry)) {
buffer.add(entry);//加入数据信息
}
}
if (jc.isCancelled()) return null;
}
} finally {
Utils.closeSilently(cursor);
}
return buffer.toArray(new BucketEntry[buffer.size()]);
} private static String getBucketNameInTable(
ContentResolver resolver, Uri tableUri, int bucketId) {
String selectionArgs[] = new String[] {String.valueOf(bucketId)};
Uri uri = tableUri.buildUpon()
.appendQueryParameter("limit", "1")
.build();
Cursor cursor = resolver.query(uri, PROJECTION_BUCKET_IN_ONE_TABLE,
"bucket_id = ?", selectionArgs, null);
try {
if (cursor != null && cursor.moveToNext()) {
return cursor.getString(INDEX_BUCKET_NAME);
}
} finally {
Utils.closeSilently(cursor);
}
return null;
} @TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB)
private static Uri getFilesContentUri() {
return Files.getContentUri(EXTERNAL_MEDIA);
} public static String getBucketName(ContentResolver resolver, int bucketId) {
if (ApiHelper.HAS_MEDIA_PROVIDER_FILES_TABLE) {
String result = getBucketNameInTable(resolver, getFilesContentUri(), bucketId);
return result == null ? "" : result;
} else {
String result = getBucketNameInTable(
resolver, Images.Media.EXTERNAL_CONTENT_URI, bucketId);
if (result != null) return result;
result = getBucketNameInTable(
resolver, Video.Media.EXTERNAL_CONTENT_URI, bucketId);
return result == null ? "" : result;
}
} public static class BucketEntry {
public String bucketName;
public int bucketId;
public int dateTaken; public BucketEntry(int id, String name) {
bucketId = id;
bucketName = Utils.ensureNotNull(name);
} @Override
public int hashCode() {
return bucketId;
} @Override
public boolean equals(Object object) {
if (!(object instanceof BucketEntry)) return false;
BucketEntry entry = (BucketEntry) object;
return bucketId == entry.bucketId;
}
}
}

接下来我们再来看看BucketHelper类的调用关系的时序图,详细如1-2所看到的:

 图1-2

到眼下为止我们大致了解了Gallery数据载入的一个大体流程,接下来的文章将分析Album数据的读取以及数据封装。

Android源代码之Gallery专题研究(1)的更多相关文章

  1. Android源代码之Gallery专题研究(2)

    引言 上一篇文章已经解说了数据载入过程,接下来我们来看一看数据载入后的处理过程.依照正常的思维逻辑.当数据载入之后,接下来就应该考虑数据的显示逻辑. MVC显示逻辑 大家可能对J2EE的MVC架构比較 ...

  2. Android源码之Gallery专题研究(1)

    前言 时光飞逝,从事Android系统开发已经两年了,总想写点什么来安慰自己.思考了很久总是无法下笔,觉得没什么好写的.现在终于决定写一些符合大多数人需求的东西,想必使用过Android手机的人们一定 ...

  3. Android源码之Gallery专题研究(2)

    引言 上一篇文章已经讲解了数据加载过程,接下来我们来看一看数据加载后的处理过程.按照正常的思维逻辑,当数据加载之后,接下来就应该考虑数据的显示逻辑. MVC显示逻辑 大家可能对J2EE的MVC架构比较 ...

  4. Android实战简易教程-第十枪(画廊组件Gallery有用研究)

    Gallery组件用于拖拽浏览图片,以下我们就来看一下怎样实现. 一.实现Gallery 1.布局文件非常easy: <?xml version="1.0" encoding ...

  5. Android源代码结构分析

    Google提供的Android包含了:Android源代码,工具链,基础C库,仿真环境,开发环境等,完整的一套.第一级别的目录和文件如下所示:----------------├── Makefile ...

  6. Android 源码获取-----在Windows环境下通过Git得到Android源代码

    在学习Android的过程中,深入其源代码研究对我们来说是非常重要的,这里将介绍如何通过在Windows环境下使用Git来得到我们的Android源代码. 1.首先确保你电脑上安装了Git,这个通过  ...

  7. Android ImageSwitcher和Gallery的使用

    前几天,听说室友的老师要求他们做一个图片效果.其效果如下图所示(可左右滑动切换图片): 我当时晃眼一看,第一感觉好高级的样子.我还没做过这种效果呢,但室友说他们同学已经有人做出来了,我觉得既然有人做出 ...

  8. (国内)完美下载android源代码(文章已经丢失)

    刚刚文章莫名其妙的丢了,我重写了一篇,http://blog.csdn.net/song19891121/article/details/50099857 我们在很多时候需要下载android源代码进 ...

  9. Android系统源代码目录结构 “Android源代码”“目录结构”

    在讲述Android源码编译的三个步骤之前,将先介绍Android源码目录结构,以便读者理清Android编译系统核心代码在Android源代码的位置. Android源代码顶层目录结构如下所示: ├ ...

随机推荐

  1. STM32学习内容和计划

    一.STM32学习内容(流程) 1.学习STM32开发流程 ①MDK使用.建立工程.调试等 ②库开发方法 2.学习STM32常用外设开发 ①GPIO ②中断 ③定时器 ④串口 ⑤CAN 3.学习STM ...

  2. 转:我们是否应该把后端构建为API

    原文来自于:http://www.infoq.com/cn/news/2015/07/api-or-not 不久前,在StackExchange网站上,一位名为SLC的用户提起他正在设计一个ASP.N ...

  3. bzoj 1455: 罗马游戏 左偏树+并查集

    1455: 罗马游戏 Time Limit: 5 Sec  Memory Limit: 64 MBSubmit: 668  Solved: 247[Submit][Status] Descriptio ...

  4. Kaggle Competition Past Solutions

    Kaggle Competition Past Solutions We learn more from code, and from great code. Not necessarily alwa ...

  5. Code First 指定外键名称

    指定类外键有注释(DataAnnotation)和FluentAPI两种方式, 目前掌握的知识我们先把DataAnnotation可用的四种方式总结如下 第一种方法: //1-指定导航属性,会自动生成 ...

  6. Android ViewDragHelper源码解析

    在自定义ViewGroup的过程中,如果涉及到View的拖动滑动,ViewDragHelper的使用应该是少不了的,它提供了一系列用于用户拖动子View的辅助方法和相关的状态记录,像Navigatio ...

  7. Android textAppearance的属性设置及TextView属性详解

    textAppearance的属性设置 android:textAppearance="?android:attr/textAppearanceSmall" android:tex ...

  8. jni.h头文件详解一

    1.jni.h头文件路径: /usr/lib/jvm/jdk_1.6.0_43/include/jni.h 2.jni.h头文件组成分析图: 3.下面通过上图进行分析讲解jni.h头文件. 一. jn ...

  9. POJ_3662_Telephone_Lines_(二分+最短路)

    描述 http://poj.org/problem?id=3662 给一张图,要将1与n连起来.可以有k条边免费,其他边自费,付费的值为所有自费边中最大的值.求最小付费. Telephone Line ...

  10. wps操作记录

    WPS Excel 1.点击插入---形状:画好方框,选中后右键“编辑文字”,在方框中加入你需要的文字信息 2.点击插入---形状:画出连接线,按住SHIFT拖动可以水平或垂直的直线 3.调整位置.选 ...