Android-Batch image loading OOM Solution
I. scenarios and causes of OOM Problems |
A good app always has exquisite pictures, so image loading during Android development is always unavoidable. OOM may occur if improper processing occurs during image loading. So how can we completely solve this problem? This article will introduce this knowledge in detail.
First, let's make a summary.OOM scenariosThere are only the following types:
1. The uploaded image is too large.
2. Too many images are loaded at a time.
3. Both cases
So why is the OOM problem in the above scenarios? In fact, there are clear descriptions in the API documentation,Main causes of OMMThere are two points:
1. Mobile devices limit the memory that each app can use. The minimum value is 16 Mb. Some devices allocate more memory, such as 24, 32 M, and 64 M, in short, there will be limits and will not allow you to use them without restrictions.
2. The image is stored as a bitmap when it is loaded into the memory in andorid. After android2.3, ARGB_8888 is used by default. In this way, each pixel is stored in 4 bytes. Therefore, image loading occupies a large amount of memory.
We have analyzed all the scenarios and causes. Let's take a look at how to solve these problems.
Ii. Solving the big image loading Problem |
First, we need to solve the problem of large image loading. Generally, we do not need to load the original large image because of the screen size and layout display in actual applications, only proportional sampling and scaling are required. In this way, memory saving and image distortion can be ensured. The specific implementation steps are as follows:
1. decode the image without loading the image content to obtain the image size information.
Here we need to use BitmapFactory's decode series method and BitmapFactory. Options. When using the decode series method to load images, you must set the inJustDecodeBounds attribute of Options to true.
BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds=true; BitmapFactory.decodeFile(path, options);
2. Calculate the scaling ratio based on the size of the obtained image and the size of the image to be displayed on the page.
public int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) { // Raw height and width of image final int height = options.outHeight; final int width = options.outWidth; int inSampleSize = 1; if (height > reqHeight || width > reqWidth) { if (width > height) { inSampleSize = Math.round((float) height / (float) reqHeight); } else { inSampleSize = Math.round((float) width / (float) reqWidth); } } return inSampleSize; }
3. Scale the image according to the calculated ratio.
// Calculate the scaling ratio of the image options. inSampleSize = calculateInSampleSize (options, reqWidth, reqHeight); options. inJustDecodeBounds = false; Bitmap bitmap = BitmapFactory. decodeFile (path, options );
According to the scaling ratio, it will save a lot of memory than the original big image, as shown below:
Iii. Batch loading of large charts |
Next, let's take a look at how to load large charts in batches. The first step is to determine the scaling ratio of images based on the size of the image control displayed on the interface. Here we use the gridview to load the local image as an example. The specific steps are as follows:
1. Load all image addresses in the external storage through the contentprovider provided by the system
private void loadPhotoPaths(){ Cursor cursor= getContentResolver().query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, null, null, null, null); while(cursor.moveToNext()){ String path = cursor.getString(cursor.getColumnIndex(MediaColumns.DATA)); paths.add(path); } cursor.close(); }
2. Customize the adapter to load images in the getview method of the adapter
@Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder=null; if(convertView==null){ convertView = LayoutInflater.from(this.mContext).inflate(R.layout.grid_item_layout, null); holder = new ViewHolder(); holder.photo=(ImageView)convertView.findViewById(R.id.photo); convertView.setTag(holder); }else{ holder=(ViewHolder)convertView.getTag(); } final String path = this.paths.get(position); holder.photo.setImageBitmap(imageLoader.getBitmapFromCache(path)); return convertView; }
After the above two steps, we found that the user experience was particularly poor after the program was run, and there was no response for half a day. Obviously, this is because we loaded a large number of images in the main thread, which is not suitable. Here we need to put the image loading work in the Child thread, transform the custom ImageLoader tool class, and add a thread pool object for it to manage the sub-threads used to download images.
Private ExecutorService executor; private ImageLoader (Context mContxt) {super (); executor = Executors. newFixedThreadPool (3);} // Asynchronous Method for loading images, including callback listening public void loadImage (final ImageView view, final String path, final int reqWidth, final int reqHeight, final onBitmapLoadedListener callback) {final Handler mHandler = new Handler () {@ Override public void handleMessage (Message msg) {super. handleMessage (msg); switch (msg. what) {case 1: Bitmap bitmap = (Bitmap) msg. obj; callback. displayImage (view, bitmap); break; default: break ;}}; executor.exe cute (new Runnable () {@ Override public void run () {Bitmap bitmap = loadBitmapInBackground (path, reqWidth, reqHeight); putBitmapInMemey (path, bitmap); Message msg = mHandler. obtainMessage (1); msg. obj = bitmap; mHandler. sendMessage (msg );}});}
After the transformation, the user experience is much better, as shown below:
Although the effect has been improved, there are two serious problems in the loading process:
1. misplacement of images
2. When the sliding speed is too fast, the image loading speed is too slow.
After analysis, it is not difficult to find out the cause, mainly because the holder caches the grid item for reuse and the number of loading tasks in the thread pool is too large. You only need to slightly modify the program, as shown below:
Adapter:
holder.photo.setImageResource(R.drawable.ic_launcher); holder.photo.setTag(path); imageLoader.loadImage(holder.photo, path, DensityUtil.dip2px(80), DensityUtil.dip2px(80), new onBitmapLoadedListener() { @Override public void displayImage(ImageView view, Bitmap bitmap) { String imagePath= view.getTag().toString(); if(imagePath.equals(path)){ view.setImageBitmap(bitmap); } } });
In ImageLoader:
executor.execute(new Runnable() { @Override public void run() { String key = view.getTag().toString(); if (key.equals(path)) { Bitmap bitmap = loadBitmapInBackground(path, reqWidth, reqHeight); putBitmapInMemey(path, bitmap); Message msg = mHandler.obtainMessage(1); msg.obj = bitmap; mHandler.sendMessage(msg); } } });
For better user experience, we can continue to optimize the cache, that is, to cache images. cache can be divided into two parts: memory cache disk cache, in this example, all local images are loaded with only the memory cache. Modify the ImageLoader object and add the LruCache object to cache the image.
Private ImageLoader (Context mContxt) {super (); executor = Executors. newFixedThreadPool (3); // use 1/8 of the application as the image cache ActivityManager am = (ActivityManager) mContxt. getSystemService (Context. ACTIVITY_SERVICE); int maxSize = am. getMemoryClass () * 1024*1024/8; mCache = new LruCache
(MaxSize) {@ Override protected int sizeOf (String key, Bitmap value) {return value. getRowBytes () * value. getHeight () ;};}// Save the image to the cache public void putBitmapInMemey (String path, Bitmap bitmap) {if (path = null) return; if (bitmap = null) return; if (getBitmapFromCache (path) = null) {this. mCache. put (path, bitmap) ;}} public Bitmap getBitmapFromCache (String path) {return mCache. get (path );}
In the loadImage method, the image is obtained from the memory before being asynchronously loaded. Download the sample code.
To sum up the following methods to solve the OOM problem during image loading:
1. Do not load the original large image. Scale the image according to the display control and then load its thumbnail.
2. Do not load images in the main thread. It is important to handle image misplacement and useless threads when asynchronously loading images in listview and gridview.
3. Use the cache to determine whether the dual cache and cache size are used according to the actual situation.
What do you know? If you want to test the project by yourself, click "Download project" to run the test!