Use Android Handler asynchronous message processing mechanism to create a powerful image loading class

Source: Internet
Author: User

Use Android Handler asynchronous message processing mechanism to create a powerful image loading class

 

Recently, a group was created to facilitate communication. Group Number:55032675

The previous blog introduced the Android asynchronous Message processing mechanism. If you do not know about it, you can see: Android asynchronous Message processing allows you to have a deep understanding of the relationship among logoff, Handler, and Message. At the end of the blog, I proposed that the asynchronous message processing mechanism should not only update the UI in MainActivity, but also be used elsewhere. I have been thinking about this issue recently, I have come up with a practical case to use the asynchronous message processing mechanism to load a large number of images. In fact, I especially hope that I can write an article about loading a large number of images, finally, we have a chance ~ First, let's briefly introduce:

1. Overview

Generally, a large number of images are loaded. For example, when using GridView to implement the album function of a mobile phone, LruCache, thread pool, and task queue are generally used. Where can I use Asynchronous Message Processing?

1. Used by the UI thread to update ImageView after Bitmap is loaded

2. During the initialization of the image loading class, we will maintain a Loop instance in a child thread. Of course, MessageQueue will also exist in the Child thread, and logoff will stop waiting for the message to arrive in the loop all the time, when a message arrives, a task is taken from the task queue and put into the thread pool for processing according to the queue scheduling method (FIFO, LIFO, etc.

A simple process: to load an image, first add the loaded image to the task queue, and then send a message using the hander in the loop thread (subthread), prompting that a task has arrived, in loop () (sub-thread), a task is taken out to load the image. When the image is loaded, the handler of the UI thread sends a message to update the UI interface.

After talking about this, you may think that the cloud is in the fog. Let's look at the actual example below.

2. Implementation of the Image Library Function

This program first scans all the folders containing images on the mobile phone, and finally selects the most picture folder, and displays the images in the folder using the GridView.

1. layout File

 

     
      
  
 

The layout file is quite simple, just like 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;/*** number of images in the storage folder */private int mPicsSize; /*** folder with the largest number of images */private File mImgDir;/*** all images */private List
 
  
MImgs; private GridView mGirdView; private ListAdapter mAdapter;/*** temporary helper class, used to prevent multiple scans of the same folder */private HashSet
  
   
MDirPaths = new HashSet
   
    
(); Private 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 that the folder path and image path are saved separately, which greatly reduces memory consumption. */mAdapter = new MyAdapter (getApplicationContext (), mImgs, mImgDir. get AbsolutePath (); mGirdView. setAdapter (mAdapter) ;};}; @ Overrideprotected void onCreate (Bundle savedInstanceState) {super. onCreate (savedInstanceState); setContentView (R. layout. activity_main); mGirdView = (GridView) findViewById (R. id. id_gridView); getImages ();}/*** use ContentProvider to scan images in the mobile phone. This method scans images in the sub-thread, 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;} // display the 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 query jpe 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 () {// obtain the image path String path = mCursor. getString (mCursor. getColumnIndex (MediaStore. images. media. DATA); // obtain the parent path of the image File parentFile = new File (path ). getParentFile (); String dirPath = parentFile. getAbsolutePath (); // use a HashSet to prevent scanning of the same folder multiple times (without this judgment, it is quite scary to add more images ~~) 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 (); // After scanning is completed, the auxiliary HashSet can also release the memory. mDirPaths = null; // notify Handler to scan the image to complete mHandler. sendEmptyMessage (0x110 );}}). start ();}}
   
  
 

MainActivity is also relatively simple. With the help of ContentProvider, find the folder with the most images, directly handler to hide ProgressDialog, and then initialize data, adapters, and so on;

 

But pay attention to the following:

1. When scanning an image, a temporary HashSet is used to save the scanned folder, which can effectively avoid repeated scanning. For example, I have more than 3000 images under a folder on my mobile phone. If I don't know, I will scan this folder for more than 3000 times. The CPU time and memory consumption are still considerable.

2. In the adapter, save the List To save the image name, the path is passed as a variable. Generally, the image path is much longer than the image name. If you add 3000 images, the path length is 30, and the average image length is 10, the List The length of the Save path must be: (30 + 10) * 3000 = 120000; but for separate storage, only: 30 + 10*3000 = 30030; the more images, the more objective the memory saved;

In short, it is easy to reduce the memory consumption as much as possible ~

3. 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
 
  
MData; private String mDirPath; private LayoutInflater mInflater; private ImageLoader mImageLoader; public MyAdapter (Context context, List
  
   
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 the image mImageLoader. loadImage (mDirPath ++ mData. get (position), holder. mImageView); return convertView;} private final class ViewHolder {ImageView mImageView ;}}
  
 

It can be seen that there is basically no difference with the traditional adapter writing method, and there is no common callback in getView (findViewByTag ~ To prevent image misplacement); only one line of code is added:

 

MImageLoader. loadImage (mDirPath +/+ mData. get (position), holder. mImageView); is it easy to use? All the details to be processed are encapsulated.

4. ImageLoader

Now we have reached a critical moment. Our encapsulated ImageLoader class, of course, our Asynchronous Message Processing Mechanism also appears in it.

First, it is a lazy loading Singleton.

 

/*** Obtain the instance object in a single instance ** @ return */public static ImageLoader getInstance () {if (mInstance = null) {synchronized (ImageLoader. class) {if (mInstance = null) {mInstance = new ImageLoader (1, Type. LIFO) ;}}return mInstance ;}

If you do not have any questions, call the private constructor directly. We can see that 1 (number of threads in the thread pool) and LIFO (queue working method) are passed in by default)

 

 

