Full-resolution Android open source picture Framework Universal-image-loader_android

Source: Internet
Author: User
Tags garbage collection


I believe we usually do Android applications, how much will be contacted to load pictures asynchronously, or load a large number of pictures of the problem, and load pictures we often encounter many problems, such as the image of the disorder, oom and other issues, for beginners, these problems will be more difficult to solve, So there are a lot of open source image loading framework came into being, more famous is Universal-image-loader, I believe many friends have heard or used this powerful picture loading framework, today this article is the framework of the basic introduction and use, It is mainly to help those friends who have not used the framework. This project exists on GitHub Https://github.com/nostra13/Android-Universal-Image-Loader, we can first look at the features of this open source library


    • Multi-threaded download pictures, pictures can come from network, file system, project folder assets and drawable medium
    • Supports random configuration imageloader, such as thread pool, picture downloader, memory cache policy, HDD caching policy, picture display options, and other configurations
    • Support picture memory cache, file system cache or SD card cache
    • Support the monitoring of picture downloading process
    • Crop the bitmap based on the size of the control (ImageView), reducing bitmap memory consumption
    • Better control of the image loading process, such as suspend picture loading, restart loading pictures, generally used in Listview,gridview
    • Suspend loading picture during dynamic process
    • To load a picture when you stop sliding
    • For loading a picture under a slower network.


Of course, the characteristics listed above may be incomplete, to understand some other features can only be found through our use, and then we will look at the open Source Library of simple use.



Create a new Android project, download the jar package and add it to the Engineering Libs directory.
Create a new MyApplication inheritance application, and in OnCreate () The Imageloader configuration parameters are created and initialized to the Imageloader code as follows:


Package Com.example.uil; 
 
Import Com.nostra13.universalimageloader.core.ImageLoader; 
Import com.nostra13.universalimageloader.core.ImageLoaderConfiguration; 
 
Import android.app.Application; 
 
public class MyApplication extends application { 
 
 @Override public 
 void OnCreate () { 
  super.oncreate (); 
 
  Creates a default Imageloader configuration parameter 
  imageloaderconfiguration configuration = imageloaderconfiguration 
    . Createdefault ( this); 
   
  Initialize imageloader with configuration. 
  Imageloader.getinstance (). init (configuration); 
 } 
 
} 


Imageloaderconfiguration is the Imageloader configuration parameter of the picture loader, using the builder mode, where the Createdefault is used directly () method to create a default imageloaderconfiguration, and of course we can set our own imageloaderconfiguration and set the following


File cacheDir = StorageUtils.getCacheDirectory(context); 
ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(context) 
  .memoryCacheExtraOptions(480, 800) // default = device screen dimensions 
  .diskCacheExtraOptions(480, 800, CompressFormat.JPEG, 75, null) 
  .taskExecutor(...) 
  .taskExecutorForCachedImages(...) 
  .threadPoolSize(3) // default 
  .threadPriority(Thread.NORM_PRIORITY - 1) // default 
  .tasksProcessingOrder(QueueProcessingType.FIFO) // default 
  .denyCacheImageMultipleSizesInMemory() 
  .memoryCache(new LruMemoryCache(2 * 1024 * 1024)) 
  .memoryCacheSize(2 * 1024 * 1024) 
  .memoryCacheSizePercentage(13) // default 
  .diskCache(new UnlimitedDiscCache(cacheDir)) // default 
  .diskCacheSize(50 * 1024 * 1024) 
  .diskCacheFileCount(100) 
  .diskCacheFileNameGenerator(new HashCodeFileNameGenerator()) // default 
  .imageDownloader(new BaseImageDownloader(context)) // default 
  .imageDecoder(new BaseImageDecoder()) // default 
  .defaultDisplayImageOptions(DisplayImageOptions.createSimple()) // default 
  .writeDebugLogs() 
  .build(); 


These are all the options configured, we do not need each in the project to set their own, the general use of Createdefault () created by the imageloaderconfiguration can be used, The Imageloader init () method is then invoked to pass the imageloaderconfiguration parameter in, imageloader using the single case pattern.



Configure the Android manifest file


<manifest> 
 <uses-permission android:name= "Android.permission.INTERNET"/> 
 <!--Include Next Permission if you want to allow UIL to cache images on SD card--> 
 <uses-permission android:name= "Android.permis Sion. Write_external_storage "/> ... 
 <application android:name= "MyApplication" > ... 
 </application> 
</manifest> 


Next we can load the picture, and first we define the activity's layout file.


<?xml version= "1.0" encoding= "Utf-8"?> <framelayout xmlns:android= 
"http://schemas.android.com/apk/" Res/android " 
 android:layout_width=" fill_parent " 
 android:layout_height=" fill_parent "> 
 
 < ImageView 
  android:layout_gravity= "center" 
  android:id= "@+id/image" 
  android:src= "@drawable/ic_empty" 
  android:layout_width= "wrap_content" 
  android:layout_height= "wrap_content"/> 
 
</framelayout > 


There is only one imageview, very simple, next we will load the picture, we will find that Imagelader provides a few pictures to load the method, mainly is these several displayimage (), LoadImage (), Loadimagesync (), The Loadimagesync () method is synchronous, android4.0 has an attribute, network operation cannot be in the main thread, so Loadimagesync () method we do not use
.
loadimage () load picture



We first use the Imageloader LoadImage () method to load the network picture


 final ImageView Mimageview = (imageview) Findviewbyid (r.id.image); String IMAGEURL = "https://lh6.googleusercontent.com/-55osAWw3x0Q/URquUtcFr5I/AAAAAAAAAbs/rWlj1RUKrYI/s1024/A% 
   
  252520photographer.jpg "; Imageloader.getinstance (). LoadImage (ImageUrl, New Imageloadinglistener () {@Override public void Onloadingsta RTed (String Imageuri, view view) {} @Override public void onloadingfailed (string imageuri, view VI EW, Failreason failreason) {} @Override public void Onloadingcomplete (String Imageuri, View 
   View, Bitmap loadedimage) {mimageview.setimagebitmap (loadedimage); 
@Override public void onloadingcancelled (String imageuri, view view) {}});
final ImageView mImageView = (ImageView) findViewById(R.id.image); 
  String imageUrl = "https://lh6.googleusercontent.com/-55osAWw3x0Q/URquUtcFr5I/AAAAAAAAAbs/rWlj1RUKrYI/s1024/A%252520Photographer.jpg"; 
    
  ImageLoader.getInstance().loadImage(imageUrl, new ImageLoadingListener() { 
     
   @Override
   public void onLoadingStarted(String imageUri, View view) { 
      
   } 
     
   @Override
   public void onLoadingFailed(String imageUri, View view, 
     FailReason failReason) { 
      
   } 
     
   @Override
   public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) { 
    mImageView.setImageBitmap(loadedImage); 
   } 
     
   @Override
   public void onLoadingCancelled(String imageUri, View view) { 
      
   } 
  }); 


