Creating bitmap from xml layouts, off the ui thread - android

I'm trying to create multiple bitmaps, one for each map marker.
Since it happens on the ui thread, the ui freezes for a moment..
Is there a way to create a bitmap using a layout xml on a background/worker thread?
I know that It's not recommended, but I'm not sure how to tackle this issue.
If there's a way to create a designed bitmap not using my current method,
i'll be glad to hear..
Thanks
#Override
public View onCreateView(LayoutInflater inflater,
#Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
...
mMarkerContainer = (ViewGroup) LayoutInflater.from(
getActivity()).inflate(R.layout.map_text_marker, null);
mMarkerNameTv = (TextView) mMarkerContainer
.findViewById(R.id.map_marker_name);
...
return super.onCreateView(inflater, container, savedInstanceState);
}
public Bitmap createMarkerBitmap(int markerRes, String markerName) {
mMarkerNameTv.setText(markerName);
mMarkerContainer.setBackground(mBitmapDrawables.get(markerRes));
int measureSpec = View.MeasureSpec.makeMeasureSpec(0,
View.MeasureSpec.UNSPECIFIED);
mMarkerContainer.measure(measureSpec, measureSpec);
int measuredWidth = mMarkerContainer.getMeasuredWidth();
int measuredHeight = mMarkerContainer.getMeasuredHeight();
mMarkerContainer.layout(0, 0, measuredWidth, measuredHeight);
Bitmap resultBitmap = Bitmap.createBitmap(measuredWidth,
measuredHeight, Bitmap.Config.ARGB_8888);
resultBitmap.eraseColor(Color.TRANSPARENT);
Canvas canvas = new Canvas(resultBitmap);
mMarkerContainer.draw(canvas);
return resultBitmap;
}

Is there a way to create a bitmap using a layout xml on a background/worker thread?
AFAIK, working with widgets on a background thread is not a problem, so long as they are not connected to any window (e.g., they are not part of an activity or dialog). Inflating a layout might be a problem -- I seem to recall running into that with instrumentation tests, which do not run on the main application thread. But you're welcome to try putting your inflate() call and all of the createMarkerBitmap() logic into a background thread.
However:
Creating and populating widgets normally does not take much time. You may be better served using Traceview to determine why yours is taking so long.
If the issue isn't that an individual bitmap is slow, but that you are creating two tons of bitmaps, that will be a problem regardless of how you do it. Just because the work is done on a background thread does not make it "free" from a CPU standpoint, plus there are memory pressures to consider.
If there's a way to create a designed bitmap not using my current method, i'll be glad to hear.
You could draw directly to the Canvas using the methods available on Canvas.

Generally, you can use an extension of AsyncTask, like this:
private class MyAsyncTask extends AsyncTask<Integer, Void, Bitmap> {
protected Long doInBackground(Integer... bitmapID) {
return loadYourBitmap(bitmapID[0]); //<-happens in background
}
protected void onPostExecute(Bitmap yourBitmap) {
setSomethingTo(yourBitmap); //<- happens in foreground when doInBackground is done
}
}

One possible solution to this problem is to cache the bitmaps. When you want to display multiple markers as bitmaps. Simple [LruCache][1] - based in-memory cache would work just fine. The only thing that is needed to be taken care of is how much memory you are using for caching. Have a look at this official Google docs to know more about caching in bitmaps.
You can cache BitmapDescriptor for every unique bitmap you have. This way you can get some extra performance by avoiding making calls to BitmapDescriptorFactory every time you need to create a marker.
Here is the sample code:
LruCache<String, BitmapDescriptor> cache;
private void initCache()
{
//Use 1/8 of available memory
cache = new LruCache<String, BitmapDescriptor>((int)(Runtime.getRuntime().maxMemory() / 1024 / 8));
}
private void addMarker(LatLng position, String assetPath)
{
MarkerOptions opts = new MarkerOptions();
opts.icon(getBitmapDescriptor(assetPath));
opts.position(position);
mMap.addMarker(opts);
}
private BitmapDescriptor getBitmapDescriptor(String path) {
BitmapDescriptor result = cache.get(path);
if (result == null) {
result = BitmapDescriptorFactory.fromAsset(path);
cache.put(path, result);
}
return result;
}

