3

the keywords about this topic:

  1. CustomSurfaceView: three custom surfaceview for three different levels.
  2. Canvas: lock/unlockAndPost method (i'm not using custom bitmap )
  3. Multi thread ( each surface is a separate thread )
  4. Shapes ( shapes on canvas )
  5. Client/Server ( architecture )
  6. Flickering ( IS WHY I'M HERE )

We are developing a client/server application and I'm working on the client side. I'm receiving messages from the server containing general data (coordinates, color, width [...] ) about paths like, circle, rectangle, line and other shapes. The web application allows the user to send these data drawing on HTML5 Canvas, to an android device that receiving this messages and parsing it, will be able to redraw all the shapes. From my own experience on this subjects, I learned that the best way to keep in control all the things you draw on the canvas, is saving everything into a buffer, array, list or something like that, then reuse it when you want (for example, you can use the older path for show, hide, move or simply change something on the canvas). In my opinion, the android application follows the best practice of android development and OOP paradigm so I'm not assuming errors related to the bad architecture. In this case, I'm saving the messages on web client side. When the user draws on HTML5 Canvas, the messages which contain shape info are perfectly reported to the android canvas, but the problem appears when:

[example] Consider you draw 10 objects (10 messages) and you want delete only one object on web app canvas, so the only way is clearing all the canvas, and redraw all the previous shapes without the deleted shape (so resend to the client 9 messages by loop the messages buffer ). This method works perfectly for the web app but cause flickering problem on android client. So after too many experiments I found a workaround, using a Thread.sleep(100)(Whooo! 100ms is too much), in order to parse slowly the messages and let the surfaceview thread to read correctly the data (data access through singleton pattern) and write on the double-buffer of the canvas.Well, it's slow and ugly but it works ! Actually I don't like this “horrible” workaround so please help me to see an exit strategy.

This is a piece of code where the canvas get data from shapes containers and draw if data are present. The data of each containers came from server messages.

@Override
public void run() {
    Canvas canvas = null;
    while (running) {
        //this is the surface's canvas
        try {
            canvas = shapesSurfaceHolder.lockCanvas();
            synchronized (shapesSurfaceHolder) {
                if (shapesSurfaceHolder.getSurface().isValid()) {
                    if(!Parser.cmdClear){

                        //draw all the data present
                        canvas.drawPath(PencilData.getInstance().getPencilPath(),
                                PencilData.getInstance().getPaint());
                        canvas.drawPath(RectData.getInstance().getRectPath(),
                                RectData.getInstance().getPaint());
                        canvas.drawPath(CircleData.getInstance().getCirclePath(),
                                CircleData.getInstance().getPaint());
                        canvas.drawPath(LineData.getInstance().getLinePath(),
                                LineData.getInstance().getPaint());
                        canvas.drawText(TextData.getInstance().getText(),
                                TextData.getInstance().getX(),
                                TextData.getInstance().getY(),
                                TextData.getInstance().getPaint());

                    } else {
                        //remove all canvas content and clear data.
                        canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
                        for (int i = 0; i < AbstractFactory.SHAPE_NUM; i++) {
                            abstracFactory.getShape(i).clearData();
                        }
                    }
                }
            }
        } finally {
            if (canvas != null) {
                shapesSurfaceHolder.unlockCanvasAndPost(canvas);
            }
        }
    }
}//end_run()

I can summarize that, apparently, my problem is to draw too quickly

Note:

Similar concept: Android thread controlling multiple texture views causes strange flickering

Hardware acceleration is enabled.

minSdkVersion 17

Tested on

  • Tablet Samsung SM-T113

  • Google Nexus 5

Community
  • 1
  • 1
Andrea D'Ubaldo
  • 137
  • 1
  • 6

1 Answers1

2

The TextureView issue was due to a bug specific to TextureView. You're using SurfaceView, so that does not apply here.

When drawing on a SurfaceView's Surface, you must update every pixel inside the dirty rect (i.e. the optional arg passed to lockCanvas()) every time. If you don't provide a dirty rect, that means the entire screen must be updated. This is because the Surface is double- or triple-buffered, and swapped when you call unlockCanvasAndPost(). If you lock / clear / unlock, then the next time you lock / draw / unlock, you will not be drawing into the buffer you previously cleared.

If you want to do incremental rendering, you should point your Canvas at an off-screen Bitmap and do all your rendering there. Then just blit the entire bitmap between lock and unlock. The alternative is to store up the drawing commands, starting with the initial clear, and play them all back between lock/unlock.

The phrase "three custom surfaceview" is somewhat concerning if they're all on screen at once. If you have them all at different Z depths (default, media overlay, top) then they will behave correctly, but the system is generally more efficient if you can put everything on one.

fadden
  • 51,356
  • 5
  • 116
  • 166
  • Hi fadden, thanks for your reply. I'll try with dirty rect, because the alternative you suggests is not working, actually is what I'm doing...store up all the commands and then clear/redraw all between lock/unlock. About "three custom surfaceview"...I created three different surfaceview because each surface has different purpose. "shapes", "remote pointer" and "screenshots", I thought these features can't lives together. I'll let you know the success...I hope. Thank you again. – Andrea D'Ubaldo Mar 22 '16 at 09:45
  • Hi @fadden, after many tries with dirty rect, I can say that I need to update the entire canvas so `surfaceHolder.lockCanvas()` it's ok, without any argument. I also consider your alternative : "store up the drawing commands, starting with the initial clear, and play them all back between lock/unlock.", implemented like this: `canvas = surfaceHolder.lockCanvas(); for( int i = 0; i < commandList.size(); i++){ canvas.drawPath(pathList.get(i),paintList.get(i)); } surfaceHolder.unlockCanvasAndPost(canvas); ` right ? – Andrea D'Ubaldo Mar 22 '16 at 17:40
  • You would want to clear the entire canvas right after you lock it, but yes, that's a workable approach. Using a dirty rect is tricky when things overlap. Drawing on an offscreen bitmap and blitting it to the Surface can become more efficient as the amount of repeated rendering increases. – fadden Mar 22 '16 at 19:54
  • Thank you @fadden, it works ! . Drawing between `lockCanvas()` `unlockCanvasAndPost()` the stored data is the solution. I'm also interested to understand the offscreen bitmap alternative, so (if you want) can you link me some example about it ? Thanks again. – Andrea D'Ubaldo Mar 23 '16 at 11:02
  • 1
    Create a screen-sized Bitmap, then create a Canvas using the constructor that takes a Bitmap argument. Do all your rendering on that Canvas. If you just want to add one thing, you don't have to erase and redraw -- because it's backed by a Bitmap, rather than a Surface, it's just rendering pixels into a single buffer. When you want to update the display, you lock the Surface canvas, blit the bitmap with a single `drawBitmap()` call, and unlock-and-post. – fadden Mar 23 '16 at 16:28