The URL and Imageloaderlistener of the incoming picture, set Loadedimage to ImageView on the callback method Onloadingcomplete (), If you think the incoming imageloaderlistener is too complicated, we can use the Simpleimageloadinglistener class, which provides an empty implementation of the Imageloaderlistener interface method, using the default adapter pattern


Final ImageView Mimageview = (imageview) Findviewbyid (r.id.image); 
  String IMAGEURL = "https://lh6.googleusercontent.com/-55osAWw3x0Q/URquUtcFr5I/AAAAAAAAAbs/rWlj1RUKrYI/s1024/A% 252520photographer.jpg "; 
   
  Imageloader.getinstance (). LoadImage (ImageUrl, New Simpleimageloadinglistener () { 
 
   @Override public 
   Void Onloadingcomplete (String Imageuri, view view, 
     Bitmap loadedimage) { 
    super.onloadingcomplete (Imageuri, view, Loadedimage); 
    Mimageview.setimagebitmap (loadedimage); 
   } 
    
  ); 


If we want to specify the size of the picture what to do, this is OK, initialize a ImageSize object, specify the width and height of the picture, the following code


Final ImageView Mimageview = (imageview) Findviewbyid (r.id.image); 
  String IMAGEURL = "https://lh6.googleusercontent.com/-55osAWw3x0Q/URquUtcFr5I/AAAAAAAAAbs/rWlj1RUKrYI/s1024/A% 252520photographer.jpg "; 
   
  ImageSize mimagesize = new ImageSize (m); 
   
  Imageloader.getinstance (). LoadImage (ImageUrl, Mimagesize, New Simpleimageloadinglistener () { 
 
   @Override 
   public void Onloadingcomplete (String imageuri, view view, 
     Bitmap loadedimage) { 
    Super.onloadingcomplete ( Imageuri, view, loadedimage); 
    Mimageview.setimagebitmap (loadedimage); 
   } 
    
  ); 


The above is just very simple to use Imageloader to load the network image, in the actual development, we do not use, then how do we usually use it? We will use Displayimageoptions, he can configure some of the image display options, such as pictures in the load ImageView display pictures, whether the need to use the memory cache, the need to use file caching and so on, we can choose the configuration as follows


Displayimageoptions options = new Displayimageoptions.builder () 
  . showimageonloading (r.drawable.ic_stub)// Resource or drawable 
  . Showimageforemptyuri (r.drawable.ic_empty)//resource or drawable 
  . Showimageonfail ( R.DRAWABLE.IC_ERROR)//resource or drawable 
  . Resetviewbeforeloading (FALSE)//default 
  . delaybeforeloading ( 1000) 
  . Cacheinmemory (FALSE)//default 
  . Cacheondisk (FALSE)//default 
  . Preprocessor (...). 
  Postprocessor (...) 
  . Extrafordownloader (...) 
  . Considerexifparams (FALSE)//default 
  . Imagescaletype (imagescaletype.in_sample_power_of_2)//default 
  . Bitmapconfig (Bitmap.Config.ARGB_8888)//default 
  . Decodingoptions (...) 
  . Displayer (New Simplebitmapdisplayer ())//default 
  . Handler (new handler ())//default 
  . Build (); 


We'll modify the code slightly below


final ImageView mImageView = (ImageView) findViewById(R.id.image); 
  String imageUrl = "https://lh6.googleusercontent.com/-55osAWw3x0Q/URquUtcFr5I/AAAAAAAAAbs/rWlj1RUKrYI/s1024/A%252520Photographer.jpg"; 
    
  ImageLoader.getInstance().loadImage(imageUrl, new SimpleImageLoadingListener(){ 
  
   @Override
   public void onLoadingComplete(String imageUri, View view, 
     Bitmap loadedImage) { 
    super.onLoadingComplete(imageUri, view, loadedImage); 
    mImageView.setImageBitmap(loadedImage); 
   } 
     
  }); 


We used displayimageoptions to configure some of the options for displaying pictures, where I added a cached image to the file system in memory so that we don't have to worry about loading pictures from the network every time. However, some options in the Displayimageoptions option are not valid for the LoadImage () method, such as showimageonloading, Showimageforemptyuri, etc.



DisplayImage () Load picture



Next we'll look at another way to load the network picture DisplayImage (), the following code


Final ImageView Mimageview = (imageview) Findviewbyid (r.id.image); 
  String IMAGEURL = "https://lh6.googleusercontent.com/-55osAWw3x0Q/URquUtcFr5I/AAAAAAAAAbs/rWlj1RUKrYI/s1024/A% 252520photographer.jpg "; 
   
  Display the configuration of a picture 
  displayimageoptions options = new Displayimageoptions.builder () 
    . showimageonloading ( r.drawable.ic_stub) 
    . Showimageonfail (r.drawable.ic_error). 
    Cacheinmemory (True) 
    . Cacheondisk (True) 
    . Bitmapconfig (Bitmap.Config.RGB_565) 
    . Build (); 
   
  Imageloader.getinstance (). DisplayImage (ImageUrl, Mimageview, Options); 

What if we want to specify the size of the image, this is also easy to do, initialize an ImageSize object, specify the width and height of the image, the code is as follows:


final ImageView mImageView = (ImageView) findViewById(R.id.image); 
  String imageUrl = "https://lh6.googleusercontent.com/-55osAWw3x0Q/URquUtcFr5I/AAAAAAAAAbs/rWlj1RUKrYI/s1024/A%252520Photographer.jpg"; 
    
  ImageSize mImageSize = new ImageSize(100, 100); 
    
  ImageLoader.getInstance().loadImage(imageUrl, mImageSize, new SimpleImageLoadingListener(){ 
  
   @Override
   public void onLoadingComplete(String imageUri, View view, 
     Bitmap loadedImage) { 
    super.onLoadingComplete(imageUri, view, loadedImage); 
    mImageView.setImageBitmap(loadedImage); 
   } 
     
  }); 


From the above code, we can see that using displayimage () is much more convenient than using LoadImage () and does not need to add Imageloadinglistener interface, we do not need to manually set ImageView display bitmap objects, Directly to the ImageView as a parameter passed to DisplayImage () on the line, the picture shows the configuration options, we added a picture loaded ImageView the picture shown above, as well as the picture load error displayed in the picture, the effect is as follows, just beginning to show Ic_ Stub picture, if the picture loads successfully displays the picture, the load produces the error display Ic_error






This method is easy to use, and using the DisplayImage () method He will be based on the size of the control and imagescaletype from the dynamic cropping picture, we modify the MyApplication, turn on log printing


public class MyApplication extends application { 
 
 @Override public 
 void OnCreate () { 
  super.oncreate (); 
 
  Create the default imageloader configuration parameter 
  imageloaderconfiguration configuration = new Imageloaderconfiguration.builder (this) 
  . Writedebuglogs ()//print log information 
  . Build (); 
   
   
  Initialize imageloader with configuration. 
  Imageloader.getinstance (). init (configuration); 
 } 
 
} 


Let's take a look at the log information loaded by the picture






The first message tells us to start loading the picture, print out the URL of the picture and the maximum width and height of the picture, the width of the picture by default is the height of the device, of course, if we know the size of the picture, we can also set this size, In the imageloaderconfiguration option memorycacheextraoptions (int maximagewidthformemorycache, int Maximageheightformemorycache)
The second message shows that the image we loaded comes from the network
The third message shows that the original size of the picture is 1024 x 682 has been cropped to 341
Fourth display picture added to the memory cache, I did not add to the SD card, so did not join the file cache log



When we load the network picture, often need to display the picture download progress demand, Universal-image-loader of course also provides such function, only need to be in displayimage () method to pass in the Imageloadingprogresslistener interface, the code is as follows


Imageloader.displayimage (IMAGEURL, Mimageview, Options, New Simpleimageloadinglistener (), New Imageloadingprogresslistener () { 
    
   @Override public 
   void Onprogressupdate (String imageuri, view view, int Current, 
     int total) { 
     
   } 
  }); 


Since the method with Imageloadingprogresslistener parameters in the DisplayImage () method has a imageloadinglistener parameter, so I'm here to direct new A simpleimageloadinglistener, then we can get the picture's loading progress in the callback method Onprogressupdate ().



Load pictures from other sources



Use Universal-image-loader frame not only can load network picture, also can load the picture of SD card, Content provider etc., use also very simple, just change the URL of the picture to a little changed on line, the following is the picture of loading file system


Display the configuration of a picture 
  displayimageoptions options = new Displayimageoptions.builder () 
    . showimageonloading ( r.drawable.ic_stub) 
    . Showimageonfail (r.drawable.ic_error). 
    Cacheinmemory (True) 
    . Cacheondisk (True) 
    . Bitmapconfig (Bitmap.Config.RGB_565) 
    . Build (); 
   
  Final ImageView Mimageview = (imageview) Findviewbyid (r.id.image); 
  String ImagePath = "/mnt/sdcard/image.png"; 
  String imageUrl = Scheme.FILE.wrap (ImagePath);  String imageUrl = "http://img.my.csdn.net/uploads/201309/01/1378037235_7476.jpg"; 
   
  Imageloader.displayimage (IMAGEURL, Mimageview, Options); 


And, of course, it comes from Content provider,drawable,assets, and it's easy to use, and we just need to add scheme packages to each image source (except for content provider), Then, as the URL of the picture is passed into the Imageloader, the Universal-image-loader frame gets the input stream according to different scheme


The picture comes from 
  the content provider String Contentprividerurl = "CONTENT://MEDIA/EXTERNAL/AUDIO/ALBUMART/13"; 
   
  The picture comes from ASSETS 
  String assetsurl = Scheme.ASSETS.wrap ("Image.png"); 
   
  The picture comes from 
  String drawableurl = Scheme.DRAWABLE.wrap ("R.drawable.image"); 



Girdview,listview Load Picture



I believe most people use Gridview,listview to display a lot of pictures, and when we slide Gridview,listview quickly, we want to stop the image loading and load the picture of the current interface when Gridview,listview stops sliding. , this framework, of course, provides this functionality and is simple to use, and it provides a Pauseonscrolllistener class to control the Listview,gridview sliding process to stop loading pictures, which use proxy mode
[Java] View plain copy on the code to see a piece of coding derived from my Code slice
Listview.setonscrolllistener (New Pauseonscrolllistener (Imageloader, Pauseonscroll, pauseonfling));
Gridview.setonscrolllistener (New Pauseonscrolllistener (Imageloader, Pauseonscroll, pauseonfling));
The first parameter is our picture loading object Imageloader, the second is to control whether the picture is suspended during the slide, if you need to pause to pass true, and the third parameter controls the swipe of the sliding interface when the picture is loaded



OutOfMemoryError



Although this framework has a good caching mechanism to effectively avoid the generation of oom, in general, the probability of generating oom is relatively small, but can not guarantee that outofmemoryerror never happen, this framework for the OutOfMemoryError do a simple catch To ensure that our program encounters Oom without being crash, but how do we improve if we use the framework often oom?
Reduce the number of threads in the thread pool, configure in Imageloaderconfiguration (. threadpoolsize), recommend configuration 1-5
Configure Bitmapconfig as Bitmap.Config.RGB_565 in the Displayimageoptions option because the default is argb_8888, and using rgb_565 consumes twice times less memory than using argb_8888
The memory cache for the configuration picture in Imageloaderconfiguration is MemoryCache (new Weakmemorycache ()) or does not use the memory cache
Set. Imagescaletype (Imagescaletype.in_sample_int) or Imagescaletype (ImageScaleType.EXACTLY) in the displayimageoptions option.
Through these, I believe you have a very good understanding of the use of the Universal-image-loader framework, we use the framework as far as possible to use the DisplayImage () method to load the picture, LoadImage () is to recall the picture object to the Onloadingcomplete () method of the Imageloadinglistener interface, which requires us to manually set it to the ImageView above, the DisplayImage () method, For the ImageView object, the weak references is used to facilitate the garbage collector to reclaim ImageView objects, and if we want to load a fixed-size picture, we need to pass a LoadImage object using the ImageSize () method. The DisplayImage () method is based on the measured value of the ImageView object, or the value set by Android:layout_width and Android:layout_height, or Android:maxwidth and/ Or android:maxheight the picture by the value you set.



Memory Cache



First, let's take a look at what is a strong reference and what is a weak reference?
A strong reference is the creation of an object and assigning it to a reference variable, and a strong reference to a reference variable will never be garbage collected. Even if there is not enough memory to report oom and not be recycled by the garbage collector, we new objects are strong references
Weak references are implemented by the WeakReference class, which is highly uncertain, and if the garbage collector scans for objects with WeakReference, it reclaims the memory



Now let's see what memory caching policies Universal-image-loader have
1. Only use the strong reference cache
Lrumemorycache (This class is the open source framework default memory cache class, caching is the strong reference of bitmap, I will analyze this class from the source above)
2. Caching with strong references combined with weak references is
Usingfreqlimitedmemorycache (if the total number of cached pictures exceeds the limit, delete the bitmap with the least frequency)
Lrulimitedmemorycache (This is also used LRU algorithm, and Lrumemorycache is different, he caches is bitmap weak reference)
Fifolimitedmemorycache (first-out cache policy, when exceeding the set value, first delete the first to add cache bitmap)
Largestlimitedmemorycache (deletes the largest bitmap object first) when the cache limit value is exceeded
Limitedagememorycache (when bitmap is added to the cache time exceeds our set value, delete it)
3. Use only weak reference caching
Weakmemorycache (The total size of this class cache bitmap is not limited, the only deficiency is instability, cached images are easily recycled)
It describes all of the memory cache classes provided by Universal-image-loader, and of course we can use our own memory cache classes, and we'll see how to add these memory caches to our project. We only need to configure Imageloaderconfiguration.memorycache (...), as follows


Imageloaderconfiguration configuration = new Imageloaderconfiguration.builder (this) 
  . MemoryCache (New Weakmemorycache ()) 
  . Build (); 


Now let's analyze the source code for this class Lrumemorycache.


Package com.nostra13.universalimageloader.cache.memory.impl;
  
Import android.graphics.Bitmap;
Import com.nostra13.universalimageloader.cache.memory.MemoryCacheAware;
  
Import java.util.Collection;
Import java.util.HashSet;
Import java.util.LinkedHashMap;
Import java.util.Map;
  
/**
 * A cache that holds strong references to a limited number of Bitmaps. Each time a Bitmap is accessed, it is moved to
 * the head of a queue. When a Bitmap is added to a full cache, the Bitmap at the end of that queue is evicted and may
 * become eligible for garbage collection.<br />
 * <br />
 * <b>NOTE:</b> This cache uses only strong references for stored Bitmaps.
 *
 * @author Sergey Tarasevich (nostra13[at]gmail[dot]com)
 * @since 1.8.1
 */
Public class LruMemoryCache implements MemoryCacheAware<String, Bitmap> {
  
 Private final LinkedHashMap<String, Bitmap> map;
  
 Private final int maxSize;
 /** Size of this cache in bytes */
 Private int size;
  
 /** @param maxSize Maximum sum of the sizes of the Bitmaps in this cache */
 Public LruMemoryCache(int maxSize) {
  If (maxSize <= 0) {
   Throw new IllegalArgumentException("maxSize <= 0");
  }
  this.maxSize = maxSize;
  This.map = new LinkedHashMap<String, Bitmap>(0, 0.75f, true);
 }
  
 /**
  * Returns the Bitmap for {@code key} if it exists in the cache. If a Bitmap was returned, it is moved to the head
  * of the queue. This returns null if a Bitmap is not cached.
  */
 @Override
 Public final Bitmap get(String key) {
  If (key == null) {
   Throw new NullPointerException("key == null");
  }
  
  Synchronized (this) {
   Return map.get(key);
  }
 }
  
 /** Caches {@code Bitmap} for {@code key}. The Bitmap is moved to the head of the queue. */
 @Override
 Public final boolean put(String key, Bitmap value) {
  If (key == null || value == null) {
   Throw new NullPointerException("key == null || value == null");
  }
  
  Synchronized (this) {
   Size += sizeOf(key, value);
   Bitmap previous = map.put(key, value);
   If (previous != null) {
    Size -= sizeOf(key, previous);
   }
  }
  
  trimToSize(maxSize);
  Return true;
 }
  
 /**
  * Remove the eldest entries until the total of remaining entries is at or below the requested size.
  *
  * @param maxSize the maximum size of the cache before returning. May be -1 to evict even 0-sized elements.
  */
 Private void trimToSize(int maxSize) {
  While (true) {
   String key;
   Bitmap value;
   Synchronized (this) {
    If (size < 0 || (map.isEmpty() && size != 0)) {
     Throw new IllegalStateException(getClass().getName() + ".sizeOf() is reporting inconsistent results!");
    }
  
    If (size <= maxSize || map.isEmpty()) {
     Break;
    }
  
    Map.Entry<String, Bitmap> toEvict = map.entrySet().iterator().next();
    If (toEvict == null) {
     Break;
    }
    Key = toEvict.getKey();
    Value = toEvict.getValue();
    Map.remove(key);
    Size -= sizeOf(key, value);
   }
  }
 }
  
 /** Removes the entry for {@code key} if it exists. */
 @Override
 Public final void remove(String key) {
  If (key == null) {
   Throw new NullPointerException("key == null");
  }
  
  Synchronized (this) {
   Bitmap previous = map.remove(key);
   If (previous != null) {
    Size -= sizeOf(key, previous);
   }
  }
 }
  
 @Override
 Public Collection<String> keys() {
  Synchronized (this) {
   Return new HashSet<String>(map.keySet());
  }
 }
  
 @Override
 Public void clear() {
  trimToSize(-1); // -1 will evict 0-sized elements
 }
  
 /**
  * Returns the size {@code Bitmap} in bytes.
  * <p/>
  * An entry's size must not change while it is in the cache.
  */
 Private int sizeOf(String key, Bitmap value) {
  Return value.getRowBytes() * value.getHeight();
 }
  
 @Override
 Public synchronized final String toString() {
  Return String.format("LruCache[maxSize=%d]", maxSize);
 }
}


We can see in this class that a linkedhashmap is maintained, and we can see in the Lrumemorycache constructor that we have set a maximum value for the cached picture maxsize and instantiate Linkedhashmap, The third parameter from the Linkedhashmap constructor, Ture, indicates that it is sorted in the order of access.
Let's look at adding Bitmap to the Lrumemorycache method put (String key, Bitmap value), line 61st, SizeOf () is the byte count for each picture, the size is the total size of the current cache Bitmap, If the key is cached before bitmap, we need to reduce the previous bitmap, then look at the TrimToSize () method, we look directly at line 86, if the total number of bitmap currently cached is less than the set value maxsize, do not do any processing, If the total number of bitmap in the current cache is greater than maxsize, delete the first element in the Linkedhashmap and the size to subtract the corresponding byte from the bitmap
We can see that the cache class is simpler, the logic is clearer, if you want to know the logic of other memory caching, you can analyze its source code, here I simply say fifolimitedmemorycache implementation logic, The class uses the HashMap to cache bitmap weak references, and then uses LinkedList to save the strong references to the bitmap that were successfully added to the Fifolimitedmemorycache. If the total number of bitmap added to the fifolimitedmemorycache exceeds the defined value, the first element of the LinkedList is deleted directly, so the first-come out caching strategy is implemented, and the other caches are similar and are interesting to see.



Hard disk Cache



The next step is to analyze the hard disk caching strategy, this framework also provides several common caching strategies, of course, if you do not feel that they meet your requirements, you can also expand their


    • Filecountlimiteddisccache (You can set the number of cached pictures, when more than the set value, delete the first file added to the hard disk)
    • Limitedagedisccache (set file to survive for the longest time, when this value is exceeded, delete the file)
    • Totalsizelimiteddisccache (sets the maximum cache bitmap, when this value is exceeded, deletes the file first added to the hard drive)
    • Unlimiteddisccache (This cache class does not have any restrictions)


Below we will analyze and analyze the source code implementation of Totalsizelimiteddisccache


entry.getValue();
     } else {
      Long lastValueUsage = entry.getValue();
      If (lastValueUsage < oldestUsage) {
       oldestUsage = lastValueUsage;
       mostLongUsedFile = entry.getKey();
      }
     }
    }
   }
  
   Int fileSize = 0;
   If (mostLongUsedFile != null) {
    If (mostLongUsedFile.exists()) {
     fileSize = getSize(mostLongUsedFile);
     If (mostLongUsedFile.delete()) {
      lastUsageDates.remove(mostLongUsedFile);
     }
    } else {
     lastUsageDates.remove(mostLongUsedFile);
    }
   }
   Return fileSize;
  }
  
  /**
   * Abstract method to get the file size
   * @param file
   * @return
   */
  Protected abstract int getSize(File file);
}


