I. Overview
generally a large number of pictures loaded, such as the GridView implementation of the mobile phone album function, the general use of LRUCache, thread pool, task queues, etc., so asynchronous message processing can be used?
1. For UI thread update ImageView after bitmap load completes
2, in the picture loading class initialization, we will maintain a loop instance in a child thread, of course, there are messagequeue,looper in the child thread will always be in the loop stop waiting for the arrival of the message, when a message arrives, from the task queue according to the queue scheduling method (FIFO, LIFO, etc.), take out a task and put it into the thread pool for processing.
A simple process: when you need to load a picture, first add the picture to the task queue, and then use the hander in the Loop thread (child thread) to send a message indicating that the task arrives, loop () (child thread) will then take out a task, to load the picture, when the picture is loaded, A message is sent using the UI thread's handler to update the UI interface.
Said so much, we also think that the clouds in the fog to go, see the actual examples below.
Second, the realization of the function of Image Library
The program first scans all the folders that contain pictures on the phone, and eventually selects the folder with the most pictures, using 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, 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;
/** * Store the number of pictures in the folder * * Private int mpicssize;
/** * The largest number of pictures folder * * Private File Mimgdir;
/** * All Pictures * * * private list<string> Mimgs;
Private GridView Mgirdview;
Private ListAdapter Madapter;
/** * Temporary auxiliary class, used to prevent multiple scans of the same folder * *Private hashset<string> mdirpaths = new hashset<string> (); Private Handler Mhandler = new Handler () {public void Handlemessage (Android.os.Message msg) {Mprogressdialog
. dismiss (); Mimgs = Arrays.aslist (mimgdir.list (new FilenameFilter () {@Override public boolean accept (File dir, String F
Ilename) {if (Filename.endswith (". jpg")) return true;
return false;
}
})); /** * You can see the path of the folder and the path of the picture saved separately, greatly reducing the memory consumption;/Madapter = new Myadapter (Getapplicationcontext (), Mimgs, MI
Mgdir.getabsolutepath ());
Mgirdview.setadapter (Madapter);
};
};
@Override protected void OnCreate (Bundle savedinstancestate) {super.oncreate (savedinstancestate);
Setcontentview (R.layout.activity_main);
Mgirdview = (GridView) Findviewbyid (R.id.id_gridview);
GetImages (); /** * Use ContentProvider scan mobile phone pictures, this method is running in the child thread to complete the scan of the picture, and finally get the most JPG folder * * private void GetImages () {if (! EnviRonment.getexternalstoragestate (). Equals (environment.media_mounted)) {Toast.maketext (this, "temporary no external storage", TOAST.L
Ength_short). Show ();
Return
///Show progress bar Mprogressdialog = Progressdialog.show (this, null, "Loading ..."); New Thread (New Runnable () {@Override public void run () {Uri Mimageuri = MediaStore.Images.Media.EX
Ternal_content_uri;
Contentresolver mcontentresolver = mainactivity.this. Getcontentresolver (); Query only JPEG and png pictures Cursor mcursor = mcontentresolver.query (Mimageuri, NULL, MediaStore.Images.Media.MIME_TYPE + "=? or "+ MediaStore.Images.Media.MIME_TYPE +" =? ", new string[] {" Image/jpeg "," image/png "}, medias Tore.
Images.Media.DATE_MODIFIED); while (Mcursor.movetonext ()) {//Get 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 more or less scary ~ ~) if (Mdirpaths.contains (Dirpath)) {continue;
else {mdirpaths.add (dirpath); int picsize = parentfile.list (new FilenameFilter () {@Override public boolean accept (Fi
Le dir, String filename) {if (Filename.endswith (". jpg")) return true;
return false;
}). length;
if (Picsize > mpicssize) {mpicssize = picsize;
Mimgdir = Parentfile;
} mcursor.close ();
The scan completes, the auxiliary HashSet also can release the memory mdirpaths = NULL;
Notify handler scan picture complete mhandler.sendemptymessage (0x110);
}). Start ();
}
}
Mainactivity is also relatively simple, using ContentProvider Auxiliary, find the most Pictures folder, direct handler to hide ProgressDialog, and then initialize data, adapters and so on;
But a little attention:
(1) When scanning a picture, use a temporary hashset to save the scanned folder, this can effectively avoid duplicate scan. For example, I have a folder on my phone with more than 3,000 pictures below, 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 as a separate variable passed in. Under normal circumstances, the picture path is much longer than the picture name, add 3000 pictures, path length 30, picture average length 10, then list<string> save complete path need length: (30+10) *3000 = 120000; and separate storage only need: 30+10*3000 = 30030; the more pictures, the more objective the memory saving;
In short, as far as possible to reduce the memory consumption, these are very 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 ();
@Override public int GetCount () {return mdata.size ();
@Override public Object getitem (int position) {return mdata.get (position); @Override public long getitemid (int position) {
return position; @Override public View getview (int position, View Convertview, final viewgroup parent) {Viewholder holder = n
ull;
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 pictures mimageloader.loadimage (Mdirpath + "/" + mdata.get (position), holder.mimageview);
return convertview;
Private Final class Viewholder {ImageView mimageview;
}
}
You can see that the traditional adapter is basically no different from the writing, even in the getview there is no common callback (findviewbytag~ to prevent the image dislocation); just one more line of code:
Mimageloader.loadimage (Mdirpath + "/" + mdata.get (position), holder.mimageview);
It's still pretty cool to use, and all the details that need to be handled are encapsulated.
4, Imageloader
It is now at a critical juncture that we encapsulate the Imageloader class, and of course our asynchronous message processing mechanism also appears in it.
First is a lazy load of a single case
/**
* Single example obtains this 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, direct call to the private constructor, you can see that the default passed in 1 (number of thread pool threads), and LIFO (how the queue works)
Private Imageloader (int threadcount, type type) {init (threadcount, type);
} private void init (int threadcount, type type) {//loop thread mpoolthread = new Thread () {@Override
public void Run () {try {//Request a Semaphore msemaphore.acquire ();
catch (Interruptedexception e) {} looper.prepare (); Mpoolthreadhander = new Handler () {@Override public void Handlemessage (msg) {Mthre
Adpool.execute (Gettask ());
try {mpoolsemaphore.acquire ();
The catch (Interruptedexception e) {}}};
Releasing a semaphore msemaphore.release ();
Looper.loop ();
}
};
Mpoolthread.start ();
Gets the application's maximum available memory int maxmemory = (int) runtime.getruntime (). MaxMemory ();
int cacheSize = MAXMEMORY/8; Mlrucache = new lrucache<string, bitmap> (cacheSize) {@Override protected int sizeOf (String key, Bitmap V
Alue) {return value.getrowbytes () * Value.getheight ();
};
};
Mthreadpool = Executors.newfixedthreadpool (threadcount);
Mpoolsemaphore = new semaphore (threadcount);
Mtasks = new linkedlist<runnable> (); Mtype = Type = null?
Type.LIFO:type;
}
Then we call our Init method in the private construct, and at the beginning of this method we create the Mpoolthread thread, in which we execute the Looper.prepare, initialize Mpoolthreadhander, Looper.loop If you read the previous blog, you must know that a message queue is maintained in this child thread, and that the child thread enters a loop of infinite read messages, and messages sent by Mpoolthreadhander This handler are sent directly to the message queue in this thread. Then look at the Handlemessage method in the Mpoolthreadhander, call the Gettask method directly to take out a task and then put it into the thread pool to execute. If you are more careful, you may find that there are some signals in the operation of the code. To put it simply, the effect of the Msemaphore (signal number 1), because the mpoolthreadhander is actually child-thread initialized, So I called Msemaphore.acquire to request a semaphore before initialization, and then released the semaphore after the initialization was completed. Because the main thread may be used immediately to mpoolthreadhander, but the Mpoolthreadhander is initialized in the child thread, although the speed is very fast, but I also can not hundred percent guarantee that the main thread is used when the end of initialization, in order to avoid null pointer exception, So I call this when the main thread needs to be used:
/**
* Add a task
*
* @param runnable * *
private synchronized void AddTask (runnable runnable)
{
Try
{
///request semaphore to prevent Mpoolthreadhander from being null
if (Mpoolthreadhander = null)
msemaphore.acquire ();
(interruptedexception e)
{
}
Mtasks.add (runnable);
Mpoolthreadhander.sendemptymessage (0x110);
}
If the mpoolthreadhander is not initialized, it will go acquire a semaphore, in fact, to wait for Mpoolthreadhander initialization to complete. If you are interested in this, you can comment on the Msemaphore code, and then in the initialization mpoolthreadhander use Thread.Sleep to pause for 1 seconds, you will find such an error.
At the end of initialization, it is called in the GetView
Mimageloader.loadimage (Mdirpath + "/" + mdata.get (position), holder.mimageview);
Way, so let's go see the LoadImage method.
/** * Load Picture * * @param path * @param imageview/public void LoadImage (final String path, final Imagevie
W imageview) {//Set tag Imageview.settag (path); UI thread if (Mhandler = null) {Mhandler = new Handler () {@Override public void Handlemessage (Mes
Sage 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;
Holder.imageview = ImageView;
Holder.path = path;
Message message = Message.obtain ();
Message.obj = holder;
Mhandler.sendmessage (message); else {addtask (new Runnable () {@Override public 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 and, of course, the core code.
10-29 Lines: First, you set the path to the incoming ImageView, but when you initialize a mhandler to set up the ImageView bitmap, notice that at this point in the UI thread, This is the message that the Mhandler emits, which is invoked in the UI thread. You can see in the handlemessage, we remove the Imageview,bitmap,path from the message, and then compare the path to the ImageView tag, to prevent the image dislocation, the last set bitmap;
31 Lines: First we go to the LRUCache to find out if this picture has been cached
32-40: If found, then send the message directly using Mhandler, where a imgbeanholder is used to encapsulate the ImageView, Bitmap,path these three objects. Then update the execution handlemessage code to update the UI
43-66 lines: If there is no cache, create a Runnable object as a task to perform the AddTask method to join the task queue
Line 49: Getimageviewwidth According to the ImageView to get the appropriate picture size, for the subsequent compression of the picture, the code in order to paste the following
54 lines: According to the needs of the calculation of the width and height of the picture compression. The code pastes the following
Line 56: Put the compressed picture in the cache
58-64 lines, create the message, send it using Mhandler, update the UI
/** * ImageView to obtain the appropriate compression width and height * * @param imageview * @return * * Private imagesize Getimageviewwidth (Image
View imageview) {imagesize 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 width if (width <= 0) width = params.width; Get layout width parameter if (width <= 0) width = getimageviewfieldvalue (ImageView, "mmaxwidth"); Check//maxwidth//Parameter if (width <= 0) width = Displaymetrics.wid
Thpixels; int height = Params.height = = layoutparams.wrap_content? 0:imageview. GetHeight (); Get actual image height if (height <= 0) height = params.height; Get layout height parameter if (height <= 0) height =Getimageviewfieldvalue (ImageView, "mmaxheight"); Check//maxheight//parameter if (height <= 0) height = displaymetric
S.heightpixels;
ImageSize.Width = width;
Imagesize.height = height;
return imagesize;
/** * Based on computed insamplesize, the compressed picture * * @param pathName * @param reqwidth * @param reqheight * @return /Private Bitmap Decodesampledbitmapfromresource (String pathName, int reqwidth, int reqheight) {//The first resolution will be in
Justdecodebounds set to True to get picture size final bitmapfactory.options Options = new Bitmapfactory.options ();
Options.injustdecodebounds = true;
Bitmapfactory.decodefile (pathName, Options);
Invoke the method defined above to compute the insamplesize value options.insamplesize = calculateinsamplesize (options, Reqwidth, reqheight);
Resolves the picture Options.injustdecodebounds = False again using the obtained insamplesize value;
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 Mpoolthreadhander from being null
if (Mpoolthreadhander = null)
msemaphore.acquire ();
(interruptedexception e)
{
}
Mtasks.add (runnable);
Mpoolthreadhander.sendemptymessage (0x110);
}
As you can see, simply put the task in the task queue and then use Mpoolthreadhander to send a message to the loop in the background, and the loop in the background takes out the message execution:
Mthreadpool.execute (Gettask ());
Execute is the Run method in the runnable that is analyzed above.
Note: The above code will also see mpoolsemaphore this signal volume figure, said the use; since the call to AddTask, will go directly from the task queue to remove a task, into the thread pool, because the thread pool in fact also maintains a queue, then "pull a task from the task queue "This action will be done instantaneously, directly join the queue maintained by the thread pool; This can cause the user to set the dispatch queue as LIFO, but because the action" pull a task from the task queue "is done instantaneously, the queue remains in the empty queue, so the user feels that the LIFO has no effect at all. ; So I set a semaphore according to the number of thread pool worker threads, so that after the task is completed, the task queue will be taken from the task queues, so that the LIFO has a very good effect; interested can annotate all the Mpoolsemaphore code, the test will understand.
A basic introduction to this code is complete. Details or a lot of, the following will be attached to the source code, there is interest in the study of codes, no interest, you can run the code, if the feeling of fluency, good experience, can be used as a tool for direct use, use also getview inside a line of code.
Paste the effect picture, my mobile phone most folders about 3000 pictures, loading speed is quite quite smooth:
True machine record, a bit drop frame, pay attention to the effect chart, the middle of my crazy drag scroll bar, but the picture is basically still instantaneous display.
Say, FIFO if set to this mode, in the control does not do, the user pull slower effect is good, but the user mobile phone if there is a thousands of, instantaneous pull to the end, the last screen picture may need to drink a cup of tea ~ Of course, you can do in the control, or, Drag when not to load the picture, stop to load. Or, when the phone is lifted up, gives a big acceleration, the screen still quickly slides when the load stops, and when it stops loading the picture.
LIFO This mode may user experience will be much better, regardless of the user pull many pieces, the final stop of the screen picture will instantly show ~
Finally, break off. Using the asynchronous message processing mechanism as the benefit of the child threads behind it is actually possible to use a child thread directly, however, this child thread run may need a while (true) to query the task queue for tasks every 200 milliseconds or less. There is no thread.sleep, then go to the query, so if a long time did not add tasks, this thread will continue to query;
and the asynchronous message mechanism, only when the message is sent to execute, of course more accurate; when there is no task to arrive, also will not go to the query, will always be blocked in this; there is also a point, this mechanism within the Android implementation, how also than we do a thread stability, high efficiency bar ~