Related

View Pager Memory Leak with Bitmaps and Volley

I'm using View Pager to show images which are downloaded from the network in my application. The number of images could be from 5 to 20. I'm using Volley library to do the network operations. The app wasn't taking much memory before but now after adding the view pager, the app takes a lot of memory and every time i open this activity, the memory used in heap increase (checked from the log messages). I also used Eclipse Memory analyzer to check where the leak was and it is definitely the bitmaps and the multiple instances of this activity. There is definitely a leak, as this activity isn't getting GC'ed, some references are keeping this from getting garbage collected. I've added my implementation of the view pager here.
public class ViewPagerAdapter extends PagerAdapter {
Context context;
public ViewPagerAdapter(Context context) {
this.context = context;
}
#Override
public int getCount() {
return photoReferences.size();
}
#Override
public boolean isViewFromObject(View view, Object object) {
return view == ((RelativeLayout) object);
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
final ImageView im;
final ProgressBar pb;
View itemView = inflater.inflate(R.layout.place_photos_item, container, false);
im = (ImageView) itemView.findViewById(R.id.placeImage);
attributes = (TextView) itemView.findViewById(R.id.placeAttributes);
pb = (ProgressBar) itemView.findViewById(R.id.progressBarPhoto);
imageLoader.get(url, new ImageListener() {
public void onErrorResponse(VolleyError arg0) {
im.setImageResource(R.drawable.onErrorImage);
}
public void onResponse(ImageContainer response, boolean arg1) {
if (response.getBitmap() != null) {
im.startAnimation(AnimationUtils.loadAnimation(context, android.R.anim.fade_in));
im.setImageBitmap(response.getBitmap());
pb.setVisibility(View.GONE);
}
}
});
((ViewPager) container).addView(itemView);
return itemView;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
((ViewPager) container).removeView((RelativeLayout) object);
}
}
Also, I'm using the Bitmap Cache of size 3 times the number of screenBytes(screenWidth * screenHeight * 4). I'm testing on Nexus 4 running 4.3 and I never run into a OOM exception cause the heap size is huge on this device but the app can take more than 100 mb of memory(it will crash on most devices) if I open the activity again and again, and before it used to take around 16-20 mbs of memory no matter what. Here's the cache code.
public class BitmapCache extends LruCache<Object, Object> implements ImageCache {
public BitmapCache(int maxSize) {
super(maxSize);
}
#Override
public Bitmap getBitmap(String url) {
return (Bitmap) get(url);
}
#Override
public void putBitmap(String url, Bitmap bitmap) {
put(url, bitmap);
}
}
Could anyone please suggest me what should I do to catch the leak? Is there anything wrong in the View Pager or my Volley usage? I'm not happy with the transition of the Pager as well, lags a bit, is that related?
Update: Here's the screenshot of MAT, possible leak. This is on every activity that uses Volley library. I've been reading a lot but I couldn't solve the problem. Is volley causing leak or am I doing something terribly wrong?
You can find your leak by using MAT. First you run your app and leak a few activity instances. Then you grab a snapshot of the heap and look for those leaked Activity objects... you can use 'Object Query Language' (OQL) to find them by type (e.g. "SELECT * FROM com.foo.FooActivity").
Once you've found a leaked object, right-click on it and ask MAT to trace all its incoming references back to their GC roots. The leaked reference will be one of those.
For a better introduction to the technique you could try this article:
http://android-developers.blogspot.co.uk/2011/03/memory-analysis-for-android.html
I guess you are using using Viewpager and Imageviews
About image views you are using powerful image downloading and caching library like latest Volley Imageloading(really helpful for large size images) to improve the image loading capabilities in a efficient way.
About Viewpager you have to use efficient adapter FragmentStatePagerAdapter:
This version of the pager is more useful when there are a large number of pages, working more like a list view. When pages are not visible to the user, their entire fragment may be destroyed, only keeping the saved state of that fragment. This allows the pager to hold on to much less memory associated with each visited page as compared to FragmentPagerAdapter at the cost of potentially more overhead when switching between pages.
please think before you are using FragmentPagerAdapter becouse it stores the whole fragment in memory, and could increase a memory overhead if a large amount of fragments are used in ViewPager. In contrary its sibling, FragmentStatePagerAdapter only stores the savedInstanceState of fragments, and destroys all the fragments when they lose focus. Therefore FragmentStatePagerAdapter should be used when we have to use dynamic fragments, like fragments with widgets, as their data could be stored in the savedInstanceState. Also it wont affect the performance even if there are large number of fragments. In contrary its sibling FragmentPagerAdapter should be used when we need to store the whole fragment in memory. When I say the whole fragment is kept in memory it means, its instances wont be destroyed and would create a memory overhead. Therefore it is advised to use FragmentPagerAdapter only when there are low number of fragments for ViewPager. It would be even better if the fragments are static, since they would not be having large amount of objects whose instances would be stored. Hope this clears out the difference between Android FragmentPagerAdapter and FragmentStatePagerAdapter.
Try to learn Google android gallary app example, use image view loading animations to make a great user experience.
I hope this will solves your grow heap problems.
Credits:FragmentPagerAdapter vs FragmentStatePagerAdapter
You forget to recycle your downloaded Bitmaps as they become unneeded.
Basically, every Bitmap you handle manually, you have to recyle().
That being said, your destroyItem() method should look something like this:
public void destroyItem(ViewGroup container, int position, Object object) {
RelativeLayout rl = (RelativeLayout) object;
ImageView im = rl.findViewById(R.id.image_view);
bitmapDrawable = (BitmapDrawable) im.getDrawable();
if (bitmapDrawable != null && bitmapDrawable.getBitmap() != null) {
bitmap = bitmapDrawable.getBitmap();
bitmap.recycle();
}
container.removeView(rl);
}
You should check out the new version of Volley , old version did cause the leak problem.
In old version ,Volley has 4 thread do request , And each of them will keep a request , and request keep strong reference of listener , and your response listener do something with the ImageView , ImageView keep the Activity context. so all of your View is leaked.
In MAT use select * from instanceof android.app.Activity you will see your Activity is leaked.
New Version of Volley has fixed this problem . please check out here
And use this will help your find out your leaked Activity , leakcanary

