lockCanvas failing when screen orientation changes - java

I'm trying to build a live wallpaper in Android, but it crashes the app when the orientation changes. It looks like it's crashing when trying to lockCanvas on the surface holder, but I'm not sure what I can do to prevent it.
Here's the class:
public class LiveWallpaperService extends WallpaperService
{
public void onCreate() {
super.onCreate();
}
public void onDestroy() {
super.onDestroy();
}
public Engine onCreateEngine() {
return new MyWallpaperEngine();
}
class MyWallpaperEngine extends Engine
{
private final Handler handler = new Handler();
private final Runnable drawRunner = new Runnable() {
#Override
public void run() {
draw();
}
};
private boolean visible = true;
Paint paint;
MyWallpaperEngine() {
paint = new Paint();
}
public void onCreate(SurfaceHolder surfaceHolder) {
super.onCreate(surfaceHolder);
}
#Override
public void onVisibilityChanged(boolean visible) {
this.visible = visible;
if (visible) {
handler.post(drawRunner);
}
else {
handler.removeCallbacks(drawRunner);
}
}
#Override
public void onSurfaceDestroyed(SurfaceHolder holder) {
super.onSurfaceDestroyed(holder);
this.visible = false;
handler.removeCallbacks(drawRunner);
}
public void onOffsetsChanged(float xOffset, float yOffset, float xStep, float yStep, int xPixels, int yPixels) {
draw();
}
void draw() {
final SurfaceHolder holder = getSurfaceHolder();
Canvas c = null;
try {
c = holder.lockCanvas();
if (c != null) {
// Paint stuff here.
}
}
finally {
if (c != null) {
holder.unlockCanvasAndPost(c);
}
}
handler.removeCallbacks(drawRunner);
if (visible) {
handler.postDelayed(drawRunner, 10);
}
}
}
}
And this is the exception that happens when the orientation changes:
E/StudioProfiler: JVMTI error: 15(JVMTI_ERROR_THREAD_NOT_ALIVE)
E/Surface: dequeueBuffer failed (No such device)
E/BaseSurfaceHolder: Exception locking surface
java.lang.IllegalArgumentException
at android.view.Surface.nativeLockCanvas(Native Method)
at android.view.Surface.lockCanvas(Surface.java:318)
at com.android.internal.view.BaseSurfaceHolder.internalLockCanvas(BaseSurfaceHolder.java:194)
at com.android.internal.view.BaseSurfaceHolder.lockCanvas(BaseSurfaceHolder.java:158)
at android.service.wallpaper.WallpaperService$Engine$1.lockCanvas(WallpaperService.java:262)
at greencell.bitpatternswallpaper.LiveWallpaperService$MyWallpaperEngine.draw(LiveWallpaperService.java:206)
at greencell.bitpatternswallpaper.LiveWallpaperService$MyWallpaperEngine$1.run(LiveWallpaperService.java:51)
at android.os.Handler.handleCallback(Handler.java:790)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:164)
at android.app.ActivityThread.main(ActivityThread.java:6494)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)
Update:
I've checked many other threads that seem to have the same issue but the only thing that I can do so far is to wrap unlockCanvasAndPost and lockCanvas in a try catch to ignore IllegalArgumentException.

In draw(), I'd try moving handler.removeCallbacks(drawRunner); just before the try block. It could be that onOffsetsChanged() is getting called on orientation change, and that the previous thread on the handler might not have called unlockCanvasAndPost(c) yet, which explains why you're getting an error with lockCanvas() at that point. However, this shouldn't be the case if the code you've posted here exactly matches what you're running locally, since you haven't overriden onOffsetsChanged().
Another thing you could try is overriding onSurfaceChanged() and clearing the handler queue like this:
#Override
public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) {
handler.removeCallbacks(drawRunner);
super.onSurfaceChanged(holder, format, width, height);
}
Ultimately, all of the examples regarding WallpaperService that I've read online have a try-finally block with the lock/unlock canvas logic, so I wouldn't be worried.

