• After 15+ years, we've made a big change: Android Forums is now Early Bird Club. Learn more here.

Apps How to set the image processing functions to a worker thread

0belix

Lurker
Mar 3, 2017
9
2
Hi,

I've developed the following code, but now i get messages stating that the application main thread is getting too much load. I'm new to Android programming, but i've read that i should be doing the heavy load on a thread other that the main one. Can someone help me figuring out how to get all the image processing from the main thread to a worker thread (at least the decodeFile and the getResizedBitmap functions should be running there, i suppose...):

Code:
public class InputItemDataActivity extends AppCompatActivity {

    static final int CAM_REQUEST = 1;
    String baseFolderName = "ConservationApp";
    String itemId = "";
    String imageFileName = "";
    ArrayList<String> allImages = new ArrayList<String>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_input_item_data);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        Intent intent = getIntent();
        itemId = intent.getStringExtra(AddItemActivity.EXTRA_MESSAGE);
        TextView textView = (TextView) findViewById(R.id.itemId);
        textView.setText(itemId);

        LoadImages(itemId);

    }

    public class ImageAdapter extends BaseAdapter{
        private Context mContext;

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

        public int getCount(){
            return allImages.size();
        }

        public Object getItem(int position){
            return null;
        }

        public long getItemId(int position){
            return 0;
        }

        public View getView(int position, View convertView, ViewGroup parent){
            ImageView imageView;
            if (convertView == null){
                imageView = new ImageView(mContext);
                imageView.setLayoutParams(new GridView.LayoutParams(85,85));
                imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
                imageView.setPadding(8,8,8,8);
            }else {
                imageView = (ImageView) convertView;
            }

            Bitmap bitmap = decodeFile(allImages.get(position));

            imageView.setImageBitmap(bitmap);

            Bitmap bitmapThumb = getResizedBitmap(bitmap, 85, 85);

            imageView.setImageBitmap(bitmapThumb);

            return imageView;

        }

    }

    public static Bitmap decodeFile(String pathName) {
        Bitmap bitmap = null;
        BitmapFactory.Options options = new BitmapFactory.Options();
        for (options.inSampleSize = 1; options.inSampleSize <= 32; options.inSampleSize++) {
            try {
                bitmap = BitmapFactory.decodeFile(pathName, options);
                Log.d("No Error", "Decoded successfully for sampleSize " + options.inSampleSize);
                break;
            } catch (OutOfMemoryError outOfMemoryError) {
                // If an OutOfMemoryError occurred, we continue with for loop and next inSampleSize value
                Log.e("Error", "outOfMemoryError while reading file for sampleSize " + options.inSampleSize
                        + " retrying with higher value");
            }
        }
        return bitmap;
    }

    private static Bitmap getResizedBitmap(Bitmap bitmap, float maxWidth, float maxHeight) {

        float width = bitmap.getWidth();
        float height = bitmap.getHeight();
        if (width > maxWidth) {
            height = (maxWidth / width) * height;
            width = maxWidth;
        }
        if (height > maxHeight) {
            width = (maxHeight / height) * width;
            height = maxHeight;
        }
        return Bitmap.createScaledBitmap(bitmap, (int) width, (int) height, true);

    }

    public void cancelAction(View view){

        Intent intent = new Intent(this, AddItemActivity.class);
        startActivity(intent);

    }

    public void captureImage(View view){

        Intent camera_intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        File file = getFile();
        camera_intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(file));
        startActivityForResult(camera_intent, CAM_REQUEST);

    }

    private File getFile(){

        File folder = new File("sdcard/" + baseFolderName);

        if(!folder.exists()){
            folder.mkdir();
        }

        String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
        imageFileName = itemId + "_" + timeStamp + ".jpg";

        File image_file = new File(folder, imageFileName);

        return image_file;

    }

    private void InsertImageIntoDB(String itemId, String imageFilename){

        SQLiteDatabase myDB = null;
        String ItemTableName = "Image";
        String erro = "";
        String Data="";

        // Cria a tabela principal
        try {

            // Declara a DB a usar
            myDB = this.openOrCreateDatabase("ConservationAppDB", MODE_PRIVATE, null);

            myDB.execSQL("INSERT INTO " + ItemTableName + " (QRCodeId, Image) VALUES ('" + itemId + "', '" + imageFilename + "');");
        } catch(Exception e) {
            Log.e("Error", "Error", e);
            erro = e.getMessage();
            TextView errorPanel = (TextView) findViewById(R.id.errorPanel);
            errorPanel.setText("Insert Image Error: " + erro);
            errorPanel.setVisibility(View.VISIBLE);
        } finally {
            if (myDB != null) {
                myDB.close();
            }
        }

    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data){

        String path="sdcard/" + baseFolderName + "/" + imageFileName;

        // Add photo to DB
        InsertImageIntoDB(itemId, imageFileName);

        LoadImages(itemId);

    }

    private void LoadImages(String itemId){

        SQLiteDatabase myDB= null;

        /*
        gridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                Toast.makeText(HelloGridView.this, "" + position, Toast.LENGTH_SHORT).show();
            }
        });
        */

        String erro = "";

        try {

            // Declara a DB a usar
            myDB = this.openOrCreateDatabase("ConservationAppDB", MODE_PRIVATE, null);

            Cursor c = myDB.rawQuery("SELECT Image.ItemId, Image.QRCodeId, Image.Image FROM Image WHERE Image.QRCodeId = '" + itemId + "' AND Image.Image <> '';" , null);

            int itemIdInt = c.getColumnIndex("ItemId");
            int imageInt = c.getColumnIndex("Image");

            // Check if our result was valid.
            c.moveToFirst();
            if (c != null) {
                // Loop through all Results
                do {

                    String currentImageFile = c.getString(imageInt);
                    String path="sdcard/" + baseFolderName + "/" + currentImageFile;

                    if(currentImageFile != ""){

                        // Prevent duplicates in adapter
                        if(allImages.contains(path)) {
                            Log.d("Image exists", path);
                        }else{
                            allImages.add(path);
                        }

                        //allImages.add(path);
                        Log.d("Path", path);
                    }else{
                        Log.d("Empty Path", path);
                    }

                }while(c.moveToNext());

            }else{

                erro = "Não foram encontrados items na DB";

            }

        } catch(Exception e) {
            Log.e("Error", "Error", e);
            erro = e.getMessage();
        } finally {
            if (myDB != null) {
                myDB.close();
            }

        }

        if(erro != "") {
            // Mostra texto de detalhe do erro
            TextView errorPanel = (TextView) findViewById(R.id.errorPanel);
            errorPanel.setText("Read Error: " + erro);
            errorPanel.setVisibility(View.VISIBLE);
        }else{

            GridView gridView = (GridView) findViewById(R.id.ImageGrid);

            try{
                gridView.setAdapter(new ImageAdapter(this));
            }catch (Exception e) {
                Log.e("Error", "Error", e);
                erro = e.getMessage();
                TextView errorPanel2 = (TextView) findViewById(R.id.errorPanel);
                errorPanel2.setText("GridView Error: " + erro);
            }

        }

}

}
 
