Android Framework training teaches you how to create an efficient image loading framework
1. Overview
There are not many good image loading frameworks, such as UIL, Volley, Picasso, and Imageloader. But as a qualified programmer, I must understand the implementation principles. So, today I will bring everyone together to design a picture frame that is loaded into the network and locally. Some people may say whether writing by themselves will be very scum, operational efficiency, and memory overflow. Rest assured that we can use the demo to talk about speed and make things so capricious.
Well, if you have read the previous blog post and use the asynchronous message processing mechanism similar to Android Handler to create a powerful image loading class, it may be of great help to the next article. If you don't have one, let's move on with me and stop looking at it.
For loading local images, of course, I have a relatively small number of mobile phone images. There are 7000 images:
1. First of all, the memory will not overflow, but Nima's pixels are so high now. How can this be guaranteed? I believe that using LruCache to manage your images in a unified manner is the best choice. All images are taken from LruCache to ensure that the memory of all images will not exceed the preset space.
2. The loading speed is just getting better. I tried to slide to the position of 3000 sheets. If you are still loading from the first one, Nima, what do you think I hit dota. Therefore, we need to introduce the loading policy. We cannot use the FIFO mode. We select LIFO, which is currently presented to the user and the latest loading. If not, select load.
3. Easy to use. Generally, the image uses the GridView as the control to load the image in getView. Of course, in order to make a good mess, you may need to set your own tag and write your own callback settings. Of course, we don't need to be so troublesome. We just need to use IoadImage (imageview, path) in a single sentence. The rest should be handled by our image loading framework.
When we do the above, there should be something wrong with loading local images.
The principle of loading network images is almost the same. There is an option to enable hard disk cache. If hard disk cache is enabled, you can search for images from the memory and then find images from the hard disk, finally, go to the network to download. After the download is complete, do not forget to write to the hard disk and add it to the memory cache. If it is not enabled, it is obtained directly from the network compression and added to the memory.
2,
Finally, let's take a look at it. For loading local images, you can download the Demo from this blog by loading the Android ultra-high imitation image selector image.
The following is an example of network image loading:
More than 80 images loaded from the network, we can see that I dragged them to the end, which is basically the first loading in front of the user's eyes. If there are more than 80 images from the first one, it is estimated that they are also drunk.
In addition, the picture is from Lao Guo's blog. Thank you !!! Ps: If you think the picture is not amazing, go to Lao Guo on Day Up.
3. Complete Parsing
1. Image Compression
Whether it is from the network or local images, the image must be compressed and then displayed:
What will the user give us if you want to compress the display? One imageview and one path are displayed after compression.
1. Local Image Compression
A. Obtain the size of the imageview.
To compress, the first step is to obtain the size of the imageview to display. If it is not large, it cannot be compressed?
How can I get the size of the imageview to be displayed?
/*** Obtain the appropriate compression width and height based on ImageView ** @ param imageView * @ return */public static ImageSize getImageViewSize (ImageView imageView) {ImageSize imageSize = new ImageSize (); displayMetrics displayMetrics = imageView. getContext (). getResources (). getDisplayMetrics (); LayoutParams lp = imageView. getLayoutParams (); int width = imageView. getWidth (); // obtain the actual imageview width. if (width <= 0) {width = lp. width; // obtain the width declared by imageview in layout} if (width <= 0) {// width = imageView. getMaxWidth (); // check the maximum value width = getImageViewFieldValue (imageView, mMaxWidth);} if (width <= 0) {width = displayMetrics. widthPixels;} int height = imageView. getHeight (); // obtain the actual imageview height. if (height <= 0) {height = lp. height; // obtain the width declared by imageview in layout} if (height <= 0) {height = getImageViewFieldValue (imageView, mMaxHeight ); // check the maximum value} if (height <= 0) {height = displayMetrics. heightPixels;} imageSize. width = width; imageSize. height = height; return imageSize;} public static class ImageSize {int width; int height ;}
We can see that after we get the imageview:
First, getWidth is used to obtain the display width. Sometimes, the returned value of getWidth is 0;
Then let's see if it has written width in the layout file;
If there is no exact value in the layout file, let's see if it has set the maximum value;
If the maximum value is not set, we only need to come up with our ultimate solution and use our screen width;
In short, we must get a suitable display value instead of making it willful.
We can see here or the maximum width. We use reflection instead of getMaxWidth (). Where is visa? Because getMaxWidth actually requires API 16, I am also drunk. For compatibility, we adopt the reflection solution. The reflected code will not be pasted.
B. Set the appropriate inSampleSize.
We get the size of the image we want to display. We do not want to compare it with the true width and height of the image. Do we get a suitable inSampleSize to compress the image.
The first step is to get the image width and height:
// Obtain the image width and height, and do not load the image into the memory BitmapFactory. options options = new BitmapFactory. options (); options. inJustDecodeBounds = true; BitmapFactory. decodeFile (path, options );
These three rows are used to obtain the true width and height of the image, which exists in our options;
Then we can calculate the inSampleSize happily:
/*** Calculate SampleSize Based on the required width and height and the actual width and height of the image ** @ param options * @ param width * @ param height * @ return */public static int caculateInSampleSize (options options, int reqWidth, int reqHeight) {int width = options. outWidth; int height = options. outHeight; int inSampleSize = 1; if (width> reqWidth | height> reqHeight) {int widthRadio = Math. round (width * 1.0f/reqWidth); int heightRadio = Math. round (height * 1.0f/reqHeight); inSampleSize = Math. max (widthRadio, heightRadio);} return inSampleSize ;}
Options stores the actual width and height. reqWidth and reqHeight are the sizes we have previously obtained to display. After comparison, we can get a suitable inSampleSize;
With inSampleSize:
Options. inSampleSize = ImageSizeUtil. caculateInSampleSize (options, width, height); // use the obtained InSampleSize to parse the image options again. inJustDecodeBounds = false; Bitmap bitmap = BitmapFactory. decodeFile (path, options); return bitmap;
After these rows, the image is compressed.
The above is the compression of local images. What if it is a network image?
2. Network Image Compression
A. Download and save the package to the SD card, and then use the local compression solution. This method is currently enabled when the hard disk cache is enabled. What if it is not enabled?
B. Use BitmapFactory. decodeStream (is, null, opts );
/*** Download the image to the specified file based on the url ** @ param urlStr * @ param file * @ return */public static Bitmap downloadImgByUrl (String urlStr, ImageView imageview) {FileOutputStream fos = null; InputStream is = null; try {URL url = new URL (urlStr); HttpURLConnection conn = (HttpURLConnection) url. openConnection (); is = new BufferedInputStream (conn. getInputStream (); is. mark (is. available (); Options opts = new Options (); opts. inJus TDecodeBounds = true; Bitmap bitmap = BitmapFactory. decodeStream (is, null, opts); // obtain the width and height of the imageview imageViewSize = ImageSizeUtil. getImageViewSize (imageview); opts. inSampleSize = ImageSizeUtil. caculateInSampleSize (opts, imageViewSize. width, imageViewSize. height); opts. inJustDecodeBounds = false; is. reset (); bitmap = BitmapFactory. decodeStream (is, null, opts); conn. disconnect (); return bitmap ;} Catch (Exception e) {e. printStackTrace ();} finally {try {if (is! = Null) is. close ();} catch (IOException e) {}try {if (fos! = Null) fos. close ();} catch (IOException e) {}} return null ;}
Basically it is similar to local compression, and it is also two sampling times. Of course, note that our is packaged so that reset () can be performed. Directly returning is cannot be used twice.
At this point, image compression is finished.
2. image loading framework architecture
After our image compression is completed, we should put it into our LruCache and set it to our ImageView.
Now let's talk about the architecture of our framework;
1. The Singleton contains an LruCache for managing our images;
2. In the Task queue, every time we attach an image request, we encapsulate it into a Task that is stored in our TaskQueue;
3. A background thread is included. This thread is started when the instance is initialized for the first time and runs in the background all the time. What about the task? Do you still remember that we have a task queue and a queue for storing tasks, so we need to send a message to the background thread at the same time every time we load image requests, the background thread uses the thread pool to go to TaskQueue to obtain a task for execution;
4. scheduling policy. As mentioned in 3, the backend thread goes to TaskQueue to retrieve a task. This task is not random and can be selected based on a policy. One is FIFO and the other is LIFO, I prefer the latter.
Okay, basically we have these structures. Next let's look at our specific implementation.
3. Specific implementation
1. Constructor
public static ImageLoader getInstance(int threadCount, Type type){if (mInstance == null){synchronized (ImageLoader.class){if (mInstance == null){mInstance = new ImageLoader(threadCount, type);}}}return mInstance;}
This is not necessary. Focus on our constructor.
/*** Image loading class ** @ author zhy **/public class ImageLoader {private static ImageLoader mInstance;/*** core image cache object */private LruCache
MLruCache;/*** thread pool */private ExecutorService mThreadPool; private static final int DEAFULT_THREAD_COUNT = 1;/*** queue Scheduling Method */private Type mType = Type. LIFO;/*** task queue */private queue list
MTaskQueue;/*** background polling Thread */private Thread mPoolThread; private Handler mPoolThreadHandler;/*** Handler */private Handler mUIHandler In the UI Thread; private Semaphore records = new Semaphore (0); private Semaphore mSemaphoreThreadPool; private boolean isDiskCacheEnable = true; private static final String TAG = ImageLoader; public enum Type {FIFO, LIFO ;} private ImageLoader (int threadCount, Type type) {init (threadCount, type );} /*** initialize ** @ param threadCount * @ param type */private void init (int threadCount, Type type) {initBackThread (); // obtain the maximum available memory int maxMemory = (int) Runtime of our application. getRuntime (). maxMemory (); int cacheMemory = maxMemory/8; mLruCache = new LruCache
(CacheMemory) {@ Overrideprotected int sizeOf (String key, Bitmap value) {return value. getRowBytes () * value. getHeight () ;}}; // create a thread pool mThreadPool = Executors. newFixedThreadPool (threadCount); mTaskQueue = new queue list
(); MType = type; mSemaphoreThreadPool = new Semaphore (threadCount);}/*** initialize the backend polling thread */private void initBackThread () {// backend polling Thread mPoolThread = new Thread () {@ Overridepublic void run () {loid. prepare (); mPoolThreadHandler = new Handler () {@ Overridepublic void handleMessage (Message msg) {// define cute (getTask (); try {mSemaphoreThreadPool. acquire () ;}catch (InterruptedException e) {}}; // release a semaphore mSemaphorePoolThreadHandler. release (); logoff. loop () ;};}; mPoolThread. start ();}
All member variables are appended to the constructor;
In the construction, we call init. In init, we can set the number of image loading threads and the loading policy in the background. In init, we first initialize the background thread initBackThread (), and we can see this background thread, in fact, it is a loose loop that eventually keeps going. We also initialize a mPoolThreadHandler to send messages to this thread;
Next, initialize mLruCache, mThreadPool, and mTaskQueue;
2. loadImage
After the construction is complete, it is certainly used. You can load local or network images by calling loadImage (final String path, final ImageView imageView, final boolean isFromNet.
/*** Set the image ** @ param path * @ param imageview */public void loadImage (final String path, final imageView ImageView, final boolean isFromNet) {imageView. setTag (path); if (mUIHandler = null) {mUIHandler = new Handler () {public void handleMessage (Message msg) {// get the image, set the image ImgBeanHolder holder = (ImgBeanHolder) msg for the imageview callback. obj; Bitmap bm = holder. bitmap; ImageView imageview = holder. imag EView; String path = holder. path; // compare path with the getTag storage path if (imageview. getTag (). toString (). equals (path) {imageview. setImageBitmap (bm) ;}};}// obtain bitmapBitmap bm = getBitmapFromLruCache (path) in the cache based on the path; if (bm! = Null) {refreashBitmap (path, imageView, bm);} else {addTask (buildTask (path, imageView, isFromNet ));}}
First, we will set the imageview. setTag; and then initialize a mUIHandler. You don't have to guess. This mUIHandler user updates our imageview, because this method must be called by the main thread.
Then call: getBitmapFromLruCache (path); obtain bitmap in the cache according to the path; if it is found, set our image directly;
private void refreashBitmap(final String path, final ImageView imageView,Bitmap bm){Message message = Message.obtain();ImgBeanHolder holder = new ImgBeanHolder();holder.bitmap = bm;holder.path = path;holder.imageView = imageView;message.obj = holder;mUIHandler.sendMessage(message);}
You can see that if an image is found, you can directly use UIHandler to send a message. Of course, some necessary parameters are included, and then the image is set in the handleMessage of UIHandler;
Path, bitmap, and imageview are obtained in handleMessage. Remember:
// Compare the path with the getTag storage path
If (imageview. getTag (). toString (). equals (path ))
{
Imageview. setImageBitmap (bm );
}
Otherwise, the image may be messy.
If it is not found, you can use buildTask to create a new task and add it to the task queue in addTask.
BuildTask is complicated. because it involves both local and network, Let's first look at the addTask code:
private synchronized void addTask(Runnable runnable){mTaskQueue.add(runnable);// if(mPoolThreadHandler==null)wait();try{if (mPoolThreadHandler == null)mSemaphorePoolThreadHandler.acquire();} catch (InterruptedException e){}mPoolThreadHandler.sendEmptyMessage(0x110);}
It's easy, that is, runnable joins TaskQueue and uses mPoolThreadHandler at the same time (Do you remember this handler for interacting with our background thread .) Send a message to the background thread and ask it to extract a task for execution. Specific Code:
MPoolThreadHandler = new Handler () {@ Overridepublic void handleMessage (Message msg) {// The thread pool retrieves a task and executes mthreadpool.exe cute (getTask ());
Directly use the mThreadPool thread pool, and then use getTask to retrieve a task.
/*** Retrieve a method from the task queue ** @ return */private Runnable getTask () {if (mType = Type. FIFO) {return mTaskQueue. removeFirst ();} else if (mType = Type. LIFO) {return mTaskQueue. removeLast ();} return null ;}
The getTask code is also relatively simple, that is, the task is retrieved from the task queue header or tail according to the Type.
Are you curious about the code in the task? In fact, we have the last piece of code left. buildTask
/*** Create a task ** @ param path * @ param imageView * @ param isFromNet * @ return */private Runnable buildTask (final String path, final ImageView imageView, final boolean isFromNet) {return new Runnable () {@ Overridepublic void run () {Bitmap bm = null; if (isFromNet) {File file = getDiskCacheDir (imageView. getContext (), md5 (path); if (file. exists () // If {Log. e (TAG, find image: + path + in disk cache .); bm = loadImageFromLocal (file. getAbsolutePath (), imageView);} else {if (isDiskCacheEnable) // check whether hard disk cache is enabled {boolean downloadState = DownloadImgUtils. downloadImgByUrl (path, file); if (downloadState) // if the download is successful {Log. e (TAG, download image: + path + to disk cache. path is + file. getAbsolutePath (); bm = loadImageFromLocal (file. getAbsolutePath (), imageView) ;}} else // load {Log. e (TAG, load image: + path + to memory .); bm = DownloadImgUtils. downloadImgByUrl (path, imageView) ;}} else {bm = loadImageFromLocal (path, imageView) ;}// 3. Add the image to the cache addBitmapToLruCache (path, bm ); refreashBitmap (path, imageView, bm); mSemaphoreThreadPool. release () ;};} private Bitmap loadImageFromLocal (final String path, final ImageView imageView) {Bitmap bm; // load the image // compress the image // 1. Obtain the image size ImageSize = ImageSizeUtil. getImageViewSize (imageView); // 2. compress the image bm = decodeSampledBitmapFromPath (path, imageSize. width, imageSize. height); return bm ;}
When we create a new task, the cached bitmap is not found in the memory. Our task is to load the compressed bitmap according to the path and return it. Then we add LruCache to set the callback display.
First, let's determine if it is a network task?
If yes, first find it in the hard disk cache (the file name in the hard disk is: md5 generated based on path is the name ).
If the hard disk cache does not exist, determine whether the hard disk cache is Enabled:
If it is enabled: Download the image and use loadImageFromLocal to load the image locally (the compressed code has been described earlier );
If it is not enabled: it is directly obtained from the Network (compressed code obtained, as described above );
If it is not a network image, load the image by loadImageFromLocal.
After the above steps, bitmap is obtained; then addBitmapToLruCache is added, and the refreashBitmap callback displays the image.
/*** Add the image to LruCache ** @ param path * @ param bm */protected void addBitmapToLruCache (String path, Bitmap bm) {if (getBitmapFromLruCache (path) = null) {if (bm! = Null) mLruCache. put (path, bm );}}
By now, all our code has been analyzed;
Cached Image Location: In Android/data/Project packageName/cache of the SD card:
However, you must note that in the code, you will see some semaphores:
First: mSemaphorePoolThreadHandler = new Semaphore (0); used to control the initialization of our mPoolThreadHandler. When mPoolThreadHandler is used, it is null. If it is null, mSemaphorePoolThreadHandler is used. acquire () is blocked. When mPoolThreadHandler initialization ends, we will call it. release (); remove blocking.
Second: mSemaphoreThreadPool = new Semaphore (threadCount); the number of semaphores is the same as the number of threads on which images are loaded. For every task executed, we will reduce the semaphores by one; each time a task is completed, the semaphore + 1 is used to retrieve the task. What is the purpose? Why is there no idle thread when our task arrives? The task is added to TaskQueue all the time. When the thread completes the task, it can be retrieved from TaskQueue according to the policy, our LIFO makes sense.
At this point, our image loading framework is over. You can try to load a local image, or load a large number of images on the network to get the loading speed together ~~~
4. MainActivity
The current time is used ~~
In MainActivity, I used Fragment. Below I paste the code of Fragment and layout files. For details, let's look at the Code:
package com.example.demo_zhy_18_networkimageloader;import android.content.Context;import android.os.Bundle;import android.support.v4.app.Fragment;import android.util.Log;import android.view.LayoutInflater;import android.view.View;import android.view.ViewGroup;import android.widget.ArrayAdapter;import android.widget.GridView;import android.widget.ImageView;import com.zhy.utils.ImageLoader;import com.zhy.utils.ImageLoader.Type;import com.zhy.utils.Images;public class ListImgsFragment extends Fragment{private GridView mGridView;private String[] mUrlStrs = Images.imageThumbUrls;private ImageLoader mImageLoader;@Overridepublic void onCreate(Bundle savedInstanceState){super.onCreate(savedInstanceState);mImageLoader = ImageLoader.getInstance(3, Type.LIFO);}@Overridepublic View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState){View view = inflater.inflate(R.layout.fragment_list_imgs, container,false);mGridView = (GridView) view.findViewById(R.id.id_gridview);setUpAdapter();return view;}private void setUpAdapter(){if (getActivity() == null || mGridView == null)return;if (mUrlStrs != null){mGridView.setAdapter(new ListImgItemAdaper(getActivity(), 0,mUrlStrs));} else{mGridView.setAdapter(null);}}private class ListImgItemAdaper extends ArrayAdapter
{public ListImgItemAdaper(Context context, int resource, String[] datas){super(getActivity(), 0, datas);Log.e(TAG, ListImgItemAdaper);}@Overridepublic View getView(int position, View convertView, ViewGroup parent){if (convertView == null){convertView = getActivity().getLayoutInflater().inflate(R.layout.item_fragment_list_imgs, parent, false);}ImageView imageview = (ImageView) convertView.findViewById(R.id.id_img);imageview.setImageResource(R.drawable.pictures_no);mImageLoader.loadImage(getItem(position), imageview, true);return convertView;}}}
We can see that in getView, the image is loaded with a line of mImageLoader. loadImage.
Fragment_list_imgs.xml
Item_fragment_list_imgs.xml
Okay, now it's over ~~~ If you have any bugs or comments, please leave a message ~
Download source code
Bytes ----------------------------------------------------------------------------------------------------------
Some of the bloggers have already been online. If you do not like boring text, stamp it (for initial record, we look forward to your support ):
1. Android custom controls create Android streaming layout and popular labels
2. Implementation of Android intelligent robot xiaomu
3. High imitation QQ5.0 slide
4. High imitation 5.2.1 main interface and message reminder