In the constructor method, line 69th has a method Calculatecachesizeandfillusagemap () that calculates the file size of the Cachedir and adds the last modification time of the file and file to the map
Then you add the file to the hard disk cache by putting (), in 106 rows to determine the total number of cache current files plus the size of the file that is about to be added to the cache exceeds the cache setting, if you exceed the execution Removenext () method, then look at the implementation of this method, 150-167 to find the first file to join the hard drive, 169-180 to remove it from the file hard disk, and return the size of the file, delete the successful member variable cachesize need to reduce the size of the file.
Filecountlimiteddisccache This class implementation logic is the same as Totalsizelimiteddisccache, except that the GetSize () method, which returns 1, is represented as a file number of 1, and the latter returns the size of the file.
When I finished writing this article, I found that Filecountlimiteddisccache and Totalsizelimiteddisccache in the latest source code has been deleted, joined the Lrudisccache, because I was before the source code, So I also do not change, everyone if you want to know Lrudisccache can go to see the latest source code, I do not introduce here, fortunately the memory cache unchanged, the following analysis of the latest source code in the section, we can not configure their own hard disk caching strategy, Just use the defaultconfigurationfactory.
Let's look at the Creatediskcache () method defaultconfigurationfactory this class


/******************************************************************************* 
 * Copyright 2011-2013 Sergey Tarasevich 
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); 
 * you may not use this file except in compliance with the License. 
 * You may obtain a copy of the License at 
 * 
 * http://www.apache.org/licenses/LICENSE-2.0 
 * 
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS, 
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 * See the License for the specific language governing permissions and 
 * limitations under the License. 
 *******************************************************************************/