try android:configChanges="orientation" on your
<service android:name=".LiveWallpaperService" /> in Manifest
example:
<service android:name=".LiveWallpaperService"
android:configChanges="orientation" />

Related

Can anyone shed some light on why I cant get a variable from this class? [duplicate]

This question already has answers here:
What is a NullPointerException, and how do I fix it?
(12 answers)
Closed 5 years ago.
I have been trying for some time now to get a variable from MySurfaceView to use in my Player class. I cant seem to make it work... I tried getting the touch information from my MainActivity class but it wasn't really where I wanted it and also couldn't seem to make it work. Any help is appreciated!
MySurfaceView.java
package com.Frenchie.SurfaceView;
import ...
public class MySurfaceView extends SurfaceView implements Runnable {
Bitmap bitmap;
SurfaceHolder surfaceHolder;
int LastTouchx;
Player player;
public MySurfaceView(Context context, AttributeSet attributeSet) {
super(context, attributeSet);
player = new Player(context);
surfaceHolder = getHolder();
//Starts the run()
Thread TestThread = new Thread(this);
TestThread.start();
}
#Override
public void run() {
//TODO movement here when display is working
while (true){
Update();
DrawPlayer();
}
}
public void Update(){
player.Update();
}
public void DrawPlayer(){
Canvas canvas = surfaceHolder.lockCanvas();
if (surfaceHolder.getSurface().isValid()) {
canvas.drawColor(Color.BLUE);
canvas.drawBitmap(player.getBitmap(), player.getX(), player.getY(), null);
surfaceHolder.unlockCanvasAndPost(canvas);
}
else{
Log.d("DrawPlayer", "Surface Not Valid");
}
}
#Override
public boolean onTouchEvent(MotionEvent event) {
LastTouchx = (int)event.getX();
//LastTouchy= (int)event.getY();
Log.d("Touch Value ",LastTouchx+"");
return false;
}
public int getLastTouchx() {
return LastTouchx;
}
}
Player.java
package com.Frenchie.SurfaceView;
import ...
public class Player {
//Bitmap to get character from image
private Bitmap bitmap;
//coordinates
private int x;
private int y;
//motion speed of the character
private int speed = 0;
MySurfaceView mySurfaceView;
//constructor
public Player(Context context) {
x = 75;
y = 500;
//Getting bitmap from drawable resource
bitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.player);
}
//Method to update coordinate of character
public void Update(){
//updating x coordinate
if (x > mySurfaceView.getLastTouchx()){
x++;
}
else if (x < mySurfaceView.getLastTouchx()){
x--;
}
else{
Log.d("Update","Else triggered");
}
}
public Bitmap getBitmap() {
return bitmap;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
}
Messages
E/AndroidRuntime: FATAL EXCEPTION: Thread-4
Process: com.Frenchie.SurfaceView, PID: 26348
java.lang.NullPointerException: Attempt to invoke virtual method 'int com.Frenchie.SurfaceView.MySurfaceView.getLastTouchx()' on a null object reference
at com.Frenchie.SurfaceView.Player.Update(Player.java:36)
at com.Frenchie.SurfaceView.MySurfaceView.Update(MySurfaceView.java:68)
at com.Frenchie.SurfaceView.MySurfaceView.run(MySurfaceView.java:62)
at java.lang.Thread.run(Thread.java:762)
This was not a duplicate of the post suggested.
Override another constructor in your MySurfaceView:
public class MySurfaceView extends SurfaceView
...
public MySurfaceView(Context context) {
this(context, (AttributeSet)null)
}
public MySurfaceView(Context context, AttributeSet attributeSet) {
super(context, attributeSet);
player = new Player(context, this);
bitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.player);
surfaceHolder = getHolder();
//Starts the run()
Thread TestThread = new Thread(this);
TestThread.start();
}
...
In your player in constructor you can pass a surfaceView: so no need to initialize:
MySurfaceView mySurfaceView;
//constructor
public Player(Context context, MySurfaceView surfaceView) {
this.mySurfaceView = surfaceView;
x = 75;
...
Try:
MySurfaceView mySurfaceView = new MySurfaceView();

Issue in rendering Camera Preview using OpenGL ES 2.0

This is the first time I am trying to render the Camera Preview using OpenGL ES 2.0. I am into some issues with the code. Here goes the code below --
MainActivity.java
public class MainActivity extends AppCompatActivity implements SurfaceTexture.OnFrameAvailableListener {
private Camera mCamera;
private GLSurfaceView glSurfaceView;
Bitmap bitmap;
FrameLayout frameLayout;
DrawOnTop drawOnTop;
FrameLayout.LayoutParams layoutParams;
private SurfaceTexture surface;
MyGLRenderer renderer;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
frameLayout = (FrameLayout)findViewById(R.id.camera_preview);
drawOnTop = null;
}
public void onCamViewButtonClicked (View view)
{
glSurfaceView = new MyGLSurfaceView(this);
renderer = MyGLSurfaceView.getRenderer();
frameLayout.addView(glSurfaceView);
}
public void startCamera(int texture)
{
surface = new SurfaceTexture(texture);
surface.setOnFrameAvailableListener(this);
renderer.setSurface(surface);
mCamera = Camera.open();
try
{
mCamera.setPreviewTexture(surface);
mCamera.startPreview();
}
catch (IOException ioe)
{
ioe.printStackTrace();
}
}
public void onFrameAvailable(SurfaceTexture surfaceTexture)
{
glSurfaceView.requestRender();
}
public void onOverlayImageButtonClicked(View view)
{
if(glSurfaceView == null)
{
Toast.makeText(getApplicationContext(),"Preview is not available now!",Toast.LENGTH_LONG).show();
return;
}
else {
if(drawOnTop != null)
{
frameLayout.removeView(drawOnTop);
drawOnTop = null;
}
else
{
bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.internetothings);
drawOnTop = new DrawOnTop(this, bitmap);
layoutParams = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT,
FrameLayout.LayoutParams.WRAP_CONTENT);
layoutParams.gravity = Gravity.CENTER_HORIZONTAL;
frameLayout.addView(drawOnTop, layoutParams);
Toast.makeText(getApplicationContext(),"Click again to remove!",Toast.LENGTH_LONG).show();
}
}
}
public void onPause()
{
super.onPause();
mCamera.stopPreview();
mCamera.release();
}
}
MyGLSurfaceView.java
public class MyGLSurfaceView extends GLSurfaceView{
static MyGLRenderer myGLRenderer;
public MyGLSurfaceView(Context c)
{
super(c);
setEGLContextClientVersion(2);
myGLRenderer = new MyGLRenderer((MainActivity)c);
setRenderer(myGLRenderer);
setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
}
public static MyGLRenderer getRenderer()
{
return myGLRenderer;
}
}
MyGLRenderer.java
public class MyGLRenderer implements GLSurfaceView.Renderer{
int texture;
private SurfaceTexture surfaceTexture;
MainActivity mainActivity;
public MyGLRenderer(MainActivity main)
{
mainActivity = main;
}
public void onSurfaceCreated(GL10 unused, javax.microedition.khronos.egl.EGLConfig config)
{
texture = createTexture();
GLES20.glClearColor(0.5f, 0.5f, 0.5f, 1.0f);
mainActivity.startCamera(texture);
}
public void onDrawFrame(GL10 unused)
{
float[] mtx = new float[16];
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
surfaceTexture.updateTexImage();
surfaceTexture.getTransformMatrix(mtx);
}
public void onSurfaceChanged(GL10 unused, int width, int height)
{
GLES20.glViewport(0, 0, width, height);
}
static private int createTexture()
{
int[] texture = new int[1];
GLES20.glGenTextures(1, texture, 0);
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, texture[0]);
GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
GL10.GL_TEXTURE_MIN_FILTER,GL10.GL_LINEAR);
GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
GL10.GL_TEXTURE_WRAP_S, GL10.GL_CLAMP_TO_EDGE);
GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
GL10.GL_TEXTURE_WRAP_T, GL10.GL_CLAMP_TO_EDGE);
return texture[0];
}
public void setSurface(SurfaceTexture _surface)
{
surfaceTexture = _surface;
}
}
Issue 1: After all this, I am getting a Grey texture instead of a Camera Preview. As a reference I have checked this post. Please help me to identify what went wrong over here.
Issue 2: While investigating Issue 1, I feel (not sure though) that in MyGLSurfaceView.java, declaring myGLRenderer as static could be a problem. Also I have declared the method getRenderer() as static as well which might cause the issue. So I removed the static keyword as below --
public class MyGLSurfaceView extends GLSurfaceView {
MyGLRenderer myGLRenderer;
public MyGLSurfaceView(Context c)
{
super(c);
setEGLContextClientVersion(2);
myGLRenderer = new MyGLRenderer((MainActivity)c);
setRenderer(myGLRenderer);
setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
}
public MyGLRenderer getRenderer()
{
return myGLRenderer;
}
}
But then I run into a very weird issue in this line --
glSurfaceView = new MyGLSurfaceView(this);
renderer = glSurfaceView.getRenderer();
It says "Cannot resolve method getRenderer()".
Can anybody please help me get over these two issues?
Thanks in advance!
You can try various options:
I had the issue null point exception: MainActivity.startCamera.
First, I would say check your device settings: Settings --> Apps --> (Your App) --> permissions and change it to camera.
Second, you can try changing your startCamera() because we need to release the camera after we performed the event. Modified as follows:
public void startCamera(int texture) {
releaseCameraAndPreview();
surface = new SurfaceTexture(texture);
surface.setOnFrameAvailableListener(this);
renderer.setSurface(surface);
mCamera = Camera.open();
Camera.Parameters parameters = mCamera.getParameters();
mCamera.setParameters(parameters);
try
{
mCamera.setPreviewTexture(surface);
mCamera.startPreview();
}
catch (IOException ioe)
{
Log.w("MainActivity","CAM LAUNCH FAILED");
}
}
just add releaseCameraAndPreview() at the bottom of MainActivity()
private void releaseCameraAndPreview() {
if (mCamera != null) {
mCamera.release();
mCamera = null;
}
}
Try that and let us know. There can be other options too, eg. changing in the manifest file : Add the following
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />
Hope this helps

