Android Handler Asynchronous message processing mechanism to create powerful picture loading classes

Source: Internet
Author: User
Tags message queue semaphore

Reprint please indicate source: http://blog.csdn.net/lmj623565791/article/details/38476887, this article from "Zhang Hongyang's Blog"

Recently created a group, convenient for everyone to communicate, group number:55032675

The previous blog describes the android asynchronous message processing mechanism, if you do not understand, you can see: The Android Asynchronous message processing mechanism lets you deeply understand Looper, Handler, message three relations. At the end of the blog, it is proposed that the asynchronous message processing mechanism can be used to update the UI not only in Mainactivity, but also in other places, and has recently been considering this issue, and has been fortunate to come up with a practical case of using the asynchronous message processing mechanism in a large number of images loaded tool classes, In fact, but also especially hope to write a large number of pictures loaded articles, and finally have a chance to introduce briefly:

1. Overview

Generally a large number of images loading, such as the GridView implementation of the mobile phone album function, will generally be used to LRUCache, thread pool, task queue, etc. what can asynchronous message processing be used for?

1. For UI thread when bitmap loading is complete, update ImageView

2, in the image loading class initialization, we will maintain a loop instance in a sub-thread, of course, the child thread also has messagequeue,looper will always be in that loop waiting for the arrival of the message, when there is a message arrives, from the task queue according to queue scheduling (FIFO, LIFO, etc.), remove a task into the thread pool for processing.

A simple process: when you need to load a picture, first load the picture into the task queue, and then use the loop thread (sub-thread) in the hander send a message that the task arrives, loop () (sub-thread) will then take out a task, to load the picture, when the picture loading is complete, A message is sent using the UI thread's handler to update the UI interface.

Said so much, we estimate also feel clouds in fog to go, below see actual example.

2, the realization of the function of the picture library

The program first scans all the folders containing pictures in the phone, eventually selects the folder with the most pictures, and uses the GridView to display the pictures.

1. layout file

<relativelayout xmlns:android= "http://schemas.android.com/apk/res/android"    xmlns:tools= "http// Schemas.android.com/tools "    android:layout_width=" match_parent "    android:layout_height=" Match_parent " >    <gridview        android:id= "@+id/id_gridview"        android:layout_width= "Match_parent"        android: layout_height= "Match_parent"        android:cachecolorhint= "@android: color/transparent"        android:columnwidth= " 90dip "        android:gravity=" center "        android:horizontalspacing=" 20dip "        android:listselector=" @android : Color/transparent "        android:numcolumns=" Auto_fit "        android:stretchmode=" ColumnWidth "        android: verticalspacing= "20dip" >    </GridView></RelativeLayout>

The layout file is fairly simple, just a gridview.

2, Mainactivity

