The development of App must involve image loading, image processing, it must be used three picture framework, or choose their own encapsulation. As for the mainstream three-way picture frames, there’s the old ImageLoader and the more popular Glide, Picasso and Fresco. But the tripartite framework will not be covered much in this article.

Glide framework, after all, is the great god and the team spent a lot of energy to develop and maintain the open source framework, their design ideas, performance optimization, code specifications and so on is worth us to learn, before a period of time also studied Glide source code (have to sincerely admire).

Today, will be their own ideas for the image loading ideas, but also borrowed from the open source framework of some good points, the package of an image loading framework – JsLoader. (Github: github.com/shuaijia/Js…) Share it with you.

Article Contents:

preface

For a network request for an image, I’m using HttpUrlConnection, which Android provides natively; When a network image is requested, the child thread is enabled for operation, and the thread pool is used for unified management of threads. Handler is still used for inter-thread communication; When it comes to image loading, you will immediately think of a level 3 image cache (memory-external-network), but HERE is a new idea: level 4 cache. Unlike level 3 cache, memory is divided into two levels, which will be discussed in more detail later.

The purpose of this article is to share with you a picture framework packaging ideas, as to the optimization of the code, such as the use of OkHttp to replace HttpUrlConnection, use RxJava to replace Handler, or other deficiencies, but also hope you can feedback to me, we progress together.

Take a look at the overall flow chart:

The thread pool

Public class MyThreadFactory {private static ThreadPoolExecutor ThreadPoolExecutor =null; Private static int num= runtime.geTruntime ().availableProcessors(); Private static BlockingDeque<Runnable> workQueue=new LinkedBlockingDeque<>(num*50); private static BlockingDeque<> workQueue=new LinkedBlockingDeque<>(num*50); privateMyThreadFactory(){
    }
    public static ThreadPoolExecutor getThreadPoolExecutor() {if(null==threadPoolExecutor){
            threadPoolExecutor=new ThreadPoolExecutor(num*2, num*4, 8, TimeUnit.SECONDS, workQueue, new ThreadPoolExecutor.CallerRunsPolicy());
//            threadPoolExecutor=new ThreadPoolExecutor(1, 1, 8, TimeUnit.SECONDS, workQueue, new ThreadPoolExecutor.CallerRunsPolicy());
        }
        returnthreadPoolExecutor; }}Copy the code

The current class is the management class for a thread pool. Because of the current thread pool, there is no need to create multiple objects throughout the project, using the singleton pattern directly.

The classes that use thread pools in Android are: ThreadPoolExecutor;

ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(int corePoolSize, int maxinumPoolSize, long keepAliveTime, TimeUnit unit, BlockingDeque<Runnable> workQueue, ThreadFactory threadFactory);
Copy the code

Parameters:

  • Int corePoolSize: indicates the number of core threads in the thread pool
  • Int maxinumPoolSize: specifies the maximum number of threads allowed in a thread pool
  • Long keepAliveTime: Specifies the timeout period for a non-core thread, after which a non-core thread will be reclaimed
  • TimeUnit Unit: Indicates the unit of the timeout period of a non-core thread
  • BlockingDeque workQueue: Keeps a list of tasks that need to be performed by the thread pool
  • Thread newThread(Runnable r) Thread newThread(Runnable r)

In the class shown above, we get the number of CPU cores for the phone, num. The number of core threads in the thread pool is twice the number of cpus, and the maximum number of threads is four times the number of CPU cores.

Memory level 1 cache

Private static final HashMap<String,Bitmap> mHardBitmapCache=new LinkedHashMap<String,Bitmap>(M_LINK_SIZE/2,0.75f,true){/** * This method is called when it is put or putAll, and returns by defaultfalse* @param eldest * @ indicates that the oldest data is not removed when addedreturn
	 */
	@Override
	protected boolean removeEldestEntry(Entry<String, Bitmap> eldest) {
		if(size() > M_LINK_SIZE) {// if the size of the map is greater than 30, put the key in mSoftBitmapCache. Bitmap value = diverted. GetValue ();if(value ! = null) { mWeakBitmapCache.put(eldest.getKey(),new SoftReference<Bitmap>(value)); }return true;
		}
		return false; }};Copy the code

That is, the HashMap that holds the location as a strong reference.

Here HashMap uses LinkedHashMap. LinkedHashMap is a subclass of HashMap that stores the insertion order of records. When iterating through a LinkedHashMap, the first record that comes back must have been inserted first. It can also be constructed with parameters, sorted by the number of applications. It’s going to be slower than a HashMap, except if a HashMap has a lot of capacity and a little bit of actual data, it’s going to be slower than a LinkedHashMap, because the speed of a LinkedHashMap is only related to the actual data, not the size. The traversal speed of a HashMap depends on its capacity.