Caching Images - Bitmap not collected after different bitmap was loaded or ImageView's activity closed

I am trying to implement something that is very similar to UrlImageViewHelper (https://github.com/koush/UrlImageViewHelper) where you can easily using a simple one line of code, load images from a url, and if the image was already downloaded it is loaded from the cache instead. The major difference is that I want the same effect but instead of downloading it from a url, I want to receive the images from my own server using my own client-server communication. Every image on my server can be uniquely identified by a string, and I use this as the id for the image.
My main idea was this: Use an LRU Cache to hold the images, but instead of holding the Bitmaps (that are very large) I want to hold the raw image data binary, so I can use the same image to build bitmaps of different sizes and qualities on demand depending on the specific situation.
This is my implementation so far:
public class ImageHandler {
private static class BitmapCache extends LruCache<String, byte[]>
{
public WigoBitmapCache(int maxSize) {
super(maxSize);
}
#Override
protected int sizeOf(String key, byte[] value) {
return value.length;
}
}
private static class ImageHandlerThread extends Thread
{
/* THIS THREAD WILL DECODE THE IMAGE AND SET THE BITMAP TO THE IMAGEVIEW IN THE BACKGROUND */
Activity activity;
ImageView imageView;
byte[] imageBytes;
public ImageHandlerThread(Activity activity, ImageView imageView, byte[] imageBytes)
{
this.activity=activity;
this.imageView=imageView;
this.imageBytes=imageBytes;
}
public void run() {
BitmapFactory.Options o = new BitmapFactory.Options();
o.inJustDecodeBounds = true;
BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length, o);
int factor1=o.outHeight/height;
int factor2=o.outWidth/width;
/* height and width are for now constant */
o = null;
o = new BitmapFactory.Options();
if (factor1>factor2)
o.inSampleSize=factor1;
else
o.inSampleSize=factor2;
Bitmap bit = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length,o);
setBitmap(bit);
bit = null;
}
private void setBitmap(final Bitmap bit) {
activity.runOnUiThread(new Runnable() {
#Override
public void run() {
imageView.setImageBitmap(bit);
}
});
}
}
private static class QueueItem
{ /*USED TO HOLD INFO ABOUT THE IMAGE REQUEST UNTIL THE IMAGE GETS FROM THE SERVER */
String imageName;
Activity activity;
ImageView imageView;
public QueueItem(String imageName, Activity activity, ImageView imageView)
{
this.imageName=imageName;
this.activity = activity;
this.imageView = imageView;
}
}
private BitmapCache cache; // this cache holds the image binaries
private ArrayList<QueueItem> queue; // this queue holds the info about the request, until the server sends the image
public ImageHandler(int maxSize)
{
cache=new BitmapCache(maxSize);
queue = new ArrayList<QueueItem>();
}
public synchronized void setBitmap(Activity activity, ImageView imageView, String imageName)
{
byte[] imageBytes = cache.get(imageName);
if (imageBytes==null)
{
QueueItem item = new QueueItem(imageName, activity, imageView);
queue.add(item);
/* HERE IS THE CODE TO RETRIEVE THE IMAGE BINARY FROM MY SERVER, THIS CODE WORKS FINE, SO THERE IS NO REASON TO BOHER YOU WITH IT */
}
else
{
ImageHandlerThread thread = new ImageHandlerThread(activity, imageView, imageBytes);
thread.start();
}
}
public synchronized void insert (String imageName, byte[] imageBytes)
{
/* THIS METHOD IS THE CALLBACK THAT IS CALLED WHEN THE IMAGE BINARY IS RECEIVED FROM THE SERVER */
cache.put(imageName, imageBytes);
for (QueueItem item: queue)
{
if (item.imageName.equals(imageName))
{
ImageHandlerThread thread = new ImageHandlerThread(item.activity, item.imageView, imageBytes);
thread.start();
queue.remove(item);
}
}
}
}
Basically, the main method here is setBitmap(), it gets the activity, the imageView that needs the bitmap, and the name of the image name. If the image is already in the cache, a new thread is started to decode the bytes into the proper size bitmap and set the bitmap to the imageView. If the image is not present in the cache, the request is put in a queue until the image is received, the image is retrieved from the server and then the same thread as before is started.
All this works absolutely fine, the problem is that when the imageView is set another bitmap for the image or even when the activity is destroyed, the bitmap is still resident in memory and is not collected by the GC.
At first I though that it is because I was keeping a reference to the activity, and that reference keeps the activity alive, but it seems not to be the case, my reference to an activity is very short lived, and once the image arrives from the server this reference is cleared.
I am running out of memory fast with this implementation, and I have no idea why or what to do to fix it. The bitmaps I create are not collected although I keep no references to them. Could this be an artifact of the way I decode the images? or do the threads keep references that are not collected properly? Anyone has any ideas?
Ok, so I misunderstood the problem, what was happening is that the GC was not collecting the finished thread objects because I was running this in debug mode, and not in run mode. When I ran this in run mode, the memory usage of my app did not go above 8MB at all, while when it was in debug mode I got to the 25MB+ area.
Conclusion: Do not trust the GC and memory usage info in debug mode, especially if you have many short term threads running.

