Preface
Time flies. I have been engaged in Android development for two years. I always want to write something to comfort myself. I have been thinking for a long time and cannot write anything. Now I finally decided to write something that meets the needs of most people. People who have used Android phones must be very familiar with the "Gallery" (Gallery) application. There are a variety of Image Library applications in the Android Market. Their initial prototype is actually the native "Image Library" of the Android system, but they are differentiated (UI differentiation ). Before studying the Gallery source code, we need to have a certain understanding of the design pattern, based on our own knowledge of Gallery, the Gallery design is like a well-designed and efficient machine (32 workers ). It is no exaggeration to say that in the Android Market, the design of no "Image Library" application is comparable to that of Gallery. In the next period, let's unveil the secret of Gallery.
Data Loading
Before studying Gallery, let's take a look at the overall effect of Gallery, as shown in Figure 1-1:
Figure 1-1
First, let's take a look at the history of Gallery. Before Android, the "Image Library" of the Android system was named Gallery3D. After Android, the system changed the previous Gallery3D to Gallery2, the latest version (4.4) has been used for a long time. Gallery2 has made a qualitative leap in UI and functions, and is a very good module in the current Android source code, for Android app developers, it is a very good open-source project. The new design ideas and design patterns are worth learning from.
Now let's go back to the topic-data loading. Let's take a look at the path of Gallery2 in the source code (package/app/Gallery2 /), the path contains the resources and source code used by "Image Library. When designing a software, we first consider data storage and access. Therefore, we also follow this design idea to explore the data loading process of Gallery2. Speaking of this, I will mention the method of analyzing the source code. If you have a little knowledge of the Android source code, you should know that the Android source code is very huge, therefore, the starting point of the analysis program can be roughly divided into two types: the first is to analyze the source code according to the operating procedures-applicable to programs with clear interface jump; the second is to analyze the program running logic based on the printed Log information-suitable for complicated operation logic.
First, let's take a look at BucketHelper. java class (/src/com/android/gallery3d/data/BucketHelper. java), which is mainly responsible for reading the Image and Video data in the MediaProvider database. The specific code is as follows:
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. // The BUCKET_DISPLAY_NAME field is the file directory name. The BUCKET_ID field is the HASH value of The directory path (path). // 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. // The album type is as follows: when the file has the same directory (BUCKET_ID) and multimedia type (MEDIA_TYPE) that is, it belongs to The same album // 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 shoshould 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) {// when API1> = 11 (after Android3.0) return loadBucketEntriesFromFilesTable (jc, resolver, type); // obtain multimedia files (images and videos) in the MediaScanner Database) directory path and directory name} else {// return handler (jc, resolver, type);} private static void updateBucketEntriesFromTable (JobContext jc, ContentResolver resolver, Uri tableUri, hashMap
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
Buckets = new HashMap
(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
() {@ Override public int compare (BucketEntry a, BucketEntry B) {// sorted by dateTaken in descending order return B. dateTaken-. dateTaken ;}}); return entries;} private static BucketEntry [] keys (JobContext jc, ContentResolver resolver, int type) {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
Buffer = new ArrayList
(); 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); // construct the metadata BucketEntry if (! Buffer. contains (entry) {buffer. add (entry); // add data information} 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 (). appendQue RyParameter ("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) {Strin G 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 Object) {if (! (Object instanceof BucketEntry) return false; BucketEntry entry = (BucketEntry) object; return bucketId = entry. bucketId ;}}}
Next, let's take a look at the sequence diagram of the call relationship of the BucketHelper class, as shown in 1-2:
Figure 1-2
So far, we have roughly understood a general process of Gallery data loading. The next article will analyze Album Data Reading and Data encapsulation.