1

I’m using thquinn’s DraggableGridView and load ~60 images into it. This all works fine. I had all the images needed in my assets, but want to create them at runtime since first of only the Text on the images change which seems redundant and I can reduce the appsize and the second reason is that I need to sometimes change the icons over the Air where adding wouldn’t be the problem but deleting from assets isn’t possible and would use unnecessary space. That briefly to explain my motives here.

So I’ve used the method from this Post to draw text over my Asset png and then convert it into a Bitmap to be able to use them in the LRUcache. This works with a few images but as soon as I try and display all needed Images I get an OutOfMemory error. The Base Images are 200x200 px which I think should also be scaled to the need size depending on screensize and density.
First of, this method doesn’t seem efficient because I create a Bitmap canvas then make a LayerdDrawable which I make into a Bitmap (for caching) again. Not sure, but it just feels like I’m creating to much temp images which clutter up the memory. And then I’m using a BitmapDrawable which is depreciated. How would this method look without the BitmapDrawable??

Am I going about this the right way in general and How would I make this method efficiently so I don’t get the OOM error?!?

BTW. When I don’t use LRUcache and just return the LayerdDrawable for the GridView the images load fine but I get the Exception after a couple of Orientation changes! This is the method as I have it atm:

private Bitmap createIcon(Drawable backgroundImage, String text,
                          int width, int height) {

    String key = text.toLowerCase();
    Bitmap cachedBitmap = getBitmapFromMemCache(key);

    if (cachedBitmap != null){
        Log.d("TRACE", "is cached");
        return cachedBitmap;
    }
    else{

        Bitmap canvasBitmap = Bitmap.createBitmap(width, height,
                Bitmap.Config.ARGB_8888);
        Canvas imageCanvas = new Canvas(canvasBitmap);

        Typeface font = Typeface.createFromAsset(getActivity().getAssets(), "myriadpro.ttf");

        Paint imagePaint = new Paint();
        imagePaint.setTextAlign(Paint.Align.CENTER);
        imagePaint.setTextSize(26);//
        imagePaint.setTypeface(font);
        imagePaint.setAntiAlias(true);
        imagePaint.setColor(Color.parseColor("#562b12"));
        backgroundImage.draw(imageCanvas);

        imageCanvas.drawText(text, (width / 2)+4, (height / 2)-8, imagePaint);

        LayerDrawable layerDrawable = new LayerDrawable(
                new Drawable[]{backgroundImage, new BitmapDrawable(canvasBitmap)});
        int w = layerDrawable.getIntrinsicWidth();
        int h = layerDrawable.getIntrinsicHeight();

        Bitmap bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
        layerDrawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
        layerDrawable.draw(canvas);

        addBitmapToMemoryCache(key,bitmap);
        return bitmap;
    }
}

Update: I have tried with another method now, which seems better because it’s not using BitmapDrawable. But I still get OOM error. Also it generally doesn’t seem to realy use the cached images, when I change orientation only 1 or 2 images come from the cache.

I also failed to metion before the this is inside a Fragment. Not sure if it matters. But in portrait mode i have only this Fragment and in Landscape there can be another one if the width allows it.

public Bitmap drawTextToBitmap(Context mContext,  int resourceId,  String mText) {
    try {

        int memory = (int) (Runtime.getRuntime().freeMemory() / 1024);
        Log.d("TRACE", "memory " + memory);
        Log.d("TRACE", mText);

        String key = mText.toLowerCase();
        Bitmap cachedBitmap = getBitmapFromMemCache(key);

        if (cachedBitmap != null){
            Log.d("TRACE", "is cached");
            return cachedBitmap;
        }
        else{
        Resources resources = mContext.getResources();
        float scale = resources.getDisplayMetrics().density;
        Bitmap bitmap = BitmapFactory.decodeResource(resources, resourceId);

        android.graphics.Bitmap.Config bitmapConfig =   bitmap.getConfig();
        // set default bitmap config if none
        if(bitmapConfig == null) {
            bitmapConfig = android.graphics.Bitmap.Config.ARGB_8888;
        }
         bitmap = bitmap.copy(bitmapConfig, true); // OOE error happens here

        Canvas canvas = new Canvas(bitmap);
        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setColor(Color.rgb(110,110, 110));
        paint.setTextSize((int) (25 * scale));

        Rect bounds = new Rect();
        paint.getTextBounds(mText, 0, mText.length(), bounds);
        int x = (bitmap.getWidth() - bounds.width())/6;
        int y = (bitmap.getHeight() + bounds.height())/5;

        canvas.drawText(mText, x * scale, y * scale, paint);

        addBitmapToMemoryCache(key,bitmap);
        return bitmap;
        }
    } catch (Exception e) {
        // TODO: handle exception
        return null;
    }

}
Community
  • 1
  • 1