Because LinkedHashMap has memory, the most recently inserted access fits our most recently used rule. But because of its slow traversal speed, we set its capacity to 30 and elements at most.

Override the removeEldestEntry method to place the most recently unused key in mHardBitmapCache (the second level of memory cache) when the map size is greater than 30, thus keeping mHardBitmapCache efficient.

In this case, we store the Url and Bitmap as key-values in the Map. Since LinkedHashMap has few stores and is quick to insert and move, we use strong references to Bitmap.

If the LinkedHashMap contains the image we want, we return the image directly. But note: at this point we think this graph is more frequently used, so we need to move this element out first and read it before adding (this is because the traverse inserted after the map is read first).

mHardBitmapCache.remove(netUrlKey);
mHardBitmapCache.put(netUrlKey,usefulBitmap);
Copy the code

This is the level 1 cache of memory.

Memory level 2 cache

If the LinkedHashMap in memory does not get the image we want, we look it up in the secondary cache.

private static Map<String, SoftReference<Bitmap>> mWeakBitmapCache = new ConcurrentHashMap<String, SoftReference<Bitmap>>(
            M_LINK_SIZE / 2);
Copy the code

This is where ConcurrentHashMap is used, which features thread safety, high concurrency, and large storage. Because of the large amount of storage, we need to use soft references to bitmaps when storing them.

If the map contains the desired image, the soft reference is fetched first, and the Bitmap object is returned from the soft reference. Move it to the level 1 cache.

Memory read the overall code is as follows:

