I have a SurfaceView object with a draw method that gets called by 1 thread about 600 times from inside a for loop. No other thread calls draw during this time.
The draw method:
public void draw() {
if(this.surface_created){
long now = System.nanoTime();
Canvas canvas = holder.lockCanvas();
Log.d("GAME_SURFACE", "Time to lock:" + (System.nanoTime()-now));
if (canvas == null)
{
System.out.println("[GameSurface] Cannot draw onto canvas as it's null");
}
else
{
canvas.drawBitmap(this.bitmap, null, this.dest, null);
}
holder.unlockCanvasAndPost(canvas);
}
else{
System.out.println("[GameSurface] SURFACE NOT CREATED YET.");
}
}
The problem happens when i use holder.lockCanvas , which takes around 14 million nanoseconds. So to draw all bitmaps it ends up taking 13 seconds to do so. Why is it taking so long? Is there a workaround?
Related
I am pretty new to Android development and I am trying to make a game in which my character moves on tiles across the screen. Since each tile is a bitmap of its own I redraw it after the character has moved on it.
Now for some reason every time I draw the character sprite the entire view sort of flickers.
It seems like on every even draw the background I created is visible and on every odd draw the background is black. Also, the character sprite leaves a trail but only half of it is visible, depending on whether it's an even or an odd draw. My guess is that for some reason there are two views or something on which the canvas is drawing.
I would've uploaded images but I can't :(
Does anyone have a clue what I'm doing wrong? Thanks a lot for any kind of help.
Here is the method moving the character:
// Moves sprite on screen
private void MoveSprite()
{
// Run as long as the sprite's location didn't reach its destination
while (m_gameView.m_playerControl.m_MoveDestination.x != m_gameView.m_playerControl.m_CharSprite.m_SpriteLocationOnMatrix.x ||
m_gameView.m_playerControl.m_MoveDestination.y != m_gameView.m_playerControl.m_CharSprite.m_SpriteLocationOnMatrix.y)
{
// Draw sprite with FPS control
Canvas c = null;
startTime = System.currentTimeMillis();
try
{
c = m_gameView.getHolder().lockCanvas();
synchronized (m_gameView.getHolder())
{
// Draws player and refreshes the tiles it was previously on
m_gameView.m_playerControl.onDraw(c);
}
}
finally
{
if (c != null)
{
m_gameView.getHolder().unlockCanvasAndPost(c);
}
}
sleepTime = ticksPS - (System.currentTimeMillis() - startTime);
try
{
if (sleepTime > 0)
Thread.sleep(sleepTime);
else
Thread.sleep(10);
}
catch (Exception e)
{
}
}
// After drawing char movement, changing flag to false
m_fIsCharMoved = false;
Check out screen width and height based drawing.
you should draw inside the screen.
I think u r drawing the characters out of the screen width or height
I was trying to make moving bitmap with accelerometer smoother and accidentally noticed that when I call invalidate(); at the end of onDraw() method instead of calling it at the end of onSensorChanged() I get much smoother movement, even if I don't have any kind of low-pass filters. Then I tried to do the same with my LiveWallpaper, but as you know there is no onDraw() method in Engine of WallpaperService, but you have to create one yourself and call it for example with Handler. But doing it that way doesn't give any smoother result even if the rest of the code is same as in other programs.
This is the code that I use in my non-Wallpaper programs and it works fine:
public void onDraw(Canvas c) {
xPosition += xAcceleration;
yPosition += yAcceleration;
drawable = BitmapFactory.decodeResource(getResources(),R.drawable.ball);
c.drawBitmap(drawable, xPosition,yPosition, paint);
invalidate();
}
So I went and tried to create my own invalidate-like solution for WallpaperService and came up with this:
void drawFrame() {
final SurfaceHolder holder = getSurfaceHolder();
Canvas c = null;
try {
c = holder.lockCanvas();
if (c != null) {
xPosition += xAcceleration;
yPosition += yAcceleration;
background = BitmapFactory.decodeResource(getResources(),R.drawable.bg);
drawable = BitmapFactory.decodeResource(getResources(),R.drawable.ball);
c.drawBitmap(background, 0,0, null);
c.drawBitmap(drawable, xPosition,yPosition, null);
}
} catch (Exception ex){
}
holder.unlockCanvasAndPost(c);
drawFrame();
}
So what I am doing is:
Get Canvas.
Draw on Canvas.
Unlock Canvas and start over.
As I have understood this should give me invalidate();-like behaviour, but instead it tries to show wallpaper and after while it gives me StackOverflowError.
Ok I got this solved already. All I had to to was move bitmap initializations into onCreate() method.
I am writing a LiveWallpaper for Android and I want to have a Bitmap with a certain amount of opacity to show.
In the constructor of my LiveWallpaper Engine I set a Paint that I will use later on my Canvas:
MyEngine() {
...
mForeGroundPaint = new Paint();
mForeGroundPaint.setAlpha(5);
}
I draw the Bitmap in this function, using the mForeGroundPaint on the drawBitmap():
void drawFrame() {
final SurfaceHolder holder = getSurfaceHolder();
Canvas c = null;
try {
c = holder.lockCanvas();
if (c != null) {
c.save();
/* allows the wallpaper to scroll through the homescreens */
c.drawBitmap(wpBitmap, screenWidth * -mOffset, 0,
mForeGroundPaint);
c.restore();
}
} finally {
if (c != null)
holder.unlockCanvasAndPost©;
}
}
What happens now is, that everything seems to work fine, what means that the Bitmap is painted with the opacity value of 5, like I set it.
The problem happens when I use that drawFrame() function several times, as it is called during onOffsetsChanged(): The opacity sums up, making it 10, 15, 20, 25, ... with every call of drawFrame().
How can I prevent that from happening, and thus keep the amount of opacity on a steady level?
The Bitmap is just being redrawn over old ones, so you have 2 Bitmaps at 5% opacity = 10% opacity. Try clearing the Canvas with c.drawColor(...); (with your background color) after c.save();.
I'm trying to do a menu based on bitmaps. The menu itself should be movable through screentouch move events, basically I want to drag the buttons around on the view. The button also includes collision detection, so whenever they touch they bounce from each other.
But I have some problems when it comes to drawing my bitmaps. Currently I'm using a rectangle to scale my bitmap to fit the window of my device. Want i want and can not get currently is for smoother movements of my bitmaps without flickering. Is the only option to move to open gl? Or have I missed something big in my code?
This is in my surfaceview for drawing each button, where MenuButton is the class that holds the bitmap and updates its position according to a touch and drag move.
protected void onDraw(Canvas canvas) {
canvas.drawColor(Color.WHITE);
for(MenuButton menuButton : menuButtonSprites) {
menuButton.onDraw(canvas);
}
}
I want the bitmaps to scale to each device's width and for that i use a rectangle for the bitmap to fit in.
public MenuButton(MenuView v, Bitmap bmp, int yPosition){
this.menuView = v;
this.menuButton = bmp;
this.xMax = v.getWidth();
this.yPosistion = yPosition;
menuButtonRectangle = new Rect(xMin, this.yPosistion-yMin, xMax, this.yPosistion+yMax);
}
public void update(int y){
if(menuButtonPressed)
{
this.yPosistion = y;
menuButtonRectangle.set(xMin, yPosistion-yMin, xMax, yPosistion+yMax);
}
}
public void onDraw(Canvas canvas){
canvas.drawBitmap(menuButton, null, menuButtonRectangle, null);
}
I also have a thread that updates the draw
public void run() {
long ticksPS = 1000 / FPS;
long startTime;
long sleepTime;
Canvas c = null;
while (running) {
startTime = System.currentTimeMillis();
try {
c = view.getHolder().lockCanvas();
synchronized (view.getHolder()) {
view.onDraw(c);
}
}
finally {
if (c != null) {
view.getHolder().unlockCanvasAndPost(c);
}
}
sleepTime = ticksPS - (System.currentTimeMillis() - startTime);
try {
if (sleepTime > 0)
sleep(sleepTime);
else
sleep(10);
}
catch (Exception e) {
}
}
}
I don't really know what I'm doing wrong and why i can't manage to get a smooth movements of my buttons. Is it a downside for using canvas or have I missed something really important :D?
Usually This problem occurs when there is sync problem exists while painting. This may due to the higher Frame rate or also may be the lower frame rate. These kind of issue can be fixed by Double buffering or adjusting the Frame Rate.
Double buffering means, Instead of drawing the Image directly on to the main canvas, we will be creating an empty bitmap of screen size and getting the graphics object. Drawing every thing on to the bitmap then directly drawing this bitmap to the main canvas.
I'm shooting for an animation in a live wallpaper. Here's the code. It pretty much follows the CubeWallpaper demo:
void drawFrame() {
final SurfaceHolder holder = getSurfaceHolder();
final BufferedInputStream buf;
final Bitmap bitmap, rbitmap;
Canvas c = null;
try {
c = holder.lockCanvas();
if (c != null) {
try {
buf = new
BufferedInputStream(assets.
open(folder+"/"
+imageList[ilen++])
);
bitmap = BitmapFactory.
decodeStream(buf);
rbitmap = Bitmap.createBitmap
(bitmap,
0,0,imageWidth,imageHeight,
transMatrix,false);
c.drawBitmap(rbitmap,
paddingX,
paddingY,
null);
if ( ilen >= imageCount ) ilen=0;
}
catch (Exception e) { e.printStackTrace(); }
}
} finally {
if (c != null) holder.unlockCanvasAndPost(c);
}
// Reschedule the next redraw
mHandler.removeCallbacks(mDrawCube);
if (mVisible) {
mHandler.postDelayed(mDrawCube, fps);
}
}
where "transMatrix" is a scaling and rotation matrix predefined before.
It's supposed to render at 30fps but of course it doesn't do that. My initial guess is that the BufferedInputStream is one factor. I should probably cache a few of these as I go along along with the Bitmaps. But any other ideas? Will I have to use the OpenGL hack for live wallpapers?
Yes, the BufferedInputStream and BitmapFactory really shouldn't be in drawFrame() at all. You're loading and creating resources on every single frame, and that's a huge waste. Like you said, cache as many as you can beforehand, and if you find the need to load more during drawing, use a separate thread to do it so it doesn't slow the drawing.
I had the same problem: slow canvas rendering in context of live wallpapers.
I agree with others saying that you shouldn't do any cpu/io heavy while rendering e.g. loading images especially on the UI thread.
However there is one more thing you should note. You request a redraw (mHandler.postDelayed(...)) AFTER the frame was rendered. If you desire a 30 fps and thus you request a redraw in (1000 / 30) 33ms then it will NOT result in 30 frames per sec. Why?
Let's assume it takes 28ms to render all your stuff to the canvas. After it's done you request a redraw after 33 millis. That is, the period between redraws is 61 ms which equals with 16 frames per sec.
You have two options to solve it:
1) Put the mHandler.postDelayed(...) code to the beginning of the drawFrame(...) method. This seems OK but it has some disadvantages: If your actual FPS is very close to the maximal possible FPS on an actual device - with other words the UI thread is busy all the time with you canvas rendering - then there won't be time for the UI thread to do other stuff. It doesn't necesseraly mean that your LWP or the home screen will lag but you (your LWP) might start missing some touch events (as my LWP did).
2) The better solution is to start a separate thread when the surface is created and pass it the reference to the SurfaceHolder. Do the rendering in this separate thread. The render method in this thread would look like this:
private static final int DESIRED_FPS = 25;
private static final int DESIRED_PERIOD_BETWEEN_FRAMES_MS = (int) (1000.0 / DESIRED_FPS + 0.5);
#Override
public void run() {
while (mRunning) {
long beforeRenderMs = SystemClock.currentThreadTimeMillis();
// do actual canvas rendering
long afterRenderMs = SystemClock.currentThreadTimeMillis();
long renderLengthMs = afterRenderMs - beforeRenderMs;
sleep(Math.max(DESIRED_PERIOD_BETWEEN_FRAMES_MS - renderLengthMs, 0));
}
}