package com.nostra13.universalimageloader.cache.disc.impl; 
  
import com.nostra13.universalimageloader.cache.disc.LimitedDiscCache; 
import com.nostra13.universalimageloader.cache.disc.naming.FileNameGenerator; 
import com.nostra13.universalimageloader.core.DefaultConfigurationFactory; 
import com.nostra13.universalimageloader.utils.L; 
  
import java.io.File; 
  
/** 
 * Disc cache limited by total cache size. If cache size exceeds specified limit then file with the most oldest last 
 * usage date will be deleted. 
 * 
 * @author Sergey Tarasevich (nostra13[at]gmail[dot]com) 
 * @see LimitedDiscCache 
 * @since 1.0.0 
 */
public class TotalSizeLimitedDiscCache extends LimitedDiscCache { 
  
 private static final int MIN_NORMAL_CACHE_SIZE_IN_MB = 2; 
 private static final int MIN_NORMAL_CACHE_SIZE = MIN_NORMAL_CACHE_SIZE_IN_MB * 1024 * 1024; 
  
 /** 
  * @param cacheDir  Directory for file caching. <b>Important:</b> Specify separate folder for cached files. It's 
  *      needed for right cache limit work. 
  * @param maxCacheSize Maximum cache directory size (in bytes). If cache size exceeds this limit then file with the 
  *      most oldest last usage date will be deleted. 
  */
 public TotalSizeLimitedDiscCache(File cacheDir, int maxCacheSize) { 
  this(cacheDir, DefaultConfigurationFactory.createFileNameGenerator(), maxCacheSize); 
 } 
  