Private ImageLoader (int threadCount, Type type) {init (threadCount, type);} private void init (int threadCount, Type type) {// loop threadmPoolThread = new Thread () {@ Overridepublic void run () {try {// request a semaphore mSemaphore. acquire ();} catch (InterruptedException e) {} Loire. prepare (); mPoolThreadHander = new Handler () {@ Overridepublic void handleMessage (Message msgworkflow implements multiple mthreadpool.exe cute (getTask (); try {mPoolSemaphore. acquire () ;}catch (InterruptedException e) {}}; // release a semaphore mSemaphore. release (); logoff. loop () ;}}; mPoolThread. start (); // get the maximum available memory of the application int maxMemory = (int) Runtime. getRuntime (). maxMemory (); int cacheSize = maxMemory/8; mLruCache = new LruCache
 
  
(CacheSize) {@ Overrideprotected int sizeOf (String key, Bitmap value) {return value. getRowBytes () * value. getHeight () ;};}; mThreadPool = Executors. newFixedThreadPool (threadCount); mPoolSemaphore = new Semaphore (threadCount); mTasks = new threadlist
  
   
(); MType = type = null? Type. LIFO: type ;}
  
 

Then our init method is called in the private constructor. At the beginning of this method, the mPoolThread subthread is created, and The logoff is executed in this subthread. prepare, initialize mPoolThreadHander, logoff. loop; if you have read the previous blog, you must know that a message queue is maintained in this sub-thread, and this sub-thread will enter an infinite cycle of reading messages, the message sent by the handler mPoolThreadHander is directly sent to the message queue in this thread. Then look at the handleMessage method in mPoolThreadHander, directly call the getTask method to retrieve a task, and then put it into the thread pool for execution. If you are more careful, you may find some Semaphore operation code. If you do not know what a Semaphore is, you can refer to: Java concurrency topic: Semaphore mutex and connection pool. To put it simply, mSemaphore (Signal Number 1) is used. Because mPoolThreadHander is initialized by a subthread, I called mSemaphore before initialization. acquire requests a semaphore and releases it after initialization. Why do I do this? Because mPoolThreadHander may be used immediately in the main thread, but mPoolThreadHander is initialized in the sub-thread. Although the speed is very fast, I cannot guarantee that the initialization of the main thread has ended when it is used, to avoid NULL pointer exceptions, I call the following method when the main thread needs to use it:

 

 