/** * The operation defined here performs the task of obtaining the image object from an in-memory Map ** @param netUrlKey ** @param netUrlKey ** @param netUrlKey ** @param netUrlKey ** @param netUrlKeyreturn
     */
    public static Bitmap getBitmapFromRAM(String netUrlKey){

        if(mHardBitmapCache.containsKey(netUrlKey)){

            Bitmap usefulBitmap=mHardBitmapCache.get(netUrlKey);
            if(null! =usefulBitmap){// Return the Bitmap object mhardBitMapCache.remove (netUrlKey) if there is an in-memory Bitmap. mHardBitmapCache.put(netUrlKey,usefulBitmap);return usefulBitmap;

            }else{mhardBitMapCache.remove (netUrlKey);} mhardbitMapCache.remove (netUrlKey);returnnull; }}else{// If the strong reference does not contain the corresponding key, then do the lookup in the soft referenceif(mWeakBitmapCache.containsKey(netUrlKey)){
                SoftReference<Bitmap> usefulSoftBitmap=mWeakBitmapCache.get(netUrlKey);
                if(null! =usefulSoftBitmap){// Obtain the corresponding Bitmap object from the software application Bitmap usefulBitmap = usefulSoftBitmap.get();if(null! =usefulBitmap){mhardBitMapCache.put (netUrlKey,usefulBitmap);return usefulBitmap;
                    }elseMweakbitmapcache.remove (netUrlKey); mWeakBitMapCache.remove (netUrlKey);returnnull; }}elseMweakbitmapcache.remove (netUrlKey); mWeakBitMapCache.remove (netUrlKey);returnnull; }}else{// If the soft reference does not include the key, then determine whether the resource image exists in the SD cardreturnnull; }}}Copy the code

Special declaration: the image will be compressed before being stored in memory.

SD card cache

If there is no image in memory, go to the file to find:

/** * Get the location of the saved data path ** @param netUrlorPath * @return
	 */
	private static String getSavedPath(String netUrlorPath) {

		String savedPath = null;
		if(StorageUtil isPhoneHaveSD ()) {/ / created to SD kagan directory to the path of the File object File fileBySD = new File (StorageUtil. GetPathBySD ()); / / create SD kagan directory for the current application package called folder File object, and verify whether there is the current directory File fileBySDSon = new File (fileBySD, PackageUtil getAppPackageName ()); // File fileBySDSon=new File(fileBySD,"AA");
			if(fileBySDSon.exists()) { String md5Url = EncryptUtil.md5(netUrlorPath); File imageFile = new File(fileBySDSon, URLEncoder.encode(md5Url));if(imageFile. The exists ()) {/ / picture file object exists for current image object corresponding path savedPath = imageFile. GetAbsolutePath (); }else {
					returnnull; }}else {
				returnnull; }}else{/ / created to Cache the root directory to the path of the File object File fileByCache = new File (StorageUtil. GetPathBycache ()); / / create SD kagan directory for the current application package called folder File object, and verify whether there is the current directory File fileByCacheSon = new File (fileByCache, PackageUtil getAppPackageName ()); // File fileByCacheSon=new File(fileByCache,"AA");
			if(fileByCacheSon.exists()) { String md5Url = EncryptUtil.md5(netUrlorPath); File imageFile = new File(fileByCacheSon, URLEncoder.encode(md5Url));if(imageFile. The exists ()) {/ / picture file object exists for current image object corresponding path savedPath = imageFile. GetAbsolutePath (); }else {
					returnnull; }}else {
				returnnull; }}return savedPath;

	}
Copy the code

The above code is based on the image URL to get the image in the file path.

All the cached images will be saved in the folder of the package name in the file named with the MD5 value of the URL. If this file is found, the file path will be returned.

/** * The path passed in contains a Bitmap object. Return the Bitmap object if it exists, otherwise return null * * @param saveTime * @param netUrl * Network The network path of the image as the file name * @return
	 */
	public static Bitmap getBitmapFromSD(long saveTime, String netUrl) {

		long nativeSaveTime = saveTime > 0 ? saveTime : DATA_DEFAULT_SAVETIME;
		long actualSaveTime = 0L;
		if (null == netUrl) {
			return null;
		}
		String imageSavePath = getSavedPath(netUrl);
	//	System.out.println("Path to stored images: :" + imageSavePath);
		if (null == imageSavePath) {
			return null;
		}
		File imageFile = new File(imageSavePath);
		if(! imageFile.exists()) { // throw new StructException("The required file does not exist!");
			return null;
		}
		actualSaveTime = System.currentTimeMillis() - imageFile.lastModified();
		if (actualSaveTime > nativeSaveTime) {
			imageFile.delete();
			//System.out.println("The file has timed out!");
			returnnull; } /** * This is the logic that gets the file object when it exists, and generates the Bitmap object and returns */ // Bitmap sdBitmap= bitmapfactory.decodefile (imageSavePath); OOM // system.out.println (system.out.println)"Links to saved images:" + imageSavePath);
		Bitmap sdBitmap = ImageUtil.getCompressBitmapBYScreen(imageSavePath);
		return sdBitmap;

	}
Copy the code

If we find the image we need in the file, we will get the file path. However, we have a valid time for the file, after which it is considered timeout and null is returned, otherwise the file is read. Compress the image according to the image path and the default screen resolution of the current phone and then return.

If the image is in the file, the image will be transplanted to memory to increase the priority, and the image will be placed in both levels of memory.

Network access

None of the above pictures, that can only be obtained from the network!

To determine whether HTTP or HTTPS, use HttpUrlConnection and HttpsUrlConnection, respectively. They have similar code, so I only post one of them.

    public static InputStream getHttpIOByGet(String netUrl) throws IOException {

//        System.out.println("Links to the Web:"+netUrl);

        URL url = new URL(netUrl);
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        conn.setRequestMethod("GET");
        conn.setConnectTimeout(5000);
        int code = conn.getResponseCode();
//        System.out.println(Return code: :+code);
        if (code == 200) {
            InputStream is = conn.getInputStream();
            return is;
        }else{
            returnnull; }}Copy the code

A return code of 200 indicates success and returns the input stream, otherwise null is returned.

Bitmap bitmap= BitmapFactory.decodeStream(inputStream);
Copy the code

After you get the input stream, use the above code to get the Bitmap object, and you can see why.

After the image is obtained, it is successively stored in THE SD card and memory, because the operation is good or good, it is carried out in the child thread.

new Thread(){
    @Override
    public void runFileutil.putbitmaptosd (netUrl, finalThreeCacheBitmap); fileutil.putBitMap (netUrl, finalThreeCacheBitmap); PutBitmapToRAM (netUrl, finalThreeCacheBitmap); } }.start();Copy the code

Image compression

Here I want to introduce the following image compression: because the image load is easy to cause OOM, so the image compression processing is particularly important.

Provides centralized compression:

  • Compress according to the expected size
  • Compress according to desired size
  • Compress the image according to the default screen resolution of the current phone

I’m not going to post the code here, but you can check it out on my Github. Github.com/shuaijia/Js…

use

1. Add dependencies

allprojects {
  repositories {
    ...
    maven { url 'https://www.jitpack.io' }
  }
}

dependencies {
  compile 'com. Making. Shuaijia: JsImageLoader: v1.0'
}
Copy the code

2. Add permissions

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
Copy the code

3. Inherit JsApplication

4, the request

JsLoader.with(this)
    .load("Https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=699359866, 1092793192 & FM = 27 & gp = 0. JPG")
    .defaultImg(R.mipmap.default)
    .errorImg(R.mipmap.error)
    .into(imageView);
Copy the code

Due to my limited level, there are inevitably wrong or insufficient places, I hope you can put forward, we make progress together.

For more exciting content, please follow my wechat official account — Android Vehicles