AsyncTask is your friend here. Do the Bitmap loading on a separate thread. Have a look at this article, which addresses the problem you have

https://stuff.mit.edu/afs/sipb/project/android/docs/training/displaying-bitmaps/process-bitmap.html

Hi LV,

Thx for the post!

I implemented this code:

Code:
class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
    private final WeakReference<ImageView> imageViewReference;
    private int data = 0;

    public BitmapWorkerTask(ImageView imageView) {
        // Use a WeakReference to ensure the ImageView can be garbage collected
        imageViewReference = new WeakReference<ImageView>(imageView);
    }

    // Decode image in background.
    @Override
    protected Bitmap doInBackground(Integer... params) {
        data = params[0];
        //return decodeSampledBitmapFromResource(getResources(), data, 100, 100));
    }

    // Once complete, see if ImageView is still around and set bitmap.
    @Override
    protected void onPostExecute(Bitmap bitmap) {
        if (isCancelled()) {
            bitmap = null;
        }

        if (imageViewReference != null && bitmap != null) {
            final ImageView imageView = imageViewReference.get();
            final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
            if (this == bitmapWorkerTask && imageView != null) {
                imageView.setImageBitmap(bitmap);
            }
        }
    }

}

public void loadBitmap(int resId, ImageView imageView) {
    if (cancelPotentialWork(resId, imageView)) {
        final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
        final AsyncDrawable asyncDrawable = new AsyncDrawable(getResources(), mPlaceHolderBitmap, task);
        imageView.setImageDrawable(asyncDrawable);
        task.execute(resId);
    }
}

static class AsyncDrawable extends BitmapDrawable {
    private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;

    public AsyncDrawable(Resources res, Bitmap bitmap, BitmapWorkerTask bitmapWorkerTask) {
        super(res, bitmap);
        bitmapWorkerTaskReference = new WeakReference<BitmapWorkerTask>(bitmapWorkerTask);
    }

    public BitmapWorkerTask getBitmapWorkerTask() {
        return bitmapWorkerTaskReference.get();
    }
}

public static boolean cancelPotentialWork(int data, ImageView imageView) {
    final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);

    if (bitmapWorkerTask != null) {
        final int bitmapData = bitmapWorkerTask.data;
        if (bitmapData != data) {
            // Cancel previous task
            bitmapWorkerTask.cancel(true);
        } else {
            // The same work is already in progress
            return false;
        }
    }
    // No task associated with the ImageView, or an existing task was cancelled
    return true;
}

private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
    if (imageView != null) {
        final Drawable drawable = imageView.getDrawable();
        if (drawable instanceof AsyncDrawable) {
            final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
            return asyncDrawable.getBitmapWorkerTask();
        }
    }
    return null;
}

but i don t know how to apply this to my case, since it makes use of the Resources, assuming the images are there, when in fact they are not. They are on a folder specific to the app and referenced on a SQLite DB as you can see on my code posted before.

Also, the code on the page you provided makes use of 2 undeclared things, a function and a variable(?!), decodeSampledBitmapFromResource and mPlaceHolderBitmap.

Can you help me adapt this to my code? Thx
 
Upvote 0

BEST TECH IN 2023

We've been tracking upcoming products and ranking the best tech since 2007. Thanks for trusting our opinion: we get rewarded through affiliate links that earn us a commission and we invite you to learn more about us.

Smartphones