Thursday, September 18, 2014

Improve JPG loading with ExifInterface.getThumbnail()

Refer to last example of "getView() to load images in AsyncTask", the loading of large images (such as photos from camera/DSLR) is too slow to accept. Most JPG embedd with thumbnail, we can use ExifInterface to check and load if it has thumbnail embedded.

Check this video to know how it improve the performance:


Modify MainActivity.java from last example of "getView() to load images in AsyncTask", accepet jpg only, and check and load thumbnail.

package com.example.androidgridview;

import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;

import android.media.ExifInterface;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Environment;
import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.GridView;
import android.widget.ImageView;
import android.widget.Toast;

public class MainActivity extends Activity {
 
 AsyncTaskLoadFiles myAsyncTaskLoadFiles;

 public class AsyncTaskLoadFiles extends AsyncTask<Void, String, Void> {
  
  File targetDirector;
  ImageAdapter myTaskAdapter;

  public AsyncTaskLoadFiles(ImageAdapter adapter) {
   myTaskAdapter = adapter;
  }

  @Override
  protected void onPreExecute() {
   String ExternalStorageDirectoryPath = Environment
     .getExternalStorageDirectory().getAbsolutePath();

   String targetPath = ExternalStorageDirectoryPath + "/test/";
   targetDirector = new File(targetPath);
   myTaskAdapter.clear();
   
   super.onPreExecute();
  }

  @Override
  protected Void doInBackground(Void... params) {
   
   //open jpg only
   File[] files = targetDirector.listFiles(new FilenameFilter() {
       public boolean accept(File dir, String name)
       {
           return (name.endsWith(".jpg")||name.endsWith(".JPG")); 
       }
   });
   //File[] files = targetDirector.listFiles();
   
   Arrays.sort(files);
   for (File file : files) {
    publishProgress(file.getAbsolutePath());
    if (isCancelled()) break;
   }
   return null;
  }

  @Override
  protected void onProgressUpdate(String... values) {
   myTaskAdapter.add(values[0]);
   super.onProgressUpdate(values);
  }

  @Override
  protected void onPostExecute(Void result) {
   myTaskAdapter.notifyDataSetChanged();
   super.onPostExecute(result);
  }

 }

 public class ImageAdapter extends BaseAdapter {

  private Context mContext;
  ArrayList<String> itemList = new ArrayList<String>();

  public ImageAdapter(Context c) {
   mContext = c;
  }

  void add(String path) {
   itemList.add(path);
  }
  
  void clear() {
   itemList.clear();
  }
  
  void remove(int index){
   itemList.remove(index);
  }

  @Override
  public int getCount() {
   return itemList.size();
  }

  @Override
  public Object getItem(int position) {
   // TODO Auto-generated method stub
   return itemList.get(position);
  }

  @Override
  public long getItemId(int position) {
   // TODO Auto-generated method stub
   return 0;
  }

  //getView load bitmap in AsyncTask
  @Override
  public View getView(final int position, View convertView, ViewGroup parent) {
   ViewHolder holder;

   ImageView imageView;
   if (convertView == null) { // if it's not recycled, initialize some
          // attributes
    imageView = new ImageView(mContext);
    imageView.setLayoutParams(new GridView.LayoutParams(220, 220));
    imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
    imageView.setPadding(8, 8, 8, 8);
    
    convertView = imageView;
    
    holder = new ViewHolder();
    holder.image = imageView;
    holder.position = position;
    convertView.setTag(holder);
   } else {
    //imageView = (ImageView) convertView;
    holder = (ViewHolder) convertView.getTag();
    ((ImageView)convertView).setImageBitmap(null);
   }
   
   //Bitmap bm = decodeSampledBitmapFromUri(itemList.get(position), 220, 220);
   // Using an AsyncTask to load the slow images in a background thread
   new AsyncTask<ViewHolder, Void, Bitmap>() {
       private ViewHolder v;

       @Override
       protected Bitmap doInBackground(ViewHolder... params) {
        
        Bitmap bm = null;
        
        boolean haveThumbNail = false;
        
        try {
      ExifInterface exifInterface = 
       new ExifInterface(itemList.get(position));
      if(exifInterface.hasThumbnail()){
       byte[] thumbnail = exifInterface.getThumbnail();
       bm = BitmapFactory.decodeByteArray(thumbnail, 0, thumbnail.length);
      }
      haveThumbNail = true;
     } catch (IOException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
     }
        
        if(!haveThumbNail){
         bm = decodeSampledBitmapFromUri(
           itemList.get(position), 220, 220);
        }

           v = params[0];
           return bm;
       }

       @Override
       protected void onPostExecute(Bitmap result) {
           super.onPostExecute(result);
           //Not work for me!
           /*
           if (v.position == position) {
               // If this item hasn't been recycled already, 
            // show the image
               v.image.setImageBitmap(result);
           }
           */

           v.image.setImageBitmap(result);

       }
   }.execute(holder);

   //imageView.setImageBitmap(bm);
   //return imageView;
   return convertView;
  }

