Objective
Time flies, engaged in Android system development has been two years, always want to write something to comfort themselves. Thinking for a long time always can not write, think there is no good writing. Now it's decided to write something that fits most people's needs, and people who must have used Android phones must be very familiar with the application of the "Gallery" (hereinafter referred to as the "gallery"). In the Android Market there are various applications about the library, their original prototype is actually the Android system native "library", just make a different difference (UI differentiation). Before studying the gallery source code, we need to have a certain understanding of the design patterns, according to their understanding of gallery, gallery design is like a well-designed and efficient operation of the machine (32 saved). It's no exaggeration to say that in the Android market, there is no "gallery" app designed to rival gallery. For the next period of time, let us come together to uncover the mystery 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 Android2.3 the Android system's "gallery" named Gallery3d, After Android2.3 the system to change the previous gallery3d to Gallery2, has been used to the latest version (4.4), Gallery2 in the UI and features made a qualitative leap, is currently a very good Android source code module, for Android application developers is very good Open source projects, where the design of new ideas and design patterns are worthy of our reference.
Now back to the subject of our research-data loading, let's take a look at the path of Gallery2 in the source code (PACKAGE/APP/GALLERY2/), which contains the resources and source code used by the "gallery". When we design a software, we first consider the data storage and access, so we also follow the design ideas to explore the Gallery2 data loading process. Speaking here a little bit about the way I analyze the source code, you may have a little bit of Android source knowledge of the students should know that the Android source code is very large, so the choice of the analysis program can be divided into two categories: the first is to follow the operating procedures to analyze the source code- Apply to the interface jump clear program; the second is to analyze the running logic of the program based on the printed log information-for complex operational logic.
First, let's take a look at the Buckethelper.java class (/src/com/android/gallery3d/data/ Buckethelper.java), this class is primarily responsible for reading the image and video data in the Mediaprovider database, as shown in the following code:
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 are the directory//NAME of where an image or video was in. BUCKET_ID is a hash of the path//name of this directory (see ComputebuckeTvalues () in Mediaprovider for//details). Media_type is video, image, audio, etc. The Bucket_display_name field is the file directory name bucket_id the hash value of the directory path (path)//The "albums" is not explicitly recorded in the Databas E, but per image//or video has the columns (bucket_id, media_type). We define AN//"album" To be the collection of the Images/videos which has the same value//for the and the columns. The "album" is divided into: When the file has the same directory (BUCKET_ID) and the multimedia type (media_type) that belong to the same album//The goal of the query (used in Loadsubmediasetsfromfile STable ()) is to//Find all albums, which 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 are 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 and columns specified//after SELECT. Note that because there are 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//buckets 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 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 (Dateta Ken) ", 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 define D before. private static final int index_date_taken = 1; When query is from the Images or Video tables, we are 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 (i.e. after Android3.0 version) return loadbucketentriesfromfilestable (JC, resolver, type);//Received Take the directory path and directory name of the multimedia files (Pictures and videos) in the Mediascanner database} else {//android3.0 Previous version return Loadbucketentriesfromimagesandvi Deotable (JC, resolver, type); }} private static void Updatebucketentriesfromtable (Jobcontext JC, Contentresolver Resolver, Uri tableur I, 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.dateta Ken } }); 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_b Y); 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_v IDEO); } try {while (Cursor.movetonext ()) {if (Typebits & (1 << cursor.getint (INDEX _media_type))) = 0) {Bucketentry entry = new Bucketentry (Cursor.getint (in dex_bucket_id), cursor.getstring (Index_bucket_name));//Construction 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 (). 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; } }}
Let's take a look at the timing diagram of the invocation relationship of the Buckethelper class, as shown in 1-2:
Figure 1-2
So far we've got a rough overview of gallery data loading, and the next article will analyze the reading of album Data and the encapsulation of data.