Weird Android onDraw() Bug

Sysem

Expert Member
Joined
Mar 26, 2009
Messages
1,891
Reaction score
5
I'm now completely stumped. I have spent many hours, yet I cannot solve this. I have a view that I'm adding some text to, as well as a gradient rectangle (fades from an alpha grey to completely alpha). The first If Statement is for drawing the text and rectangle on the bottom left hand side, and the second is for the bottom right hand side (depending on the condition obviously).

This is the code:
Code:
if (mCurrentPage - 1 >= 0) {
	teams = ((MainActivity)getContext()).cricketGames.get(mCurrentPage - 1).getHomeCode() + " vs " +
        	((MainActivity)getContext()).cricketGames.get(mCurrentPage - 1).getAwayCode();
        offset1 = mTeam1TextPaint.measureText(teams);
        textShader1 = new LinearGradient(0, 0, 0 + offset1 + offset1, dY, Color.argb(100, 160, 160, 160), Color.alpha(0), Shader.TileMode.CLAMP);
        mTeam1BackPaint.setShader(textShader1);
        	
        bg1.set(0, 0, (int)(0 + offset1 + offset1), maxY);
        canvas.drawRect(bg1, mTeam1BackPaint);
        canvas.drawText(teams, 0 + 8, dY + 5, mTeam1TextPaint);
}
        
if (mCurrentPage + 1 < count) {
	teams = ((MainActivity)getContext()).cricketGames.get(mCurrentPage + 1).getHomeCode() + " vs " +
        	((MainActivity)getContext()).cricketGames.get(mCurrentPage + 1).getAwayCode();
        offset2 = mTeam2TextPaint.measureText(teams);
            
        textShader2 = new LinearGradient(maxX, 0, maxX - offset2 - offset2, dY, Color.argb(200, 160, 160, 160), Color.alpha(0), Shader.TileMode.CLAMP);
       	mTeam2BackPaint.setShader(textShader2);
        	
        bg2.set(maxX, 0, (int)(maxX - offset2 - offset2), maxY);
        canvas.drawRect(bg2, mTeam2BackPaint);
        canvas.drawText(teams, maxX - offset2 - 8, dY + 5, mTeam2TextPaint);
}

It works as desired on the emulator. No issues. But if I run the same code on a device, it NEVER draws the bottom right hand side gradient. It draws the text, but not the rectangle...

Why on earth is it working for the emulator, and not on devices? I've tried StackOverflow, but nobody is replying...

EDIT: Sorry for the poor formatting, this might be easier to read
 
Last edited:
Ok, finally got it. Because object creation is not recommended during onDraw(), I tried to move the creation to somewhere else, but that didn't work. So i called

Code:
System.gc();

and its solved the issue.

What a relief.
 
Your problem I believe could be a structural one; but without seeing more of your code it's difficult to be absolutely sure.

Firstly it's never a good idea to call System.gc(); any app that needs it to work is usually a good indicator of a problem with the code architecture.

Secondly you should design your app to perform all the allocations and updates outside of onDraw; onDraw in general should be designed to be very lean + the overall design should avoid unnecessary calls to run onDraw (performance reasons)

Updating views is usually done by updating properties underpinning your custom view, followed by calling invalidate() -- the view must be invalidated after any change to its properties that might change its appearance, so that the system knows that it needs to be redrawn. Similarly you would also call requestLayout(), if a property changes that will affect the size or shape of the view.

Just remember try to limit the number of times you call these, as frequent use is going to significantly slow down your UI. Try to design your code so that it's called only once for a group of updates.

I'd suggest you also read the following links for additional help / information:
http://developer.android.com/training/custom-views/create-view.html
http://developer.android.com/training/custom-views/optimizing-view.html
http://stackoverflow.com/questions/2414105/why-is-it-a-bad-practice-to-call-system-gc

Ps. The reason System.gc() seemed to have resolved your problem is because as part of the garbage collection, the view is typically invalidated; but as mentioned in the last link, you cannot rely on the behavior of System.gc(), and frequent calls to it can slow down your overall app, cause stutters, ...
 
Last edited:
The reason I can't update the values outside the OnDraw is because it uses values based on the canvas. And you can't preallocate and reuse a LinearGradient object, forcing me to recreate it everytime. I understand calling System.gc() is a short cut, but theres no harm in calling it as the OS will still have the final say of when GC is called.

So for this particular problem, it works. Within the view I already have methods to update the content, the invalidate() and redraw, but because the LinearGradient uses specific canvas coordinates, it was a little tricky.
 
The reason I can't update the values outside the OnDraw is because it uses values based on the canvas. And you can't preallocate and reuse a LinearGradient object, forcing me to recreate it everytime. I understand calling System.gc() is a short cut, but theres no harm in calling it as the OS will still have the final say of when GC is called.

So for this particular problem, it works. Within the view I already have methods to update the content, the invalidate() and redraw, but because the LinearGradient uses specific canvas coordinates, it was a little tricky.
I beg to differ, for example:
I'd also recommend you look at also moving any other allocations outside of onDraw, to facilitate preallocation and reuse, for example new Paint()

PS. the harm with System.gc() is that its forcing you to take shortcuts and these shortcuts will only end up frustrating you further down the line.
 
Last edited:
[)roi(];11694227 said:
I beg to differ, for example:
I'd also recommend you look at also moving any other allocations outside of onDraw, to facilitate preallocation and reuse, for example new Paint()

PS. the harm with System.gc() is that its forcing you to take shortcuts and these shortcuts will only end up frustrating you further down the line.

True, but thats not really reusing. You're still creating a new object every time onBoundsChanged is called. Unlike Rect() where you can change the values of the object. I'm going to try move it there as its called a lot less, but It's quite a mission as I'm extending someone else's class that had all the calculations in onDraw().But for now, LinearGradient is the only object being recreated in the onDraw(), the rest are preallocated and the System.gc() is called once, and considering its a really small feature I won't really have any issues in the future.

I do agree though, using the GC manually should be a last resort. Thanks for the useful links though!
 
True, but thats not really reusing. You're still creating a new object every time onBoundsChanged is called. Unlike Rect() where you can change the values of the object. I'm going to try move it there as its called a lot less, but It's quite a mission as I'm extending someone else's class that had all the calculations in onDraw().But for now, LinearGradient is the only object being recreated in the onDraw(), the rest are preallocated and the System.gc() is called once, and considering its a really small feature I won't really have any issues in the future.

I do agree though, using the GC manually should be a last resort. Thanks for the useful links though!

Agreed, always a challenge picking up someone else's coding.

On reuse. as you indicated the recreation is going to occur less re the bounds don't typically change as frequently as the onDraw event is fired.

Good luck with the enhancements.
 
Top
Sign up to the MyBroadband newsletter
X