  public Bitmap decodeSampledBitmapFromUri(String path, int reqWidth,
    int reqHeight) {

   Bitmap bm = null;
   // First decode with inJustDecodeBounds=true to check dimensions
   final BitmapFactory.Options options = new BitmapFactory.Options();
   options.inJustDecodeBounds = true;
   BitmapFactory.decodeFile(path, options);

   // Calculate inSampleSize
   options.inSampleSize = calculateInSampleSize(options, reqWidth,
     reqHeight);

   // Decode bitmap with inSampleSize set
   options.inJustDecodeBounds = false;
   bm = BitmapFactory.decodeFile(path, options);

   return bm;
  }

  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;
  }
  
  class ViewHolder {
            ImageView image;
            int position;
        }

 }

 ImageAdapter myImageAdapter;

 @Override
 public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);

  final GridView gridview = (GridView) findViewById(R.id.gridview);
  myImageAdapter = new ImageAdapter(this);
  gridview.setAdapter(myImageAdapter);

  /*
   * Move to asyncTaskLoadFiles String ExternalStorageDirectoryPath =
   * Environment .getExternalStorageDirectory() .getAbsolutePath();
   * 
   * String targetPath = ExternalStorageDirectoryPath + "/test/";
   * 
   * Toast.makeText(getApplicationContext(), targetPath,
   * Toast.LENGTH_LONG).show(); File targetDirector = new
   * File(targetPath);
   * 
   * File[] files = targetDirector.listFiles(); for (File file : files){
   * myImageAdapter.add(file.getAbsolutePath()); }
   */
  myAsyncTaskLoadFiles = new AsyncTaskLoadFiles(myImageAdapter);
  myAsyncTaskLoadFiles.execute();

  gridview.setOnItemClickListener(myOnItemClickListener);
  
  Button buttonReload = (Button)findViewById(R.id.reload);
  buttonReload.setOnClickListener(new OnClickListener(){

   @Override
   public void onClick(View arg0) {
    
    //Cancel the previous running task, if exist.
    myAsyncTaskLoadFiles.cancel(true);
    
    //new another ImageAdapter, to prevent the adapter have
    //mixed files
    myImageAdapter = new ImageAdapter(MainActivity.this);
    gridview.setAdapter(myImageAdapter);
    myAsyncTaskLoadFiles = new AsyncTaskLoadFiles(myImageAdapter);
    myAsyncTaskLoadFiles.execute();
   }});

 }

 OnItemClickListener myOnItemClickListener = new OnItemClickListener() {

  @Override
  public void onItemClick(AdapterView<?> parent, View view, int position,
    long id) {
   String prompt = "remove " + (String) parent.getItemAtPosition(position);
   Toast.makeText(getApplicationContext(), prompt, Toast.LENGTH_SHORT)
     .show();
   
   myImageAdapter.remove(position);
   myImageAdapter.notifyDataSetChanged();

  }
 };

}

download filesDownload the files.

Next:
Touch GridView to show photo

1 comment:

Unknown said...

Hi,
thanks a lot for your sample.
I have a problem with gridview that doesn't show all the items(image) in the gridview. It load only some pictures but not all and doesn't show always the same i think.
Can you help me?