I have encountered the nasty Android SurfaceView behavior:
When you leave an Activity with SurfaceView and quickly go back, the previous content of SurfaceView doesn't disappear and is displayed under the new one.
See the example project that reproduces the problem, please.
https://github.com/t-artikov/surface-view-bug
I have found that this is an Android bug, which was fixed on Android P
https://issuetracker.google.com/issues/72624889
But I need to get it to work on earlier OS versions.
Will be glad to any ideas how to solve the issue. Maybe someone has already encountered it and knows a workaround.
Note: This problem wouldn't occur on an API 22 device. I used the Pixel 2 API 26, as the OP did, in order to reproduce it.
It appears that, under this condition, the SurfaceView saves its current contents to some sort of hidden intermediate surface, at certain times (like when a transition begins). The condition can't be corrected by replacing the SurfaceView, or by adjusting the background drawables of any ancestor views.
Your onDrawFrame() is clearing the buffer with a transparent color, which is letting this "intermediate surface" show through. If you don't need the transparency, you could just clear it to solid white:
GLES20.glClearColor(1, 1, 1, 1);
EDIT
I found myself coming back to this question, as it is a very troublesome bug. Since you need to keep the transparency, it turns out you can add this to SecondActivity, to avoid the problem:
#Override
protected void onStart()
{
overridePendingTransition(0,0);
super.onStart();
}
I also tried disabling hardware acceleration, but no luck there.
Related
I've been trying to solve my problem for a long time now. However, I'm at a loss. The problem is this:
I have a Custom Android View that I render 10 rectangles on(kind of
like a bar chart all the same length, different color)
When updating one of the rectangles with different opacity, it
doesn't change. So I clear the Canvas. But this causes a flicker,
not all the time, but sometimes
What I've tried doing:
Render everything to an offscreen bitmap, then blting it, this still
doesn't solve my issue
Use a SurfaceView and render in another thread
A combination of 1 and 2
In the end, I think the problem is that the background gets erased, but I don't want it to erase. However, I can never get the new "pixels" to show up. I also tried experimenting with different transfer modes like SRC, SRC_ATOP, when I tried number 1, it helps, but does not get rid of the problem.
Does anyone have any guidance on what could be going wrong? Or any other possible solutions?
I have finally figured it out. I don't have to clear the back ground, just temporarily change the paint mode to SRC.
context.paint.setStyle(Paint.Style.FILL);
context.paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
context.canvas.drawPath(context.path, context.paint);
context.paint.setXfermode(null);
Hej guys, so this is the first time in about 7 years that I have not been able to find an answer to a programming issue, nor have I gotten close to what the issue might be in the first place.
Alright so let's begin from the start. I've followed some tutorials and examples on the Android SurfaceView and how to draw on its Canvas in another thread. So far, no issues, everything draws like I expect. I'm currently working on a scenario where I have some padding around my SurfaceView, which means that the background of the parent view (a FrameLayout) renders around the aforementioned SurfaceView. This is where things become interesting as I use the same color for the parent background as I do for clearing the SurfaceView's Canvas.
The root (=parent) FrameLayout has its background set in the theme like
<item name="android:windowBackground">#color/palette_primary_dark</item>
which is defined in colors.xml like
<color name="palette_primary_dark">#28252C</color>
and in my thread constructor, I retrieve the same color into the global variable mClearColor like
mClearColor = ContextCompat.getColor(context, R.color.palette_primary_dark);
where the context is the Context I receive from my SurfaceView.
The following code is the render loop that runs in my thread
#Override
public void run() {
while (mShouldRun) {
Canvas canvas = null;
try {
canvas = mSurfaceHolder.lockCanvas();
synchronized (mSurfaceHolder) {
if (canvas != null) {
canvas.drawColor(mClearColor);
onDrawFrame(canvas);
}
}
} finally {
if (canvas != null) {
mSurfaceHolder.unlockCanvasAndPost(canvas);
}
}
}
}
So far, so good. No errors and everything draws; the background isn't black and it looks like the color I want. But here's the catch, it ISN'T actually the color I want. It's off by a hair. The color I defined in my XML is #28252C but according to my GIMP color picker tool it actually draws #292429. Weird.
Then I started searching further and discovered that the color I'm using to draw touch indicators (where my fingers are located on the screen) should be #FF1744 but actually is #FF1442.
This left me scratching my head and desperately trying other ways of drawing the right color on my canvas. I've tried Canvas.drawColor(), Canvas.drawARGB() and Canvas.drawRGB(), but to no avail. In drawColor() I even tried different ways of retrieving my color, like mClearColor, Color.parseColor("#28252C"), Color.argb(255, 40, 37, 44) and so on. Nothing worked.
Then I came across the fact that drawColor() uses PorterDuff.Mode.SRC_OVER by default, so I tried using PorterDuff.Mode.SRC to get the pure color I'm feeding it, but no happy faces :(
Then I just started trying stuff but nothing seemed to work. When I tried setLayerType() in my SurfaceView, something did change though. The following screenshot shows how it looked like with the default LAYER_TYPE_NONE value
LAYER_TYPE_NONE
Now when I change this to either LAYER_TYPE_SOFTWARE or LAYER_TYPE_HARDWARE the same layout looks like follows
LAYER_TYPE_SOFTWARE/HARDWARE
The weird thing is now that it actually is the correct color and renders how I want it to, it still doesn't draw the lines and info about the touch. Calls to my onTouchListener are still being made, which in its turn should provide data to draw the lines on the screen. The latter doesn't work though, it doesn't actually draw the lines. I have no clue what causes this weird issue that changes my colors... Any help would be greatly appreciated :)
Edit #1:
I have done some more research today and as per this answer by Romain Guy and the following self-answered question by Adrian Lopez, setLayerType() actually does more harm than it solves problems. So I now have even less pointers as to what could cause or solve my issue :/ I'll make sure to edit the question when I have found more information and answer it if I found a solution :)
The SurfaceView's surface is configured to use RGB 565 by default.
In onCreate(), set the color format of the surface with setFormat() to get RGB 8888 instead:
mSurfaceHolder.setFormat(PixelFormat.TRANSLUCENT);
I know this is quite the same question as partial-invalidation-in-custom-android-view-with-hardware-acceleration but I thought I will explain my problem more detailled and hope someone will have an answer on how to solve the problem.
I wrote a simple TicTacToe app which was working fine on the emulator. But wasn't working on the device since I found out it was working as expected on the device when I disabled hardware acceleration.
All the fields are drawn with Canvas.drawBitmap(), also the "empty" fields with an empty white image. The lines are drawn with Canvas.drawLine()
I'm using partial invalidation with invalidate(Rect) because I want to redraw only the area which was choosen to set a cross/circle with the according image using again Canvas.drawBitmap(). But then the whole area is invalidated, means I see the whole area/screen is gray (the white images and lines disappeared) and only the image for the cross/circle is set in the choosen area.
When I print out the invalidated field rect with Canvas.getClipBounds() in the method onDraw(), with hardware acceleration it is the whole area (example "0,0,320,407") and without hardware acceleration the same rect which I invalidated with invalidate(Rect) (e.g. "106,135,106,135").
At the moment as workaround I redraw all the fields whith the according image and lines. How can I prevent to have the whole area invalidated with hardware acceleration?
If this matters: I'm using Android version 4.1.2 on Samsung Galaxy Young Duos S6312.
Regards
Sandro
I have post my answer in your given link. To understand the problem , you should know the difference between software rendering and hardware rendering. That's really a big topic which I won't cover here. I think the best approach is to read the source code(I did the same thing a few days ago). The key question is "what is DisplayList and when it is used"
. Repeat my answer here, the Canvas in onDraw is simply used to rebuild the DisplayList. That doesn't mean everything in your View will be redrawn.
I have a GLSurfaceView with a renderer assigned to it. On this view, I draw images which seem to be offset to the left compared to where I have requested that they be drawn on it. This problem has only arisen since I switched from Canvas-based rendering, which rendered the images in their correct positions, to OpenGL 1.0 rendering, which doesn't.
I have read through my code and have found absolutely nothing that could be causing this. What frustrates me is that I know there is nothing wrong with the positions I set using my code, but the problem still persists.
Now, here's the best part of it all. When I pause the application by pressing the home button, and then restart the application through the task manager, the positions are fixed and are exactly how they should be, which leads me to believe that the problem isn't with the positions I set using my code, but how I've set up my surface view. I've checked the dimensions in the onSurfaceChanged() parameters only to find that they are always as they should be, which leaves me even more confused.
Does anyone have any idea what could be causing this problem?
After much digging through my project, I discovered I was setting positions based on the dimensions of the GLSurfaceView before onSurfaceChanged had been called in the onSurfaceCreated method, therefore the width/height of the view at this point was 0 which affected the positions of my objects on the screen.
I feel really stupid now.
We've been fighting with some problems relating to SurfaceViews for more than a week, and find no proper solution to them. We read the other questions in the forum regarding to similar problems (and even Mixare source code) but couldn't find an answer, so we hope you could help us somehow.
Scenario:
We've got
a SurfaceView for the Camera
a SurfaceView for an OpenGL layer, which goes on top of the camera.
another View, which shows some information about what we can see on the screen. This one goes on top of both SurfaceViews.
Problem:
No matter how hard we try, both SurfaceViews apparently don't get on well with each other. If we try to:
setContentView(mCameraPreview);
addContentView(mGLSurfaceView, new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
addContentView(mInfoView, new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
(which seems logical), everything goes as expected until we lock/unlock the phone. After that, the GLSurfaceView just disappears (not the InfoView, that one is still shown).
If, instead, we try to:
setContentView(mGLSurfaceView);
addContentView(mCameraPreview, new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
addContentView(mInfoView, new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
Then the problem is that the GLSurfaceView only appears after lock/unlock, and before that the screen is showing the camera and the InfoView.
We discovered that if we sleep the main thread for 4.6 seconds (or more) after executing onStart() in the activity that shows the views, the behaviour is as expected (both camera, glsurface and info views are shown, even after lock/unlock).
The thing is, we're looking for a more... elegant solution.
It seems to us that the problem is the camera taking more time than expected in Camera.open(), and so the camera View is added, the GLSurfaceView is added, and when the camera actually opens, it opens on top of the GLSurfaceView. Regarding to this, we used bringToFront() on GLSurfaceView, and got it on top of the info view, but after lock/unlock the camera still opened on top of it, leaving us with a screen with the camera preview only.
Any ideas? How can we show both SurfaceViews and the info view on top of them?
try this:
mLayout.addView(mRenderView);
mLayout.addView(mCustomSurfaceView);
// without this line, the camera preview cannot be displayed
// when running activity at first time.
mCustomSurfaceView.setZOrderMediaOverlay(true);
this worked for me :)
I had the same problem. As you hint at yourself: multiple SurfaceViews don't get along with each other in that their Z order is undefined.
On my Samsung Galaxy S2 the ordering is the same as you describe (don't know how it is on other phones).
The way I solved this, is checking for first time creation of the Activity in onCreate():
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
//...
if ( savedInstanceState == null )
{
initCamView();
initOpenGL();
}
else
{
initOpenGL();
initCamView();
}
//...
}
with:
private void initOpenGL()
{
mGLSurfaceView = new GLSurfaceView(this);
mGLSurfaceView.setEGLConfigChooser(8, 8, 8, 8, 16, 0);
mGLSurfaceView.getHolder().setFormat(PixelFormat.TRANSLUCENT);
mOGLRenderer = new OGLRenderer(this);
mGLSurfaceView.setRenderer(mOGLRenderer);
mRL.addView(mGLSurfaceView); // mRL is the Relative Layout
}
and:
private void initCamView()
{
mCamView = new CustomCameraView( this.getApplicationContext(),
this.getWindowManager() );
mRL.addView(mCamView); // mRL is the Relative Layout
}
Not the most elegant solution, but it's better than letting the thread sleep for 4.6 seconds.
It might also be possible to just lock the screen in a single orientation, but then you need to do a lot of ugly hacking to get the overlays rotated the right way.
Or, if you're only targeting Android 3.0 or above (API level 8), you can just show the camera in an OpenGL SurfaceTexture. http://developer.android.com/reference/android/hardware/Camera.html#setPreviewTexture(android.graphics.SurfaceTexture)
Each SurfaceView's surface has a Z-depth. There are three possible depths: the default (bottom), "media overlay" (above it), and "top" (which is above everything, including the View-based UI). If you have two overlapping surfaces at the same depth, one will win, but you can't reliably define which one. You might get consistent behavior on one device only to find that it works the other way on a different device.
The composition is done with hardware overlays, when possible. Many currently-popular devices have 4 overlay planes. Once you exceed that, the composition is done with the GPU, which is going to be more expensive. If you have a status bar, navigation bar, and View UI, that's three, so you only get one SurfaceView for cheap.
For the example in the question, it would be best to combine as many things as possible onto the surface being rendered with GLES. This can include camera output on API 11+. See the "Texture from Camera" activity in Grafika for an example. (Grafika also has a demo app with three overlapping SurfaceViews; see the "multi-surface test" activity.)
If you'd like to know more about why SurfaceView behaves the way it does, see the Android System-Level Graphics doc.
I had the same problem: I wanted a SurfaceView under a GLSurfaceView, the first to play video and the second to run a game.
Whatever I did, the z order between these 2 surface views appears to be random.
But I found the solution, thanks to this post: https://stackoverflow.com/a/6028907/1557915
The trick is to remove the use of layout, but instead use setContentView/addContentView.