Package Com.example.zhy_handler_imageloader;import Java.io.file;import Java.io.filenamefilter;import Java.util.arrays;import Java.util.hashset;import Java.util.list;import Android.app.activity;import Android.app.progressdialog;import Android.content.contentresolver;import Android.database.cursor;import Android.net.uri;import Android.os.bundle;import Android.os.environment;import Android.os.Handler;import Android.provider.mediastore;import Android.widget.gridview;import Android.widget.imageview;import Android.widget.listadapter;import Android.widget.toast;public class Mainactivity extends Activity{private ProgressDialog mprogressdialog;private ImageView mimageview;/** * The number of pictures in the storage folder */private int mpicssize;/** * The folder with the highest number of pictures */ Private File mimgdir;/** * All pictures */private list<string> mimgs;private GridView mgirdview;private listadapter mAdapt er;/** * Temporary helper class to prevent multiple scans of the same folder */private hashset<string> mdirpaths = new hashset<string> ();p rivate Handler Mhandler = new Handler () {public void Handlemessage (Android.os.Message msg) {Mprogressdialog.dismiss (); Mimgs = Arrays.aslist (Mimgdir.list (new FilenameFilter () {@Overridepublic Boolean accept (File dir, String filename) {if (Filename.endswith (". jpg")) return true; return false;})); * * You can see the path of the folder and the path of the picture are saved separately, greatly reducing the memory consumption; */madapter = new Myadapter (Getapplicationcontext (), Mimgs, Mimgdir.getabsolutepath ()); Mgirdview.setadapter (madapter);}; @Overrideprotected void OnCreate (Bundle savedinstancestate) {super.oncreate (savedinstancestate); Setcontentview ( R.layout.activity_main); Mgirdview = (GridView) Findviewbyid (R.id.id_gridview); GetImages ();} /** * Using ContentProvider to scan the picture in the phone, this method runs in the sub-thread to complete the scan of the picture, eventually get the most jpg folder */private void GetImages () {if (! Environment.getexternalstoragestate (). Equals (environment.media_mounted)) {Toast.maketext (this, "No external storage", Toast.length_short). Show (); return;} Show progress bar Mprogressdialog = Progressdialog.show (this, null, "Loading ..."); New Thread (New Runnable () {@Overridepublic void run () {Uri Mimageuri = mediastore.imAges. Media.external_content_uri; Contentresolver mcontentresolver = MainActivity.this.getContentResolver ();//Only the images of JPEG and PNG are queried cursor mcursor = Mcontentresolver.query (Mimageuri, null,mediastore.images.media.mime_type + "=? or "+ MediaStore.Images.Media.MIME_TYPE +" =? ", new string[] {" Image/jpeg "," Image/png "},mediastore.images.media.date_ MODIFIED); while (Mcursor.movetonext ()) {//Gets the path of the picture, string path = Mcursor.getstring (Mcursor.getcolumnindex ( MediaStore.Images.Media.DATA));//Gets the parent path name of the picture file Parentfile = new file (path). Getparentfile (); String Dirpath = Parentfile.getabsolutepath (); Use a hashset to prevent multiple scans of the same folder (without this judgment, the picture is still quite scary ~ ~) if (Mdirpaths.contains (Dirpath)) {continue;} Else{mdirpaths.add (Dirpath);} int picsize = parentfile.list (new FilenameFilter () {@Overridepublic Boolean accept (File dir, String filename) {if ( Filename.endswith (". jpg")) return True;return false;}}). Length;if (Picsize > Mpicssize) {mpicssize = Picsize;mimgdir = Parentfile;}} Mcursor.close ();//scan complete, auxiliary hashset can also free up memory mdirpaths= NULL; Notify handler to scan the picture complete mhandler.sendemptymessage (0x110);}). Start ();}}

Mainactivity is also relatively simple, using ContentProvider Auxiliary, find the most Pictures folder, directly handler to hide ProgressDialog, and then initialize the data, adapter, etc.;

But a little attention:

1, when scanning the picture, use a temporary hashset to save the scanned folder, this can effectively avoid duplicate scan. For example, I have a folder in my phone with more than 3,000 pictures, if not judged will scan this folder 3,000 times, processor time and memory consumption is very considerable.

2, in the adapter, when saving list<string>, consider saving only the name of the picture, the path is passed on as a variable. In general, the path of the picture is much longer than the picture name, adding 3000 images, the path length 30, the average image length of 10, then the list<string> save the completion path needs length: (30+10) *3000 = 120000; and the individual storage only needs: 30+10*3000 = 30030; the more pictures, the more objective the memory is saved;

In short, as much as possible to reduce the memory consumption, these are easy to do ~

3, the GridView adapter

Package Com.example.zhy_handler_imageloader;import Java.util.list;import Android.content.context;import Android.view.layoutinflater;import Android.view.view;import Android.view.viewgroup;import Android.widget.baseadapter;import Android.widget.imageview;import Com.zhy.utils.imageloader;public class MyAdapter Extends Baseadapter{private Context mcontext;private list<string> mdata;private String mdirpath;private Layoutinflater minflater;private imageloader mimageloader;public myadapter (context context, list<string> MData, String dirpath) {this.mcontext = Context;this.mdata = Mdata;this.mdirpath = Dirpath;minflater = Layoutinflater.from ( Mcontext); mimageloader = Imageloader.getinstance ();} @Overridepublic int GetCount () {return mdata.size ();} @Overridepublic Object getItem (int position) {return mdata.get (position);} @Overridepublic long Getitemid (int position) {return position;} @Overridepublic view GetView (int position, view Convertview, final ViewGroup parent) {Viewholder holder = null; if (Convertview = = null) {holder = new Viewholder (); Convertview = Minflater.inflate (R.layout.grid_item, Parent,false); Holder.mimageview = (ImageView) Convertview.findviewbyid (r.id.id_item_image); Convertview.settag (holder);} Else{holder = (Viewholder) Convertview.gettag ();} Holder.mImageView.setImageResource (r.drawable.friends_sends_pictures_no);// Use Imageloader to load images mimageloader.loadimage (Mdirpath + "/" + mdata.get (position), Holder.mimageview); return Convertview ;} Private Final class Viewholder{imageview Mimageview;}}

As you can see, there is basically no difference in how the traditional adapter is written, even in GetView, where there are no common callbacks (findviewbytag~ is used to prevent image misalignment); Just a single line of code:

Mimageloader.loadimage (Mdirpath + "/" + mdata.get (position), holder.mimageview); it is not used or quite cool, all the details that need to be handled are encapsulated.

4, Imageloader

Now comes the critical moment, we encapsulate the Imageloader class, and of course our asynchronous message processing mechanism also appears in it.

The first is a lazy load of the singleton

/** * Single example gets the instance object *  * @return */public static Imageloader getinstance () {if (minstance = null) {synchronized (imageloader . Class) {if (minstance = = null) {minstance = new Imageloader (1, Type.lifo);}}} return minstance;}

Nothing to say, call the private constructor directly, you can see that the default is passed in 1 (the number of threads in thread pool), and LIFO (how the queue works)

Private Imageloader (int threadcount, type type) {init (threadcount, type);} private void init (int threadcount, type type) {//Loop Threadmpoolthread = new Thread () {@Overridepublic void run () {try{//please Ask for a semaphore msemaphore.acquire ();} catch (Interruptedexception e) {}looper.prepare (); mpoolthreadhander = new Handler () {@Overridepublic void handlemessage (Message msg) {Mthreadpool.execute (Gettask ()); Try{mpoolsemaphore.acquire ();} catch (Interruptedexception e) {}}};// Release a semaphore msemaphore.release (); Looper.loop ();}}; Mpoolthread.start ();//Gets the application maximum available memory int maxmemory = (int) runtime.getruntime (). MaxMemory (); int cacheSize = MAXMEMORY/8; Mlrucache = new lrucache<string, bitmap> (cacheSize) {@Overrideprotected int sizeOf (String key, Bitmap value) { return Value.getrowbytes () * Value.getheight ();};}; Mthreadpool = Executors.newfixedthreadpool (threadcount); mpoolsemaphore = new Semaphore (threadcount); mTasks = new Linkedlist<runnable> (); mtype = type = = null? Type.LIFO:type;}

We then called our Init method inside the private construct, and at the beginning of the method we created the Mpoolthread, which we performed looper.prepare, initialized Mpoolthreadhander, in this sub-thread. Looper.loop; If you read the previous blog, you must know that at this point a message queue is maintained in this sub-thread, and the child thread enters a loop of infinite read messages, and the message sent by Mpoolthreadhander This handler is sent directly to the message queue in this thread. Then look at the method of Handlemessage in Mpoolthreadhander, call the Gettask method to take out a task directly and then put it into the thread pool to execute. If you are more careful, you may find there are some semaphore operation code, if you do not understand what is the semaphore, you can refer to: Java concurrency topic: Semaphore Implementation of mutual exclusion and connection pooling. Briefly say Msemaphore (signal number 1), because Mpoolthreadhander is actually a child thread initialization, So I called Msemaphore.acquire before the initialization to request a semaphore, and then released the semaphore after initialization, why would I do that? Because the main thread may be used immediately to mpoolthreadhander, but Mpoolthreadhander is initialized in the child thread, although very fast, but I can not be assured that the main thread is initialized at the end of use, in order to avoid null pointer exceptions, So I call it when the main thread needs to be used:

/** * Add a task *  * @param runnable */private synchronized void AddTask (runnable runnable) {try{//request semaphore to prevent Mpoolthreadhan Der is nullif (Mpoolthreadhander = = null) Msemaphore.acquire ();} catch (Interruptedexception e) {}mtasks.add (runnable); Mpoolthreadhander.sendemptymessage (0x110);}

If Mpoolthreadhander does not initialize, it will acquire a semaphore, in fact, to wait for Mpoolthreadhander initialization to complete. If you are interested in this, you can note the code about Msemaphore, and then use Thread.Sleep to pause for 1 seconds when initializing Mpoolthreadhander, and you will find such an error.

At the end of initialization, Mimageloader.loadimage (Mdirpath + "/" + mdata.get (position), Holder.mimageview) is called in GetView; So let's go see the LoadImage method.

/** * Load Picture * * @param path * @param imageView */public void LoadImage (Final String path, final ImageView ImageView) {//SE T Tagimageview.settag (path);//UI thread if (Mhandler = = null) {Mhandler = new Handler () {@Overridepublic void Handlemessage ( Message msg) {Imgbeanholder holder = (imgbeanholder) Msg.obj;imageview ImageView = Holder.imageview; Bitmap BM = Holder.bitmap; String path = holder.path;if (Imageview.gettag (). toString (). Equals (path) {Imageview.setimagebitmap (BM);}};} Bitmap BM = Getbitmapfromlrucache (path), if (BM! = null) {Imgbeanholder holder = new Imgbeanholder (); holder.bitmap = Bm;hold Er.imageview = Imageview;holder.path = path; Message message = Message.obtain (); message.obj = holder;mhandler.sendmessage (message);} Else{addtask (New Runnable () {@Overridepublic void run () {ImageSize ImageSize = getimageviewwidth (imageView); int Reqwidth = Imagesize.width;int Reqheight = imagesize.height; Bitmap BM = Decodesampledbitmapfromresource (path, reqwidth,reqheight); Addbitmaptolrucache (path, BM); ImgBEanholder holder = new Imgbeanholder (); holder.bitmap = Getbitmapfromlrucache (path); holder.imageview = ImageView; Holder.path = path; Message message = Message.obtain (); message.obj = holder;//log.e ("TAG", "mhandler.sendmessage (message);"); Mhandler.sendmessage (message); Mpoolsemaphore.release ();}});}}

This code is longer, of course, is the core of the code

10-29 Line: First set the incoming ImageView path, and then initialize a mhandler used to set the ImageView bitmap, note that at this time in the UI thread, that is, the message sent by the Mhandler will be called in the UI thread. You can see in the handlemessage, we remove Imageview,bitmap,path from the message, and then the path and the ImageView tag to compare, to prevent the image dislocation, the final set of bitmap;

31 lines: Let's go from the LRUCache first to find out if this picture has been cached

32-40: If found, then use Mhandler directly to send messages, here used a imgbeanholder to encapsulate the imageview,bitmap,path of the three objects. Then update the execution handlemessage code to update the UI

43-66 rows: If the cache is not present, create a Runnable object as a task to execute the AddTask method to join the task queue

49 Line: Getimageviewwidth according to ImageView to obtain the appropriate size of the picture, for the following compressed pictures, the code in order to paste down below

54 Line: The picture is compressed according to the width and height of the calculation. The code is pasted down in sequence below

56 Line: Put the compressed picture in the cache

58-64 lines, creating messages, sending using Mhandler, updating UI

/** * Obtain appropriate compression width and height according to ImageView * @param imageView * @return */private ImageSize getimageviewwidth (ImageView imageView) {Im Agesize imageSize = new ImageSize (); final Displaymetrics displaymetrics = Imageview.getcontext (). Getresources (). Getdisplaymetrics (); final layoutparams params = Imageview.getlayoutparams (); int width = Params.width = = Layoutparams.wrap_content? 0:imageview.getwidth (); Get actual image Widthif (width <= 0) width = params.width; Get Layout width parameterif (width <= 0) width = getimageviewfieldvalue (ImageView, "mmaxwidth"); check//maxwidth//parameterif (width <= 0) width = displaymetrics.widthpixels;int height = Params.height = = LayoutPa Rams. Wrap_content? 0:imageview.getheight (); Get actual image heightif (height <= 0) height = params.height; Get Layout height parameterif (height <= 0) height = getimageviewfieldvalue (imageView, "mmaxheight"); check//maxheight//parameterif (height <= 0) height = Displaymetrics.heightpixels;imagesiZe.width = Width;imagesize.height = Height;return imageSize;} 

/** * Based on calculated insamplesize, get compressed picture *  * @param pathName * @param reqwidth * @param reqheight * @return */private Bitmap de Codesampledbitmapfromresource (String pathname,int reqwidth, int reqheight) {//First resolution sets Injustdecodebounds to True, To get the picture size final bitmapfactory.options Options = new Bitmapfactory.options (); options.injustdecodebounds = true; Bitmapfactory.decodefile (pathName, options);//Call the method defined above to calculate the Insamplesize value options.insamplesize = Calculateinsamplesize (options, reqwidth,reqheight);//Use the obtained Insamplesize value to parse the picture again options.injustdecodebounds = false; Bitmap Bitmap = Bitmapfactory.decodefile (pathName, options); return Bitmap;}
Next look at the AddTask code:

/** * Add a task *  * @param runnable */private synchronized void AddTask (runnable runnable) {try{//request semaphore to prevent Mpoolthreadhan Der is nullif (Mpoolthreadhander = = null) Msemaphore.acquire ();} catch (Interruptedexception e) {}mtasks.add (runnable); Mpoolthreadhander.sendemptymessage (0x110);}

As you can see, simply put the task into the task queue and then use Mpoolthreadhander to send a message to the loop in the background, loop in the background will take out the message execution: Mthreadpool.execute (Gettask ());

Execute executes the Run method in the runnable that was analyzed above.

Note: The above code will also see Mpoolsemaphore this semaphore figure, the use of the next, because after calling AddTask, will go directly from the task queue to remove a task, put into the thread pool, because the thread pool is actually maintained a queue, then "remove a task from the task queue "This action will be done instantaneously, directly into the queue maintained by the thread pool, which will cause the user to set the scheduling queue as LIFO, but because the" remove a task from the task queue "action will be instantaneous, the queue is always maintained in the state of the empty queue, so that users feel that LIFO has no effect at all ; So I set a semaphore according to the number of worker threads that the user set thread pool, so that when the task is finished, the task queue will be taken from the task, so that the LIFO has a good effect; it is interesting to note all the Mpoolsemaphore code, which is understood under test.

The basic introduction to this code is complete. Details are still many, the following will be attached to the source code, interested in the study of codes, no interest, you can run the code, if you feel good fluency, good experience, can be used as a tool for direct use, the use of the GetView inside a line of code.


Post, I have the most mobile phone folder about 3000 pictures, loading speed is quite smooth:


Real machine record, a little drop frame, pay attention to see, in the middle I drag the scroll bar, but the picture is basically an instant display.

Say, FIFO if set to this mode, in the control does not do processing, the user pull slow effect is good, but the user's phone if there is a thousands of, instantly pull to the end, the last screen picture of the display may need to drink a cup of tea ~ Of course, you can do in the control of the processing, or, Drag the time not to load the picture, stop in to reload. Or, when the phone is lifted up, giving a big acceleration, the screen is still quickly sliding when the load stops loading and loading the picture when it stops.

LIFO This mode may be a lot better user experience, no matter how many blocks the user pulls, the final stop of the screen image will be instantly displayed ~

Last break. The benefits of using asynchronous message processing as a child thread behind it can actually be implemented directly with a sub-thread, but the child thread may need while (true) in run and then query the task queue for tasks every 200 milliseconds or less. There is no thread.sleep, then go to query, so if not to add a task for a long time, the thread will continue to query;

The asynchronous message mechanism is only executed when the message is sent and, of course, is more accurate; When no task arrives for a long time, it will not be queried, it'll always be blocked here; there is also a point, this mechanism Android internal implementation, how also than we do a thread stability, high efficiency bar ~


SOURCE Click to download







Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.