Sunday, December 27, 2009

Dynamically Retrieving Resources in Android

Normally, when retrieving resources (drawables, strings, what have you) in code you use the automatically generated R.java to do so. However, I recently wrote a reference application wherein each item in a ListView has a different icon next to it. The data for all this is stored in JSON as an asset, which meant that there was no way for me to link my data to R.java.

Still, I needed some way to get a hold of the Drawable by name, and so I first turned to Resources.getIdentifier(). This method does the job well of finding the resource id of anything you desire, in any package:

Resources r = getResources();
int drawableId = r.getIdentifier("drawableName", "drawable", "com.mypackage.myapp");

This is all well and good, but: getIdentifier() is a general tool and as a result, slow. Running through 10,000 iterations, it took about 3200 ms on my G1. Also, it requires that you pass around a Context (or a Resources) wherever you need to use getIdentifier() which is an annoyance and seems unnecessary given that you can access R.java just fine normally in code.

The better solution - as long as you are retrieving data from your application's own R.java - is to use reflection.

try {
Class res = R.drawable.class;
Field field = res.getField("drawableName");
int drawableId = field.getInt(null);
}
catch (Exception e) {
Log.e("MyTag", "Failure to get drawable id.", e);
}

In testing, the second method is about 5x faster than getIdentifier(). Not only that, but it means you don't have to pass a Resources object around to use it. Obviously this cannot be used if you're accessing resources outside of your application's permissions and you'll have to rely on getIdentifier() then.

One last note - simply linking to R.java is way faster than either of these two methods, so only fall back on these if you need to link to resource identifiers dynamically by name.

17 comments:

  1. Thanks a lot, you helped me on finding a solution that fitted my needs.

    Just note that in case you want to retrieve Views (Buttons, TextViews, etc.) you must implement R.id.class instead of R.drawable. It took me a while to find the error :)

    ReplyDelete
  2. I say thank you as well. It helped me very much

    ReplyDelete
  3. Thanx for the code, it helped me ver much....

    ReplyDelete
  4. it's reaaaaallly good...
    Thanks

    ReplyDelete
  5. thanks a lot .. it's cool and exactly what I was looking for

    ReplyDelete
  6. Yeah baby! Really you helped me! Thank you veeeery much :D

    ReplyDelete
  7. Thanks bro this helped me a lot.

    ReplyDelete
  8. Hi, thanx for the tutorial. I am having this error, can you help me out?


    09-18 20:46:05.960: E/MyTag(19018): java.lang.NoSuchFieldException: No field 'R.id.bShots' in class com.andrewschlie.bluelinestats.R$id


    But in R.java file, I have this -

    public final class R {
    .......
    public static final class id {
    ......
    public static final int bShots=0x7f080041;
    ......
    }
    }


    Here's my code that gets the error -

    String resourceName = "R.id.bShots";
    try {
    Class res = R.id.class;
    Field field = res.getField(resourceName);
    int resID = field.getInt(null);
    Log.d(TAG, "resID is " + resID);
    Button pn = (Button) findViewById(resID);
    pno.setOnClickListener(this);
    }
    catch (Exception e) {
    Log.e("MyTag", "Failure to get drawable id.", e);
    }

    ReplyDelete
  9. I got my problem -

    String resourceName = "R.id.bShots";

    it should be -
    String resourceName = "bShots";

    ReplyDelete
  10. Bingo! This is exactly what I needed. Time for this old C++ programmer to learn some reflection.

    ReplyDelete
  11. thank you man, you make my day ....

    ReplyDelete