Replacing call to setImageResource() with setImageBitmap to reduce hiccup?

Android's setImageResource() documentation states:
This does Bitmap reading and decoding on the UI thread, which can
cause a latency hiccup. If that's a concern, consider using
setImageDrawable(android.graphics.drawable.Drawable) or
setImageBitmap(android.graphics.Bitmap) and BitmapFactory instead.
I'm looking to solve this exact issue in my example application. I think I'm supposed to "read between the lines" here and and replace my call from :
((ImageView) rootView.findViewById(R.id.screen_image)).setImageResource(imageId);
to instead call
InputStream is = this.getResources().openRawResource(imageId);
Bitmap imageBitmap = BitmapFactory.decodeStream(is);
((ImageView) rootView.findViewById(R.id.screen_image)).setImageBitmap(imageBitmap);
but do this from within another thread, by using something like an AsyncTask -
Am I correct in my understanding, or is there a simpler solution?
Update:
It looks like there is an entire section on displaying bitmaps efficiently on developer.android.com - I'm investigating that now.
The simplest way to solve this issue is create a worker thread as described in the thread & process documentation. As it mentions, async task can be used to replace this for more complex code. the final solution was to change my code in the OnCreateView() method, replacing the original laggy code:
final View rootView = inflater.inflate(R.layout.fragment_screen, container, false);
int i = getArguments().getInt(ARG_PANEL_NUMBER);
String panel = getResources().getStringArray(R.array.panel_array)[i];
int imageId = getResources().getIdentifier(panel.toLowerCase(Locale.getDefault()),
"drawable", getActivity().getPackageName());
((ImageView) rootView.findViewById(R.id.screen_image)).setImageDrawable(getResources().getDrawable(imageId));
with a new background thread:
final View rootView = inflater.inflate(R.layout.fragment_screen, container, false);
// Load image in a separate thread to ensure navigation drawer animation is smooth.
// Replace with Async Task if necessary
new Thread(new Runnable() {
public void run() {
int i = getArguments().getInt(ARG_PANEL_NUMBER);
final String panel = getResources().getStringArray(R.array.panel_array)[i];
int imageId = getResources().getIdentifier(panel.toLowerCase(Locale.getDefault()),
"drawable", getActivity().getPackageName());
InputStream is = getActivity().getResources().openRawResource(imageId);
final Bitmap imageBitmap = BitmapFactory.decodeStream(is);
rootView.post( new Runnable() {
public void run() {
((ImageView) rootView.findViewById(R.id.screen_image)).setImageBitmap(imageBitmap);
getActivity().setTitle(panel);
}
});
}
}).start();
As described in the documentation above, using an async task is more extensible and maintainable if this code grows to be too large.
setImageResource() method is bit slow because it does decoding on UI thread, We can solve this problem using worker thread or AsyncTaks, but a simple and ready made solution is available for that. Which is Glide library. You just need to add it's dependency to your gradle file as:
compile 'com.github.bumptech.glide:glide:3.7.0'
and then set drawable image using it's id as:
Glide.with(context).load(bImage).into(tipImages);
Also may be other image libraries like Picasso provide this option, but i used Glide here.
I wouldn't read into it so much. The way I interpret that is to just use:
setImageDrawable(getResources().getDrawable(R.id.yourid);
I do see another issue in your code that may be causing hiccups. You are calling rootView.findViewById. FindViewById is an expensive call, so you should only do it once. If this code is all happening in your constructor then it's ok, but if it's in a List adapter update row method for example, this could cause lag! Do your findViewById once and assign the view to a member variable.

Android: Declaring static bitmaps? Yay or nay?

Somebody recently commented on my code where I declare the following:
private Bitmap splashBackground;
private Bitmap lightDot;
private static Bitmap scaledBackground;
private static Bitmap scaledLightDot;
They advised me against declaring satic Bitmaps.
However, I've tried everything and my code doesn't work unless I declare them as static.
Also, "public static Bitmap createScaledBitmap (Bitmap src, int dstWidth, int dstHeight, boolean filter)" seems to appear on the official Android Developer site so I'm a bit confused about what I should and shouldn't be doing.
Any pointers would be appreciated - thank you
Edit: For clarification:
When I remove the static from my declaration, then by the time I get to my onDraw() method, the scaled bitmap is null. (I am creating the scaled bitmap object in an initialise() method and once it's been created, it is valid (ie, not null) - but then seems to become null at onDraw unless I declare it as static.
I am calling my initialise() method from my activity class.
Edit: More code as requested.
My OnCreate method: As you can see, I'm passing my screen height and width over so I can create my scaled bitmaps
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
displaySplashScreen= new SplashScreen(this);
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
// set View
setContentView(new SplashScreen(this));
WindowManager w = getWindowManager();
Display d = w.getDefaultDisplay();
int width=d.getWidth();
int height=d.getHeight();
displaySplashScreen.initialise(width, height);
}
My initalise method:
public void initialise(int w, int h)
{
//Get width and height (passed in from Activity)
vwidth=w;
vheight=h;
//Create pre-scaled bitmaps
scaledBackground = Bitmap.createScaledBitmap(splashBackground, vwidth, vheight, true);
scaledLightDot = Bitmap.createScaledBitmap(lightDot, vwidth, vheight, true);
}
I could add also that if I use a standard variable in the same way (say int number;) and set it in initalise (number = 5;), then number is only equal to 5in my initialise method. If I log it from onDraw() it will always repeatedly return '0'!! It's baffling.
Thanks everyone so far, please let me know if more code is required......
In general, utilizing static for Bitmaps is a very bad idea. There are a lot of good reasons for this, mostly having to do with avoiding memory leaks.
Also, "public static Bitmap createScaledBitmap (Bitmap src, int dstWidth, int dstHeight, boolean filter)" seems to appear on the official Android Developer site ...
This is not a static Bitmap. This is a method call to a class method. Static does not work the same and the return type (Bitmap) is not static. What this means is that the method is static and does not require an instance to be called. It will return a Bitmap to be placed in an appropriate variable of your choice.
I am calling my initialise() method from my activity class.
This statement is quite unhelpful. From where in the class is it being called? Is it in onCreate(), onStart(), onResume(), some other custom method? Where and when you choose to do certain things can have a huge effect as to how successful they are.
... but then seems to become null at onDraw unless I declare it as static.
This could be for several possible reasons, and since we don't have any of your code there really isn't a qualified answer. Here are some things to look at.
This could be because the Activity is getting recreated.
This could also be because some method that seems unrelated is actually getting called. This would probably be somewhere that you are manually setting it to null.
This might be due to improper use of the createScaledBitmap() method.
The Bitmap might be getting recycled due to low memory (this actually happens more often than one would think)
EDIT: After reviewing your code
This looks like it may be the culprit. Above, you have...
displaySplashScreen= new SplashScreen(this);
Below, you add...
setContentView(new SplashScreen(this));
This means that you are creating two Splashscreens. One reason why you are getting a null pointer when you are not using static may be because further down the line you use...
displaySplashScreen.initialise(width, height);
But since your contentView is set to a new SplashScreen, you are not actually utilizing that View. To resolve this, make sure you are talking to the same view object. I.E.
setContentView(displaySplashScreen);
This will at least make sure you are looking at the same object. It is possible that you may have to reorganize a bit depending upon what other things are happening. For instance,
setContentView(displaySplashScreen);
... may have to appear below ...
displaySplashScreen.initialise(width, height);
This is something that you might have to toy with, but I don't see anything else that gives any other immediate indication. Be aware that resolving nullpointerexceptions will often result in revealing more errors in code, at first. Stay the course and resolve each in order.
This line is wrong:
// set View
setContentView(new SplashScreen(this)); // This line is wrong.
Should be this:
// set View
setContentView(displaySplashScreen); // displaySplashScreen is created earlier.
You are created two instances of SplashScreen. You should keep using the same instance.
I vote for nay,
this one is a static variable
private Bitmap splashBackground;
private Bitmap lightDot;
private static Bitmap scaledBackground;
private static Bitmap scaledLightDot;
and this one is a static method
public static Bitmap createScaledBitmap (Bitmap src,
int dstWidth, int dstHeight, boolean filter)
static variable is usually declared for a constant and the variable belongs to class not object as an example if you have a class
public class car {
private static colorOfCar;
private numberOfDoor;
}
let's say you have a class car and have 2 variable colorOfCar and numberOfDoor when you create an object porsche and ferrari from car class if you change the number of door it's OK the numberOfDoor in your porsche object and ferrari object will be different, but if you change the colorOfCar both porsche object and ferrari object colorOfCar will be changed because the colorOfCar is a static object that belong to the class not the object.
I hope you understand my explanation. If you find my answer helping you please vote and accept my answer and if you have any other question feel free to ask in the comment, thank you :)
Without your code, it seems you are using your view is being created more than once. Maybe in two instances of the view, or maybe the same view being recreated (Activity restarting). In the first time around, initialize() is being called before onDraw(), making your static Drawable initialized and valid. The second time around, onDraw() is running before initialize() (which works when Drawable is static). This is most likely due to you inflating the view and calling initialize() afterwards (this means the view is already in the layout). i.e.
public void onCreate(Bundle savedInstanceState) {
setContentView(R.layout.mylayout); //the view is added to layout, onDraw() may be called
MyView view = (MyView)findViewById(R.id.myview);
view.initialize(); //initializing the drawable from null
//no guarentee that initialize was called before onDraw()
}
This runs ok when your Drawable is static because when the second view is drawn, it is using the same Drawable which was initialized by the first view. When you remove the static, you need to make sure initialize is always called before onDraw().
Instead of calling initialize() from your activity, why not invoke it from the View's constructor? I often use the following pattern:
public class MyView extends View {
private Bitmap splashBackground;
private Bitmap lightDot;
private Bitmap scaledBackground;
private Bitmap scaledLightDot;
public MyView(Context context) {
super(context);
init();
}
public MyView(AttributeSet attr, Context context) {
super(attr, context);
//parse attr for xml attributes
init();
}
private void init() {
splashBackground = getResources().getDrawable(R.drawable.splash_background);
lightDot = getResources().getDrawable(R.drawable.light_dot);
scaledLightDot = Bitmap.createScaledBitmap(lightDot, getDPI(32), getDPI(32), false);
}
public void onSizeChanged(int width, int height) {
scaledBackground = Bitmap.createScaledBitmap (splashBackground, width, height, false);
}
/**
* Convert pixel value to device independent pixels (DPI)
* #params pixels Value for pixel size for MDPI screens
*/
private int getDPI(int pixels) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, pixels, getResources().getDisplayMetrics());
}
public void onDraw(Canvas canvas) {
//non-static drawables are guaranteed to be not-null
canvas.draw(scaledBackground, 0, 0, null);
canvas.draw(scaledLightDot, 10, 10, null);
}
}
You'll be all set