/*** Add a task ** @ param runnable */private synchronized void addTask (Runnable runnable) {try {// request semaphores to prevent mPoolThreadHander from being nullif (mPoolThreadHander = null) mSemaphore. acquire ();} catch (InterruptedException e) {} mTasks. add (runnable); mPoolThreadHander. sendEmptyMessage (0x110 );}

If mPoolThreadHander is not initialized, a semaphore will be sent to acquire, which is to wait for mPoolThreadHander to complete initialization. If you are interested in this, you can comment out the mSemaphore code, and then use Thread. sleep to pause for 1 second in mPoolThreadHander initialization, and you will find such an error.

 

After Initialization is complete, the mImageLoader. loadImage (mDirPath +/+ mData. get (position), holder. mImageView) method will be called in getView. So let's look at the loadImage method.

 

/*** Load image ** @ param path * @ param imageView */public void loadImage (final String path, final ImageView imageView) {// set 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 (imageVie W. 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 () {@ 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 long and of course the core code.

 

Line 10-29: first, set the path for the input imageView, and then initialize a bitmap for the mHandler to set the imageView. Note that the message sent by the mHandler is in the UI thread at this time, it will be called in the UI thread. We can see that in handleMessage, we retrieve the ImageView, bitmap, and path from the message. Then we compare the path with the tag of imageView to prevent misplacement of the image, and finally set bitmap;

Row 31: We first go to the LruCache to check whether the image has been cached.

32-40: If a message is found, mHandler is used to send the message. Here, an ImgBeanHolder is used to encapsulate the ImageView, Bitmap, and Path objects. Then update and execute the handleMessage code to update the UI

Row 43-66: If no cache exists, create a Runnable object as the task and execute the addTask method to join the task queue.

Row 49: getImageViewWidth: obtains the size of the Image Based on the ImageView. It is used to compress the image. The code is pasted below in order.

54 rows: The image will be compressed Based on the width and height required by the calculation. Paste the following code in order

56 rows: Put the compressed image into the cache

Line 8-64: create a message, send it using mHandler, and update the UI

 

/*** Obtain the appropriate compression width and height based on ImageView ** @ param imageView * @ return */private ImageSize getImageViewWidth (ImageView 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 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 = LayoutParams. 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 ;}

 

 

/*** Obtain the compressed image based on the calculated inSampleSize ** @ param pathName * @ param reqWidth * @ param reqHeight * @ return */private Bitmap decodeSampledBitmapFromResource (String pathName, int reqWidth, int reqHeight) {// set inJustDecodeBounds to true for the first resolution to obtain the final BitmapFactory image size. 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 image options again. inJustDecodeBounds = false; Bitmap bitmap = BitmapFactory. decodeFile (pathName, options); return bitmap ;}
Next, let's look at the AddTask code:

 

 

/*** Add a task ** @ param runnable */private synchronized void addTask (Runnable runnable) {try {// request semaphores to prevent mPoolThreadHander from being 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 background loop. The background loop will retrieve the message and execute the message: mThreadPool.exe cute (getTask ());

 

Execute Executes the run method in the Runnable analyzed above.

Note: The above Code also shows the mPoolSemaphore semaphore, which is useful. Because after addTask is called, a task is taken directly from the task queue and put into the thread pool, because the thread pool also maintains a queue, the "retrieve a task from the task queue" action will be completed instantly and directly added to the queue maintained by the thread pool; this will cause the user to set the scheduling queue as LIFO, but because the "retrieve a task from the task queue" operation will be completed instantly, the queue will always remain in the empty queue status, this makes the user feel that LIFO has no effect at all. Therefore, I set a semaphore according to the number of worker threads in the thread pool set by the user, so that after the task is executed, in order to get the task from the task queue, the LIFO has a very good effect. If you are interested, you can comment out all the mPoolSemaphore code and you will understand it in the test.

This code has been basically introduced. There are still a lot of details, and the source code will be attached later. If you are interested in studying the Code and are not interested, you can run the Code. If you feel that the fluency is good and the experience is good, you can use it directly as a tool class, this is also a line of code in getView.

 

After you paste it, the maximum number of folders on my mobile phone is about 3000 images, and the loading speed is quite smooth:

Real machine recording, a little frame loss, please note, I am crazy drag the scroll bar in the middle, but the picture is basically instantly displayed.

To put it bluntly, if the FIFO mode is set to this mode and the control is not processed, the user's pull speed is still good, but if the user's mobile phone has thousands of images, it will be instantly pulled to the end, you may need a cup of tea to display the last screen ~ Of course, you can do the processing in the control, or, when dragging, do not load the image, stop and load again. Or, when the mobile phone is lifted up, it gives a great acceleration. The screen stops loading as soon as it slides, and the image is loaded when it stops.

In LIFO mode, the user experience may be much better. No matter what the user blocks, the final screen of the stopped image will be instantly displayed ~

In the end, the asynchronous message processing mechanism is used as the benefit of the subthread. In fact, it can be implemented directly using a subthread. However, the while (true) may be required in this subthread run) then, you can query whether a task exists in the task queue every 200 milliseconds or shorter. sleep, and then query; so if the task is not added for a long time, the thread will continue to query;

The asynchronous message mechanism can only be executed when a message is sent. Of course, it is more accurate. When no task arrives for a long time, it will not be queried and will be blocked; another point is that the internal implementation of this mechanism in Android is more stable and efficient than the Thread ~

 

Download source code

 

 

 

 

 

 

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.