 /** 
  * @param cacheDir   Directory for file caching. <b>Important:</b> Specify separate folder for cached files. It's 
  *       needed for right cache limit work. 
  * @param fileNameGenerator Name generator for cached files 
  * @param maxCacheSize  Maximum cache directory size (in bytes). If cache size exceeds this limit then file with the 
  *       most oldest last usage date will be deleted. 
  */
 public TotalSizeLimitedDiscCache(File cacheDir, FileNameGenerator fileNameGenerator, int maxCacheSize) { 
  super(cacheDir, fileNameGenerator, maxCacheSize); 
  if (maxCacheSize < MIN_NORMAL_CACHE_SIZE) { 
   L.w("You set too small disc cache size (less than %1$d Mb)", MIN_NORMAL_CACHE_SIZE_IN_MB); 
  } 
 } 
  
 @Override
 protected int getSize(File file) { 
  return (int) file.length(); 
 } 
} 


If we configure Diskcachesize and Diskcachefilecount in Imageloaderconfiguration, he's using Lrudisccache, or Unlimiteddisccache, In the latest source code also has a hard disk cache class can be configured, that is Limitedagedisccache, can be in Imageloaderconfiguration.diskcache (...) Configuration.



Source Code Interpretation


ImageView Mimageview = (imageview) Findviewbyid (r.id.image); 
  String IMAGEURL = "https://lh6.googleusercontent.com/-55osAWw3x0Q/URquUtcFr5I/AAAAAAAAAbs/rWlj1RUKrYI/s1024/A% 252520photographer.jpg "; 
   
  Display the configuration of a picture 
  displayimageoptions options = new Displayimageoptions.builder () 
    . showimageonloading ( r.drawable.ic_stub) 
    . Showimageonfail (r.drawable.ic_error). 
    Cacheinmemory (True) 
    . Cacheondisk (True) 
    . Bitmapconfig (Bitmap.Config.RGB_565) 
    . Build (); 