Android maps zoom - OutOfMemory

When I zoom in/zoom out from time to time in my map activity I get in console an "OutOfMemory error:bitmap size exceeds VM budget". The app doesn't crash on my developement phone, but I'm not sure If this issue won't cause a problem in future.
Note: I display maximum 30 custom markers on map.
The error stack doesn't reference my code. Does anyone have a fix or a best practice to get rid of this error?
Thanks in advance.
public void onCreate(Bundle savedInstanceState) {
...
marker = getResources().getDrawable(R.drawable.marker);
...
fillData();
}
public void fillData() {
...
for (int i = 0; i < lats.length; i++) {
...
map.getOverlays().add(new ContactOverlay(marker, tempLat, tempLon, names[i],
phones[i]));
}
}
private class ContactOverlay extends ItemizedOverlay<OverlayItem> {
private List<OverlayItem> items = new ArrayList<OverlayItem>();
public ContactOverlay(Drawable marker, double latitude,
double longitude, String title, String snippet) {
super(marker);
boundCenterBottom(marker);
items.add(new OverlayItem(getPoint(longitude, latitude), title,
snippet));
populate();
}
#Override
protected OverlayItem createItem(int i) {
return (items.get(i));
}
...
}
Try to reuse the bitmap and the canvas where you draw. Each time when you redraw the overlay, a new bitmap is allocated.
#dorin, I post an answer because the answer is too long.
I think it is ok, but this is not the problem.
Each time when you redraw the overlay a new bitmap is created.You can not see it, because this happens in onDraw method and the overlay do it. So, lets imagine you redraw the overlay 10 times. 10 bitmaps are created. If the GC do not clean this 10 bitmaps, when you create the next one, the OS is trying to allocate memory for new bitmap. If there is not enough memory in the heap, an exception is thrown and then GC frees the memory that was allocated for the previous 10 bitmaps. The thing you have to do is to avoid this bitmap memory allocation. Create base overlay class and extend the ItemizedOverlay. Override the onDraw() Method and create a bitmap field. Each time onDraw() is called, recycle the bitmap and draw again on it. On this way you avoid a new bitmap recreation -> OutOfMemory exception too

Categories

Resources