M4tchB0X3r
  • 1,531
  • 1
  • 15
  • 28
  • Use a HashMap> instead of the LRUCache. You can test this by altering your `addBitmapToMemoryCache()` and `getBitmapFromMemCache()` functios. – Sherif elKhatib Sep 18 '13 at 21:09
  • omg. that sounds so plausible that it actually could work. would this retain the states betwesen orientation changes?? – M4tchB0X3r Sep 19 '13 at 02:26
  • You can of course retain since Bitmap is Parcelable. However I would advice you in such case (when you are not downloading but just creating them) to use a static variable. It won't be a big deal. And will actually make your activity faster onPause/onResume. – Sherif elKhatib Sep 19 '13 at 05:30
  • @SherifelKhatib Only just got round to test this. It had the same behaviour at first, but then worked as soon as i made it static. I tried inside a test project but will transfer it to the actual project. If it holds up( wich i think it will) you could write it up as an answer so i can reward you the bounty! – M4tchB0X3r Sep 19 '13 at 18:46
  • Strange, its not working in the project. although it has the exact same free memory in the test and actual project. And from what i can see in the log it does come to the last image and then crashes. wired – M4tchB0X3r Sep 19 '13 at 19:18
  • it crashes because of OOM? – Sherif elKhatib Sep 19 '13 at 19:55
  • @SherifelKhatib Yes OOM. in this line `bitmap = bitmap.copy(bitmapConfig, true);` – M4tchB0X3r Sep 19 '13 at 20:35
  • Can you please replace this line with the function found in this answer (http://stackoverflow.com/a/9194259/833622)? Another thing, are you using big images? And, make sure your images have smaller versions in drawable-mdpi. – Sherif elKhatib Sep 19 '13 at 20:51
  • they are fairly big, 200x200px but anything under that looks washed out. also i'm testing on a hdpi device. was going to add smaler images for other densities. im using the function from the post now, but it still crashes but strange enough without an error!?!?! – M4tchB0X3r Sep 19 '13 at 21:10
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/37691/discussion-between-sherif-elkhatib-and-m4tchb0x3r) – Sherif elKhatib Sep 19 '13 at 21:30
  • 1
    I did not forget this. I just created my DraggableGridView and it is a real gridview with setAdapter and all the caching. It is here: https://github.com/sherifelkhatib/WidgyWidgets – Sherif elKhatib Sep 26 '13 at 09:04
  • You Sir, are amazing!! I hope i will have time this evening to test it. otherwise 2morrow. One question tho, does this support Android 2.3? – M4tchB0X3r Sep 26 '13 at 13:22
  • I think it supports 1.6 :P. But I just created it, so it needs some tweaking and maybe not the best. But i managed to scroll when you drag on the upper or lower edge. When the user releases the dragged view, you get a callback. So it is your job to really swap when he releases. Hope it works – Sherif elKhatib Sep 26 '13 at 13:55

1 Answers1

0

I worked on an app which needed to constantly hold 3-4 very large images in memory, and I was struggling with a problem very similar to yours. I loaded a bitmap from a byte array, then I needed to copy it to make it mutable, so that I could draw on it. This copy would cause there to be 1 too many bitmaps in memory, and then cause an OOM crash.

Eventually I found a way to load it once, and make it mutable at the same time:

Options options = new Options();
options.inMutable = true;
BitmapFactory.decodeResource(getResources(), id, options);

Use this, instead of copying the bitmap to make it mutable. I'm not sure if you really need the ARGB_8888 configuration, but if you don't, this should at least improve your memory efficiency.

Note: This will only work with Android 3.0 and above. For versions that need to run on 2.x and above, you can use a little reflection magic to see if the "inMutable" field exists in runtime. While this won't help on pre-3.0 devices, it will still provide a good improvement for most devices (and I've also noticed that devices running 2.x tend to have more memory flexibility).

Here's the code:

Options options = new Options();
options.inPurgeable = true;
options.inInputShareable = true;

Bitmap mutableBitmap = null;

try
{
    Options.class.getField("inMutable").set(options, Boolean.TRUE);
    Bitmap decodedBitmap = BitmapFactory.decodeResource(getResources(), id, options);                       
    mutableBitmap = decodedBytes;

}
catch (NoSuchFieldException noFieldException)                       
{
    Bitmap decodedBitmap  = BitmapFactory.decodeResource(getResources(), id, options);                              
    mutableBitmap = decodedBitmap .copy(decodedBitmap .getConfig(), true);
    decodedBitmap .recycle();                                                       
}
Gil Moshayof
  • 16,633
  • 4
  • 47
  • 58
  • Pity. most devices are running 4.x... you barely see 2.x being used these days, but I suppose a requirement is a requirement. One thing you should keep in mind though, is to use the bitmap.recycle() command when you don't need a bitmap anymore. The GC doesn't release the entire bitmap when it clears the reference, since the bitmap's data is stored on native memory. The full bitmap will be cleared with some delay. Call recycle will ensure it's removed immediately. – Gil Moshayof Sep 25 '13 at 23:21