Error, if hide canvas-game

public class GameView extends SurfaceView
{
private GameThread mThread;
SurfaceHolder holder;
Bitmap fon = BitmapFactory.decodeResource(getResources(), R.drawable.fon);
ArrayList<Integer> lasers = new ArrayList<Integer>();
ArrayList<Integer> coordYlasers = new ArrayList<Integer>();
ArrayList<Integer> coordXlasers = new ArrayList<Integer>();
Bitmap laser = BitmapFactory.decodeResource(getResources(), R.drawable.laser);
int touchX = 0;
int touchY = 0;
int coordX = 0;
int coordY = 600;
int _coordX = 0;
private boolean running = false;
ArrayList<Bitmap> blocks = new ArrayList<Bitmap>();
public class GameThread extends Thread
{
private GameView view;
public GameThread(GameView view)
{
this.view = view;
}
public void setRunning(boolean run)
{
running = run;
}
public void run()
{
int i = 0;
while (running)
{
Canvas canvas = null;
try
{
canvas = view.getHolder().lockCanvas();
synchronized (view.getHolder())
{
onDraw(canvas);
}
}
catch (Exception e) { }
finally
{
if (canvas != null)
{
view.getHolder().unlockCanvasAndPost(canvas);
}
}
}
}
}
public GameView(Context context)
{
super(context);
mThread = new GameThread(this);
getHolder().addCallback(new SurfaceHolder.Callback() {
public void surfaceDestroyed(SurfaceHolder holder) {
boolean retry = true;
mThread.setRunning(false);
while (retry) {
try {
mThread.join();
retry = false;
} catch (InterruptedException e) {
}
}
}
public void surfaceCreated(SurfaceHolder holder) {
mThread.setRunning(true);
mThread.start();
}
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
});
}
public boolean onTouchEvent(MotionEvent event)
{
if(event.getAction() == MotionEvent.ACTION_DOWN)
{
_coordX = coordX;
X = (int) event.getX();
Y = (int) event.getY();
}
if(event.getAction() == MotionEvent.ACTION_MOVE)
{
touchX = ((int) event.getX());
touchY = (int) event.getY();
//if (Math.abs(touchX - X) > 10){
coordX = _coordX + (touchX - X);
invalidate();
//}
}
if(event.getAction() == MotionEvent.ACTION_UP)
{
_coordX = coordX;
Y = touchY;
}
return true;
}
protected void onDraw(Canvas canvas) {
Paint paint = new Paint();
paint.setColor(Color.WHITE);
paint.setStyle(Paint.Style.FILL);
paint.setColor(Color.RED);
paint.setTextSize(20);
canvas.drawColor(Color.WHITE);
canvas.drawBitmap(fon, 0, 0, null);
canvas.drawBitmap(player, coordX, coordY, null);
canvas.drawBitmap(laser, coordLaserX, coordLaserY, null);
for (int i = 1; i<coordYlasers.size(); ++i){
canvas.drawBitmap(laser, coordXlasers.get(i), coordYlasers.get(i), null);
}
canvas.drawText("" + V, 10, 25, paint);
}
}
Application is worked excellent, but....
If I hide app, game crashes. Help, please.
Logcat error.
5553-5553/com.example.thetronuo.pregame E/AndroidRuntime﹕ FATAL
EXCEPTION: main
java.lang.IllegalThreadStateException: Thread already started.
at java.lang.Thread.start(Thread.java:1045)
at com.example.thetronuo.pregame.GameView$1.surfaceCreated(GameView.java:161)
at android.view.SurfaceView.updateWindow(SurfaceView.java:569)
at android.view.SurfaceView.onWindowVisibilityChanged(SurfaceView.java:231)
at android.view.View.dispatchWindowVisibilityChanged(View.java:7618)
at android.view.ViewGroup.dispatchWindowVisibilityChanged(ViewGroup.java:1039)
at android.view.ViewGroup.dispatchWindowVisibilityChanged(ViewGroup.java:1039)
at android.view.ViewGroup.dispatchWindowVisibilityChanged(ViewGroup.java:1039)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1211)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:989)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:4356)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:749)
at android.view.Choreographer.doCallbacks(Choreographer.java:562)
at android.view.Choreographer.doFrame(Choreographer.java:532)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:735)
at android.os.Handler.handleCallback(Handler.java:725)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:5099)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:803)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:570)
at dalvik.system.NativeStart.main(Native Method)
It's activity.
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
setContentView(new GameView(this));
}
"wait()" - unhandled exception.
Where to write "resumeThread()"?
public class GameThread extends Thread
{
private GameView view;
private boolean pause = false;
private synchronized void check(){
while(pause){
wait();
}
}
public void pauseThread(){
pause = true;
}
public void resumeThread(){
pause = false;
notify();
}
public GameThread(GameView view)
{
this.view = view;
}
I think I had the same problem. But i fixed it.
Put a this in your Thread class:
private boolean pause = false;
private synchronized void check(){
while(pause){
wait();
}
}
public void pauseThread(){
pause = true;
}
public void resumeThread(){
pause = false;
notify();
}
And when onSurfaceDestroyed is called you need to pause the thread with the method youve created: pauseThread();
also call the method check(); in the while-loop, so:
while(true){
check();
... other code ...
}
and create a onResume() method in the activity class, so whenever the user turns back to the app, it will call this method automatically, and put this code into the method:
resumeThread();
I hope this would help you, im not sure if it is all the code you need as im not at home at the moment, if it doesnt work ill have a look and edit when im at home.

Android - Sharing surfaceview between Activities or prevent drawing tasks to lock the main thread when switching activity

I am developing an app which uses a common header in all its activities.
The header contains a sort of a custom progress bar which indicates task completion. The "progress bar" is implemented by subclassing a SurfaceView and the drawing operations are managed by an inner ExecutorService.
The tasks which tell the "progress bar" to run a certain animation are issued by a Singleton custom AsyncTaskManager, which holds a reference to the custom SurfaceView and the current activity.
Some of the AsyncTasks the singleton manager controls are executed upon custom Activities onCreate method, hence sometimes the AsyncTaskManager notifies the progress bar to animate before the activity is actually displayed.
It can also happens that the user might choose to switch activity before the progressbar's drawing Runnable task is finished.
To better explain, this is what happens when I switch to some activities:
oldActivity tells the ExecutorService to cancel it's Future task that draws on the SurfaceView canvas.
newActivity's onCreate is triggered and issues the AsyncTaskManager
singleton to start a new AsyncTask.
The AsyncTask in its onPreExecute tells the progress bar to start drawing on its canvas.
The ExecutorService manages the drawing Runnable, which in turn
locks the SurfaceHolder
When the AsyncTask completes, in its onPostExecute method,
tells the surfaceview drawing Runnable to draw a different thing
according on the result.
The problem I am having is that SOMETIMES (not always - seems randomly but maybe it has to do with tasks threadpools), upon starting the new activity, the application skips frames xx where xx is apparently random (sometimes it skips ~30 frames, other times ~ 300, other times the app gets an ANR).
I have been trying to solve this for some days now, but to no avail.
I think the problem could be one of the following or a combination of both:
The drawing thread does not cancel/ends in a timely manner thus causing the SurfaceHolder to stay locked and thus preventing the Activity to take control of the View as it goes onPause/onResume and hence leading to the main thread skipping frames. The animation is by no means heavy in terms of computations (a couple of dots moving around) but it needs to last at least 300ms to properly notify the user.
The singleton AsyncTaskManager holds the reference to the "leaving activity"'s SurfaceView preventing the former to be destroyed until the surfaceholder is released and causing the frame-skipping.
I am more prone to believe the second issue is what is making Coreographer's angry and so this leads to the following question:
How can I share the SAME (as in the same instance) surfaceView (or any view, really) between all the activities or alternatively to allow the current instance of SurfaceView to be destroyed and recreated without waiting fot the threads to join/interrupt?
As it is now, the SurfaceView is being destroyed/recreated when switching between activities and I would have nothing against it if its drawing thread would stop as the new activity begins its lifecycle.
This is the custom AsyncTaskManager that holds a reference to the SurfaceView
public class AsyncTaskManager {
private RefreshLoaderView mLoader;
//reference to the customactivity holding the surfaceview
private CustomBaseActivity mActivity;
private final ConcurrentSkipListSet<RequestedTask> mRequestedTasks;
private volatile static AsyncTaskManager instance;
private AsyncTaskManager() {
mRequestedTasks = new ConcurrentSkipListSet<RequestedTask>(new RequestedTaskComparator());
}
public void setCurrentActivity(CustomBaseActivity activity) {
mActivity = activity;
if (mLoader != null) {
mLoader.onDestroy();
}
mLoader = (RefreshLoaderView) mActivity.getViewById(R.id.mainLoader);
}
This is what happens when an AsyncTask (RequestedTask in the above code snippet)
is executed
#Override
protected void onPreExecute() {
if (mLoader != null) {
mLoader.notifyTaskStarted();
}
}
#Override
protected Integer doInBackground(Void... params) {
//do the heavy lifting here...
}
#Override
protected void onPostExecute(Integer result) {
switch (result) {
case RESULT_SUCCESS:
if (mLoader != null) {
mLoader.notifyTaskSuccess();
}
break;
//TELLS THE SURFACE VIEW TO PLAY DIFFERENT ANIMATIONS ACCORDING TO RESULT ...
This is the CustomBaseActivity that holds the SurfaceView from which all others activities inherit.
public abstract class CustomBaseActivity extends FragmentActivity {
private volatile RefreshLoaderView mLoader;
//...
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
super.setContentView(R.layout.activity_base);
mLoaderContainer = (FrameLayout) findViewById(R.id.mainLoaderContainer);
mLoader = (RefreshLoaderView) findViewById(R.id.mainLoader);
//other uninteresting stuff goin on ...
And the code for the SurfaceView as well:
public class RefreshLoaderView extends SurfaceView implements SurfaceHolder.Callback {
private LoaderThread mLoaderThread;
private volatile SurfaceHolder mHolder;
private static final int ANIMATION_TIME = 600;
private final ExecutorService mExecutor;
private Future mExecutingTask;
public RefreshLoaderView(Context context) {
super(context);
...
init();
}
private void init() {
mLoaderThread = new LoaderThread();
...
}
#Override
public void surfaceChanged(SurfaceHolder holder, int arg1, int arg2, int arg3) {
...
mHolder = this.getHolder();
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
//uninteresting stuff here
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
stopThread();
}
private void stopThread() {
mLoaderThread.setRunning(false);
if (mExecutingTask != null) {
mExecutingTask.cancel(true);
}
}
private void startThread() {
if (mLoaderThread == null) {
mLoaderThread = new LoaderThread();
}
mLoaderThread.setRunning(true);
mExecutingTask = mExecutor.submit(mLoaderThread);
}
public void notifyTaskStarted() {
stopThread();
startThread();
mLoaderThread.setAction(LoaderThread.ANIMATION_TASK_STARTED);
}
public void notifyTaskFailed() {
mLoaderThread.setAction(LoaderThread.ANIMATION_TASK_FAILED);
}
public void notifyTaskSuccess() {
mLoaderThread.setAction(LoaderThread.ANIMATION_TASK_SUCCESS);
}
private class LoaderThread implements Runnable {
private volatile boolean mRunning = false;
private int mAction;
private long mStartTime;
private int mMode;
public final static int ANIMATION_TASK_STARTED = 0;
public final static int ANIMATION_TASK_FAILED = 1;
public final static int ANIMATION_TASK_SUCCESS = 2;
private final static int MODE_COMPLETING = 0;
private final static int MODE_ENDING = 1;
public LoaderThread() {
mMode = 0;
}
public synchronized boolean isRunning() {
return mRunning;
}
public synchronized void setRunning(boolean running) {
mRunning = running;
if (running) {
mStartTime = System.currentTimeMillis();
}
}
public void setAction(int action) {
mAction = action;
}
#Override
public void run() {
if (!mRunning) {
return;
}
while (mRunning) {
Canvas c = null;
try {
c = mHolder.lockCanvas();
synchronized (mHolder) {
//switcho quello che devo animare
if (c != null) {
switch (mAction) {
case ANIMATION_TASK_STARTED:
animationTaskStarted(c);
break;
case ANIMATION_TASK_FAILED:
animationTaskFailed(c, mMode);
break;
case ANIMATION_TASK_SUCCESS:
animationTaskSuccess(c, mMode);
break;
}
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (c != null) {
mHolder.unlockCanvasAndPost(c);
}
}
}
}
private void animationTaskStarted(Canvas canvas) {
//do an animation here
}
private void animationCloseLoaderCycle(Canvas canvas) {
//do stuff here ...
} else {
mStartTime = System.currentTimeMillis();
mMode = MODE_ENDING;
}
}
private void queryThreadClose() {
mProgress = 0;
mMode = MODE_COMPLETING;
mRunning = false;
}
private void animationTaskFailed(Canvas canvas, int mode) {
switch (mode) {
case MODE_COMPLETING:
animationCloseLoaderCycle(canvas);
break;
case MODE_ENDING:
if (System.currentTimeMillis() - mStartTime < ANIMATION_TIME) {
//notify user task is failed
} else {
queryThreadClose();
}
break;
}
}
private void animationTaskSuccess(Canvas canvas, int mode) {
switch (mode) {
case MODE_COMPLETING:
animationCloseLoaderCycle(canvas);
break;
case MODE_ENDING:
if (System.currentTimeMillis() - mStartTime < ANIMATION_TIME) {
//notify user task is failed
} else {
queryThreadClose();
}
break;
}
}
}
public void onPause() {
stopThread();
}
public void onStop() {
stopThread();
}
public void onDestroy() {
stopThread();
}
}
Using DDMS when Coreographer warns me I'm skipping frame shows that there are usually around 30 threads (daemon and normal) running, where an asynctask, the main thread and the drawing task are waiting for something.
(Also, how can I check what are they waiting for?)
Thanks in advance for your help.
Edit: these are the main thread calls when it hangs, according to DDMS Threads view:
at hava.lang.Object.wait(Native Method)
at java.lang.Thread.parkFor(Thread.java:1205)
at sun.misc.Unsafe.park(Unsafe.java:325)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:157)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:813)
...
I solved this in the end. There was a mistake in the synchronized block:
while (mRunning) {
Canvas c = null;
try {
//mistake was here
c = mHolder.lockCanvas();
synchronized (mHolder) {
if (c != null) {
//do stuff
}
}
}
I was getting the canvas outside the synchronized block, thus causing a deadlock when the activity needed to be destroyed/recreated.
moving c = mHolder.lockCanvas(); inside the synchronized block solved this.
in the end the working code is as follows:
synchronized (mHolder) {
c = mHolder.lockCanvas();
if (c != null) {
switch (mAction) {
//do stuff
}
}
}
Thanks anyway!

how to call the onDraw method of a SurfaceView using a thread class [duplicate]

I am trying to develop a game with a SurfaceView. The thing is that when I whant to destroy the thread with the method surfaceDestroyed() the application halts in thread.join(), but if don't
draw the canvas (canvas.drawColor(Color.GREEN);) in the onDraw() method everything works well.
How do I do to draw the canvas and call thread.join()??
(I tested many example games from the internet and they all have the same problem, when you hit the go back button for example, you get a FATAL EXCEPTION because the thread is still running)
Here is the code as simple as posible.
Thank you
public class GameView extends SurfaceView implements SurfaceHolder.Callback {
private SurfaceHolder holder;
private GameLoopThread gameLoopThread;
public GameView(Context context) {
super(context);
holder = getHolder();
holder.addCallback(this);
}
#Override
public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) {
}
#Override
public void surfaceCreated(SurfaceHolder arg0) {
if (gameLoopThread == null) {
gameLoopThread = new GameLoopThread(this);
gameLoopThread.setRunning(true);
gameLoopThread.start();
Log.e("thread", "iniciado");
}
}
#Override
public void surfaceDestroyed(SurfaceHolder arg0) {
gameLoopThread.setRunning(false);
boolean retry = true;
while (retry) {
try {
gameLoopThread.join();
retry = false;
} catch (InterruptedException e) {
}
}
Log.e("thread", "finalizado");
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(Color.GREEN);
}
public static class GameLoopThread extends Thread {
private static final int FPS = 20;
private GameView view;
private boolean running;
public GameLoopThread(GameViwe gameView) {
this.view = gameView;
}
public void setRunning(boolean run) {
running = run;
}
#Override
public void run() {
long ticksPS = 1000 / FPS;
long startTime;
long sleepTime;
while (running) {
Canvas c = null;
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) {
}
}
}
}
}
So the solution I gave to this problem is to change where it says:
try {
c = view.getHolder().lockCanvas();
synchronized (view.getHolder()) {
view.onDraw(c);
}
} finally {
if (c != null) {
view.getHolder().unlockCanvasAndPost(c);
}
}
on the run() method of the GameLoopThread class with:
try {
c = view.getHolder().lockCanvas();
synchronized (view.getHolder()) {
if(c != null)
view.onDraw(c);
}
} finally {
if (c != null) {
view.getHolder().unlockCanvasAndPost(c);
}
}
To finish this activity and to call another one I added a method called GameOver which works well:
private void GameOver() {
gameLoopThread.setRunning(false);
Context c = getContext();
c.startActivity(intent); //intent must be declared
((Activity) Pruebas.this.getContext()).finish();
}
I hope this helps someone.

Categories