Currently, there are too many excellent picture loading frameworks for Android, such as Volley, Picasso, Imageloader, Glide, etc. However, as a programmer, it is still very important to understand the implementation principles, and only by understanding can you better use it. So, today I will simply design a network loading picture frame. The main thing is to be familiar with the network loading mechanism of images.
Generally speaking, an excellent image loading framework (ImageLoader) should have the following functions:
Image compression
Memory cache
Disk Cache
Synchronous loading of pictures
Asynchronous loading of pictures
Network pull
Then let’s introduce it from the above aspects:
1. Image compression (effectively reduces the probability of OOM occurrence)
I have already introduced the image compression function in the efficient loading of Bitmap. I will not say much about it and just upload the code. Here we directly abstract a class to complete the image compression function.
public class ImageResizer { private static final String TAG = "ImageResizer"; public ImageResizer() { super(); // TODO Auto-generated constructor stub } public Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight) { final options = new (); = true; (res, resId, options); = calculateInSampleSize(options, reqWidth, reqHeight); = false; return (res, resId, options); } public Bitmap decodeSampledBitmapFromBitmapFileDescriptor(FileDescriptor fd, int reqWidth,int reqHeight){ final options = new (); = true; (fd, null, options); = calculateInSampleSize(options, reqWidth, reqHeight); = false; return (fd, null, options); }
public int calculateInSampleSize( options, int reqWidth, int reqHeight) { final int width = ; final int height = ; int inSampleSize = 1; if (height > reqHeight || width > reqWidth) { final int halfHeight = height / 2; final int halfWidth = width / 2; while ((halfHeight / inSampleSize) > reqHeight && (halfWidth / inSampleSize) > halfWidth) { inSampleSize *= 2; } } return inSampleSize; } }
2. Memory cache and disk cache
Cache directly selects LruCache and DiskLruCache to complete memory caching and disk caching.
First initialize it:
private LruCache<String, Bitmap> mMemoryCache; private DiskLruCache mDiskLruCache; public ImageLoader(Context context) { mContext = (); //Allocate memory cache as 1/8 of the current process, and the disk cache capacity is 50M int maxMemory = (int) (().maxMemory() * 1024); int cacheSize = maxMemory / 8; mMemoryCache = new LruCache<String, Bitmap>(cacheSize) { @Override protected int sizeOf(String key, Bitmap value) { return () * () / 1024; } }; File diskCacheDir = getDiskChaheDir(mContext, "bitmap"); if (!()) { (); } if (getUsableSpace(diskCacheDir) > DISK_CACHE_SIZE) { try { mDiskLruCache = (diskCacheDir, 1, 1, DISK_CACHE_SIZE); mIsDiskLruCacheCreated = true; } catch (IOException e) { (); } } }
After creation, you need to provide a method to add and obtain the visual line. First, let’s look at the memory cache.
private void addBitmapToMemoryCache(String key, Bitmap bitmap) { if (getBitmapFromMemCache(key) == null) { (key, bitmap); } } private Bitmap getBitmapFromMemCache(String key) { return (key); }
Relatively speaking, memory cache is relatively simple, while disk cache is much more complex. Disk cache (LruDiskCache) does not provide a direct method to implement it, but requires the addition and reading of the file system through Editor and Snapshot.
First look at Editor, which provides commit and abort methods to commit and undo write operations to the file system.
//Write the downloaded pictures to the file system to realize disk cache private Bitmap loadBitmapFromHttp(String url, int reqWidth, int reqHeight) throws IOException { if (() == ()) { throw new RuntimeException("can not visit network from UI Thread."); } if (mDiskLruCache == null) return null; String key = hashKeyFormUrl(url); editor = (key); if (editor != null) { OutputStream outputStream = editor .newOutputStream(DISK_CACHE_INDEX); if (downloadUrlToStream(url, outputStream)) { (); } else { (); } } (); return loadBitmapForDiskCache(url, reqWidth, reqHeight); }
Snapshot, through it, you can get the FileInputStream corresponding to the disk cache object, but FileInputStream cannot be compressed easily, so the compressed image is loaded through FileDescriptor, and finally the loaded bitmap is added to the memory cache.
public Bitmap loadBitmapForDiskCache(String url, int reqWidth, int reqHeight) throws IOException { if (() == ()) { (TAG, "load bitmap from UI Thread , it's not recommended"); } if (mDiskLruCache == null) return null; Bitmap bitmap = null; String key = hashKeyFormUrl(url); snapshot = (key); if (snapshot != null) { FileInputStream fileInputStream = (FileInputStream) snapshot .getInputStream(DISK_CACHE_INDEX); FileDescriptor fileDescriptor = (); bitmap = ( fileDescriptor, reqWidth, reqHeight); if (bitmap != null) { addBitmapToMemoryCache(key, bitmap); } } return bitmap; }
3. Synchronous loading
The synchronous loading method needs to be called externally in the child thread.
//Synchronous loading public Bitmap loadBitmap(String uri, int reqWidth, int reqHeight) { Bitmap bitmap = loadBitmpaFromMemCache(uri); if (bitmap != null) { return bitmap; } try { bitmap = loadBitmapForDiskCache(uri, reqWidth, reqHeight); if (bitmap != null) { return bitmap; } bitmap = loadBitmapFromHttp(uri, reqWidth, reqHeight); } catch (IOException e) { (); } if (bitmap == null && !mIsDiskLruCacheCreated) { bitmap = downloadBitmapFromUrl(uri); } return bitmap; }
From the method, we can see that the working process follows the following steps:
First try to read the picture from the memory cache, then try to read the picture from the disk cache, and finally pull it from the network. This method cannot be executed in the main thread anymore, and the detection of the execution environment is implemented in loadBitmapFromHttp.
if (() == ()) { throw new RuntimeException("can not visit network from UI Thread."); }
4. Asynchronous loading
//Asynchronous loading public void bindBitmap(final String uri, final ImageView imageView, final int reqWidth, final int reqHeight) { (TAG_KEY_URI, uri); Bitmap bitmap = loadBitmpaFromMemCache(uri); if (bitmap != null) { (bitmap); return; } Runnable loadBitmapTask = new Runnable() { @Override public void run() { Bitmap bitmap = loadBitmap(uri, reqWidth, reqHeight); if (bitmap != null) { LoaderResult result = new LoaderResult(imageView, uri, bitmap); (MESSAGE_POST_RESULT, result) .sendToTarget(); } } }; THREAD_POOL_EXECUTOR.execute(loadBitmapTask); }
From the implementation of bindBitmap, the bindBitmap method will try to read the image from the memory cache. If the read is successful, the result will be returned directly. Otherwise, the loadBitmap method will be called in the thread pool. When the image is loaded successfully, the image, the address of the image and the imageView that needs to be bound will be encapsulated into a LoaderResult object, and then a message will be sent to the main thread through mMainHandler, so that the imageView can be set in the main thread.
Let’s take a look at the thread pool and Handler used in the bindBitmap method. First, let’s take a look at the implementation of the thread pool THREAD_POOL_EXECUTOR.
private static final int CPU_COUNT = () .availableProcessors(); private static final int CORE_POOL_SIZE = CPU_COUNT + 1; private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1; private static final long KEEP_ALIVE = 10L; private static final ThreadFactory sThreadFactory = new ThreadFactory() { private final AtomicInteger mCount = new AtomicInteger(); @Override public Thread newThread(Runnable r) { // TODO Auto-generated method stub return new Thread(r, "ImageLoader#" + ()); } }; public static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor( CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE, , new LinkedBlockingDeque<Runnable>(), sThreadFactory);
1. Reasons for using thread pools and handlers.
First of all, it cannot be implemented with ordinary threads. If ordinary threads are used to load pictures, a large number of threads may be generated as the list slides, which is not conducive to the improvement of efficiency. The implementation of Handler directly uses the main thread's Looper to construct the Handler object, which allows ImageLoader to be constructed on non-main threads. In addition, in order to solve the problem of list misalignment caused by View multiplexing, before setting the image for ImageView, we will check whether its url has changed. If it changes, we will no longer set the image for it, which solves the problem of list misalignment.
private Handler mMainHandler = new Handler(()) { @Override public void handleMessage(Message msg) { LoaderResult result = (LoaderResult) ; ImageView imageView = ; (); String uri = (String) (TAG_KEY_URI); if (()) { (); } else { (TAG, "set image bitmap,but url has changed , ignored!"); } } };
Summarize:
The problem of image loading, especially the loading of large numbers of images, has always been a problem for android developers. This article only mentions the most basic solution, which is good for learning.
The above is all the content of this article. I hope it will be helpful to everyone's study and I hope everyone will support me more.