Imageloader.getinstance (). DisplayImage (ImageUrl, Mimageview, Options);
Most of the time we are using the above code to load the picture, we first look at the


public void DisplayImage (String uri, ImageView imageview, displayimageoptions options) { 
  displayimage (URI, new Imageviewaware (ImageView), options, NULL, NULL); 
  


From the above code, we can see that it will convert ImageView into Imageviewaware, imageviewaware What is the main thing to do? This class is mainly to ImageView a package, will imageview strong reference into a weak reference, when the memory is not enough, you can better recycle ImageView objects, there is to get imageview width and height. This allows us to tailor the image according to the ImageView, reducing the use of memory.
Next look at the specific DisplayImage method, because the code of this method is quite a lot, so I read it separately



 Checkconfiguration (); 
  if (Imageaware = = null) {throw new illegalargumentexception (error_wrong_arguments); 
  } if (listener = = NULL) {listener = Emptylistener; 
  } if (options = null) {options = configuration.defaultdisplayimageoptions; 
   } if (Textutils.isempty (URI)) {engine.canceldisplaytaskfor (imageaware); 
   Listener.onloadingstarted (URI, Imageaware.getwrappedview ()); if (Options.shouldshowimageforemptyuri ()) {imageaware.setimagedrawable (Options.getimageforemptyuri) ( 
   configuration.resources)); 
   else {imageaware.setimagedrawable (null); 
   } listener.onloadingcomplete (URI, Imageaware.getwrappedview (), NULL); 
  Return } 


The 1th line of code is to check whether Imageloaderconfiguration is initialized, and this initialization is done in application
12-21 lines are mainly for the time when the URL is empty, in the 13th line of code, There is a hashmap in the imageloaderengine that records the task being loaded, and when the picture is loaded, the ImageView ID and the URL of the picture are added to the HashMap, which is then removed when the load is complete. The Imageresforemptyuri image of Displayimageoptions is then set to the ImageView, and the final callback to the Imageloadinglistener interface tells it that the task is complete.



ImageSize targetsize = Imagesizeutils.definetargetsizeforview (Imageaware, Configuration.getmaximagesize ()); 
 String Memorycachekey = Memorycacheutils.generatekey (URI, targetsize); 
 
 Engine.preparedisplaytaskfor (Imageaware, Memorycachekey); 
 
 Listener.onloadingstarted (URI, Imageaware.getwrappedview ()); 
 Bitmap bmp = Configuration.memoryCache.get (Memorycachekey); 
 
  if (BMP!= null &amp;&amp;!bmp.isrecycled ()) {L.D (Log_load_image_from_memory_cache, Memorycachekey); if (options.shouldpostprocess ()) {Imageloadinginfo imageloadinginfo = new Imageloadinginfo (URI, Imageaware, TargetSiz 
   E, Memorycachekey, options, Listener, Progresslistener, Engine.getlockforuri (URI)); Processanddisplayimagetask displaytask = new Processanddisplayimagetask (engine, BMP, Imageloadinginfo, DefineHandler 
   (options)); 
   if (options.issyncloading ()) {Displaytask.run (); 
   else {engine.submit (displaytask); } else {Options.getdisplayer (). Display (BMP, IMAGeaware, Loadedfrom.memory_cache); 
  Listener.onloadingcomplete (URI, Imageaware.getwrappedview (), BMP); 
 } 
 }

The 1th line is mainly the imageview of the wide-high package into ImageSize objects, if you get the ImageView width of 0, you will use the width of the mobile phone screen as the height of the ImageView, we use ListView, The GridView to load the picture, the first page to get the width is 0, so the first page of the mobile phone screen width, the subsequent acquisition is the size of the control itself
Line 7th gets the bitmap object from the memory cache. We can then configure the memory cache logic in Imageloaderconfiguration, the default is Lrumemorycache, this class I said in the previous article,
there is a judgment in line 11th, If we set the Postprocessor in Displayimageoptions, we enter the true logic, but the default postprocessor is null, and the Bitmapprocessor interface mainly handles the bitmap. This framework does not give a corresponding implementation, if we have their own needs when we can implement the Bitmapprocessor interface (such as the picture set to circle)
第22-23 Line is to set the bitmap to ImageView above, Here we can configure the display requirements in Displayimageoptions Displayer, the default is Simplebitmapdisplayer, directly to the bitmap set to ImageView above, we can configure other display logic, He provides a fadeinbitmapdisplayer (transparency from 0-1) Roundedbitmapdisplayer (4 Corners is an arc), and then callbacks to the Imageloadinglistener interface



if (options.shouldshowimageonloading ()) { 
    imageaware.setimagedrawable (options.getimageonloading) ( configuration.resources)); 
   } else if (options.isresetviewbeforeloading ()) { 
    imageaware.setimagedrawable (null); 
   } 
 
   Imageloadinginfo imageloadinginfo = new Imageloadinginfo (URI, Imageaware, Targetsize, Memorycachekey, 
     options, Listener, Progresslistener, Engine.getlockforuri (URI)); 
   Loadanddisplayimagetask displaytask = new Loadanddisplayimagetask (engine, Imageloadinginfo, 
     Definehandler ( Options)); 
   if (options.issyncloading ()) { 
    displaytask.run (); 
   } else { 
    engine.submit (displaytask); 
   } 


This code is mainly bitmap not in the memory cache, from the file or the network to obtain bitmap objects, instantiate a Loadanddisplayimagetask object, Loadanddisplayimagetask implemented Runnable, If you configure Issyncloading to True, execute the Loadanddisplayimagetask run method directly, indicating that synchronization, default is False, submits the loadanddisplayimagetask to the thread pool object
And then we're going to look at Loadanddisplayimagetask's run (), which is still quite complicated, and we're still a section of the analysis


if (waitifpaused ()) return; 
if (Delayifneed ()) return; 


If waitifpaused (), Delayifneed () returns True, it is returned directly from the run () method, without executing the following logic, let's look at


Waitifpaused ()

private Boolean waitifpaused () { 
 Atomicboolean pause = Engine.getpause (); 
 if (Pause.get ()) { 
  synchronized (Engine.getpauselock ()) { 
   if (Pause.get ()) { 
    L.D (Log_waiting_for_resume, Memorycachekey); 
    try { 
     Engine.getpauselock (). Wait (); 
    } catch (Interruptedexception e) { 
     L.E (log_task_interrupted, Memorycachekey); 
     return true; 
    } 
    L.D (Log_resume_after_pause, Memorycachekey); 
   } 
  } 
 return istasknotactual (); 
} 


This method is why use it, mainly we use Listview,gridview to load pictures, sometimes in order to slide more fluent, we will choose the fingers in the slide or a sharp slide when not to load the picture, so just put forward such a method, then how to use it? Here we use the Pauseonscrolllistener class, which uses very simple Listview.setonscrolllistener (the new Pauseonscrolllistener (Pauseonscroll, pauseonfling), pauseonscroll controls Our slow sliding listview,gridview whether to stop loading pictures, pauseonfling control the fierce sliding listview,gridview whether to stop loading pictures
In addition, the return value of this method is determined by istasknotactual () and we then look at the source code of the Istasknotactual ()


Private Boolean istasknotactual () {return 
  isviewcollected () | | isviewreused (); 
 } 


Isviewcollected () is to determine whether we imageview is recycled by the garbage collector, if recycled, the Loadanddisplayimagetask method of run () directly returned, isviewreused () Why use the Isviewreused () method to determine if the ImageView is reused and the Reuse run () method is returned directly? Mainly Listview,gridview we will reuse item objects, if we first to load Listview,gridview first page of the picture, the first page of the picture has not been all loaded we will quickly scroll, isviewreused () Method will avoid these invisible item to load the picture, but directly load the picture of the current interface


Reentrantlock loadfromurilock = Imageloadinginfo.loadfromurilock; 
  L.D (Log_start_display_image_task, Memorycachekey); 
  if (loadfromurilock.islocked ()) {L.D (log_waiting_for_image_loaded, Memorycachekey); 
  } loadfromurilock.lock (); 
  Bitmap bmp; 
 
   try {checktasknotactual (); 
   BMP = Configuration.memoryCache.get (Memorycachekey); 
    if (bmp = null | | bmp.isrecycled ()) {BMP = Tryloadbitmap (); if (BMP = null) return; 
    Listener callback already was fired Checktasknotactual (); 
 
    Checktaskinterrupted (); 
     if (options.shouldpreprocess ()) {L.D (log_preprocess_image, Memorycachekey); 
     BMP = Options.getpreprocessor (). process (BMP); 
     if (BMP = = null) {L.E (error_pre_processor_null, Memorycachekey); } if (BMP!= null &amp;&amp; options.iscacheinmemory ()) {L.D (log_cache_image_in_memory, Memorycacheke 
     y); 
    Configuration.memoryCache.put (Memorycachekey, BMP); } else {Loadedfrom = Loadedfrom.memory_cache; 
   L.D (log_get_image_from_memory_cache_after_waiting, Memorycachekey); 
    } if (BMP!= null &amp;&amp; options.shouldpostprocess ()) {L.D (log_postprocess_image, Memorycachekey); 
    BMP = Options.getpostprocessor (). process (BMP); 
    if (BMP = = null) {L.E (error_post_processor_null, Memorycachekey); 
   } checktasknotactual (); 
  Checktaskinterrupted (); 
   catch (Taskcancelledexception e) {firecancelevent (); 
  Return 
  finally {Loadfromurilock.unlock (); 
 }


The 1th line of code has a loadfromurilock, which is a lock, and the method of acquiring the lock is in the Getlockforuri () method of the Imageloaderengine class


Reentrantlock Getlockforuri (String uri) { 
  Reentrantlock lock = Urilocks.get (URI); 
  if (lock = = null) { 
   lock = new Reentrantlock (); 
   Urilocks.put (URI, lock); 
  } 
  return lock; 
 } 


As can be seen from above, this lock object and the image URL is reciprocal, why do this? You're a little confused, you know? Do not know if you have considered a scene, if in a ListView, an item is getting pictures of the process, and at this time we will roll out the interface and then roll it in, roll in if not lock, the item will go to load a picture, Suppose that in a very short period of time scrolling very frequently, then there will be many times to the network to request pictures, so here according to the image URL to a Reentrantlock object, so that the request with the same URL will wait in line 7th, wait until the picture loaded complete, Reentrantlock is released, and just those requests for the same URL will continue to execute the code below line 7th.
To line 12th, they are first fetched from the memory cache, and if the memory cache is not executing the following logic, Reentrantlock's role is to avoid asking for pictures from the web over and over again.
The 14th line of the method Tryloadbitmap (), this method is indeed a bit long, I first tell you, this logic is to get from the file cache to have no bitmap object, if not in the network to obtain, and then save the bitmap in the file system, we are specific analysis


File imagefile = Configuration.diskCache.get (URI); 
   if (imagefile!= null && imagefile.exists ()) { 
    l.d (Log_load_image_from_disk_cache, memorycachekey); 
    Loadedfrom = Loadedfrom.disc_cache; 
 
    Checktasknotactual (); 
    Bitmap = Decodeimage (Scheme.FILE.wrap (Imagefile.getabsolutepath ())); 
   } 


First to determine whether the file cache, if any, directly to call the Decodeimage () method to decode the picture, the method inside the call Baseimagedecoder class decode () method, according to the imageview of the width of the height, ScaleType to cut the picture, the specific code I do not introduce, we go to see, we take a look down the Tryloadbitmap () method


if (bitmap = null | | bitmap.getwidth () <= 0 | | bitmap.getheight () <= 0) { 
   l.d (log_load_image_from_network, Memo Rycachekey); 
   Loadedfrom = loadedfrom.network; 
 
   String imageurifordecoding = URI; 
   if (Options.iscacheondisk () && Trycacheimageondisk ()) { 
    ImageFile = Configuration.diskCache.get (URI); 
    if (imagefile!= null) { 
     imageurifordecoding = Scheme.FILE.wrap (Imagefile.getabsolutepath ()); 
    } 
   } 
 
   Checktasknotactual (); 
   Bitmap = Decodeimage (imageurifordecoding); 
 
   if (bitmap = null | | bitmap.getwidth () <= 0 | | bitmap.getheight () <= 0) { 
    firefailevent (Failtype.decoding_erro R, NULL); 
   } 
   


Line 1th indicates that the bitmap obtained from the file cache is null, or the width is 0, to get bitmap on the network, to the 6th line of code is configured Displayimageoptions Iscacheondisk, Indicates whether the bitmap object needs to be saved in the file system, generally we need to configure to true, the default is false this should be noted, then it is to execute the Trycacheimageondisk () method, go to the server to pull the picture and save in the local file


Private Bitmap Decodeimage (String Imageuri) throws IOException {Viewscaletype Viewscaletype = Imageaware.getscaletype ( 
 ); Imagedecodinginfo decodinginfo = new Imagedecodinginfo (Memorycachekey, Imageuri, Uri, Targetsize, ViewScaleType, GetDo 
 Wnloader (), options); 
Return Decoder.decode (Decodinginfo); }/** @return &lt;b&gt;true&lt;/b&gt;-If image was downloaded successfully; &lt;b&gt;false&lt;/b&gt;-otherwise * * Private Boolean Trycacheimageondisk () throws Taskcancelledexception {L.D (log_c 
 
 Ache_image_on_disk, Memorycachekey); 
 Boolean loaded; 
  try {loaded = Downloadimage (); 
   if (loaded) {int width = Configuration.maximagewidthfordiskcache; 
    
   int height = Configuration.maximageheightfordiskcache; 
    if (Width &gt; 0 | | height &gt; 0) {l.d (log_resize_cached_image_file, Memorycachekey); Resizeandsaveimage (width, height); 
  Todo:process boolean Result}} catch (IOException e) {L.E (e); 
 loaded = false; Return loaded; Private Boolean Downloadimage () throws IOException {InputStream is = Getdownloader (). GetStream (URI, Options.getext 
 Rafordownloader ()); 
Return Configuration.diskCache.save (URI, are, this); 
 }

The

Line 6th Downloadimage () method is responsible for downloading the picture and keeping it in the file cache, bitmap the progress of the download save to the onbytescopied of the Ioutils.copylistener interface (int current, int total) method, so we can set the Imageloadingprogresslistener interface to get the progress of the picture download save, where the picture saved in the file system is the original image
第16-17 line, Gets whether the imageloaderconfiguration sets the size of the picture saved in the file system, and if Maximagewidthfordiskcache and Maximageheightfordiskcache are set, Will call the Resizeandsaveimage () method to crop the picture and then replace the original image, save the cropped picture to the file system, before a classmate asked me that this framework saved in the file system pictures are original, how to save thumbnails, Just set Maximagewidthfordiskcache and Maximageheightfordiskcache when instantiating imageloaderconfiguration in application


if (BMP = null) return; Listener callback already was fired 
 
    checktasknotactual (); 
    Checktaskinterrupted (); 
 
    if (options.shouldpreprocess ()) { 
     l.d (log_preprocess_image, memorycachekey); 
     BMP = Options.getpreprocessor (). process (BMP); 
     if (BMP = = null) { 
      L.E (error_pre_processor_null, Memorycachekey); 
     } 
    } 
 
    if (BMP!= null && options.iscacheinmemory ()) { 
     l.d (log_cache_image_in_memory, memorycachekey); 
     Configuration.memoryCache.put (Memorycachekey, BMP); 
     


The next step here is simple, whether 6-12 lines to deal with the bitmap, this needs to implement itself, 14-17 is to save the picture to the memory cache


Displaybitmaptask displaybitmaptask = new Displaybitmaptask (BMP, Imageloadinginfo, engine, loadedfrom); 
  Runtask (Displaybitmaptask, syncloading, Handler, engine); 


The last two lines of code are a display task that looks directly at the Displaybitmaptask class's Run () method


@Override public 
 Void Run () { 
  if (imageaware.iscollected ()) { 
   L.D (log_task_cancelled_imageaware_) collected, Memorycachekey); 
   Listener.onloadingcancelled (Imageuri, Imageaware.getwrappedview ()); 
  } else if (isviewwasreused ()) { 
   l.d (log_task_cancelled_imageaware_reused, memorycachekey); 
   Listener.onloadingcancelled (Imageuri, Imageaware.getwrappedview ()); 
  } else { 
   L.D (log_display_image_in_imageaware, Loadedfrom, Memorycachekey); 
   Displayer.display (Bitmap, Imageaware, loadedfrom); 
   Engine.canceldisplaytaskfor (imageaware); 
   Listener.onloadingcomplete (Imageuri, Imageaware.getwrappedview (), bitmap); 
  } 
  


If ImageView is recycled or reused, callback to Imageloadinglistener interface, or call Bitmapdisplayer to show bitmap
The article has been written here, I do not know if you have any further understanding of this open source framework, the open source framework design is also very flexible, with a lot of design patterns, such as builder mode, decoration mode, agent mode, strategy mode, etc., so that we can expand to achieve the functions we want.


Related Article

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.