Hey everyone I am fairly new to the android/glass development so please correct me if I am wrong.
I tried to create a High Frequency Live Card which is fine. However when I close the app, the function surfaceDestroyed() did not get trigger and thread.quit() did not get called.
I tried looking into the sample project stopwatch/timer and apparently they did not stop the thread as well. According to the documentation, surfaceDestroyed gets called right before the surface is destroy but what is my surface? I also read something call surfaceview, are they the same thing? is surfaceview my customview or livecard?
https://developers.google.com/glass/develop/gdk/ui/live-cards#creating_high-frequency_live_cards
Appreciate any kind of help!!!
/ ***
* LiveCardRender Class
*** /
public class LiveCardRender implements DirectRenderingCallback {
private static final long FRAME_TIME_MILLIS = 33;
private CustomView mCustomView;
private SurfaceHolder mHolder;
private boolean mPaused;
private RenderThread mRenderThread;
private class RenderThread extends Thread {
private boolean mShouldRun;
public RenderThread() {
mShouldRun = true;
}
private synchronized boolean shouldRun() {
return mShouldRun;
}
public synchronized void quit() {
mShouldRun = false;
}
#Override
public void run() {
while (shouldRun()) {
draw(mCustomView);
SystemClock.sleep(FRAME_TIME_MILLIS);
}
}
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
mHolder = holder;
updateRendering();
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
mHolder = null;
updateRendering();
}
#Override
public void renderingPaused(SurfaceHolder holder, boolean paused) {
mPaused = paused;
updateRendering();
}
private synchronized void updateRendering() {
boolean shouldRender = (mHolder != null) && !mPaused;
boolean rendering = mRenderThread != null;
if (shouldRender != rendering) {
if (shouldRender) {
mRenderThread = new RenderThread();
mRenderThread.start();
} else {
mRenderThread.quit();
mRenderThread = null;
}
}
}
private void draw(View view) {
Canvas canvas;
try {
canvas = mHolder.lockCanvas();
} catch (Exception e) {
return;
}
if (canvas != null) {
view.draw(canvas);
mHolder.unlockCanvasAndPost(canvas);
}
}
}
/ ***
* LaunchService Class that uses LiveCardRender to update the live card
*** /
public class LaunchService extends Service {
private static final String LIVE_CARD_TAG = "motion_card";
private TimelineManager mTimelineManager;
private LiveCard mLiveCard;
private LiveCardRender mLiveCardRender;
#Override
public void onCreate() {
mTimelineManager = TimelineManager.from(this);
}
public int onStartCommand(Intent intent, int flags, int startId) {
if (mLiveCard == null) {
mLiveCard = mTimelineManager.createLiveCard(LIVE_CARD_TAG);
mLiveCardRender = new LiveCardRender(this);
mLiveCard.setDirectRenderingEnabled(true);
mLiveCard.getSurfaceHolder().addCallback(mLiveCardRender);
mLiveCard.publish(PublishMode.REVEAL);
}
return START_STICKY;
}
#Override
public void onDestroy(){
if (mLiveCard != null && mLiveCard.isPublished()) {
if (mLiveCardRender != null) {
mLiveCard.getSurfaceHolder().removeCallback(mLiveCardRender);
}
mLiveCard.unpublish();
mLiveCard = null;
}
super.onDestroy();
}
}
It looks like the bug is here, inside onDestroy:
if (mLiveCard != null && mLiveCard.isPublished()) {
if (mLiveCardRender != null) {
mLiveCard.getSurfaceHolder().removeCallback(mLiveCardRender);
}
mLiveCard.unpublish();
By removing the callback before the unpublish method is called, the system no longer knows whose surfaceDestroyed method to call when the card is removed afterwards. You can actually remove call to removeCallback entirely; it's unnecessary.
It looks like this is a bug in some of our samples that we missed as our APIs evolved. Thanks for catching it and we'll have them updated shortly!
Related
I used this tutorial to create a SurfaceView for a game. The tutorial fixed the problems I had with handling the thread but now whenever I open the fragment it takes anywhere between 3 and 15 seconds for the screen to turn yellow.
I thought it might have something to with the code in the container activity but the only thing I changed is this fragment alone. The speed before the change was fine.
public class VirtualMapFragment extends Fragment {
private MapSurfaceView mapSurfaceView;
#Override
public void onResume() {
if (mapSurfaceView != null){
mapSurfaceView.surfaceRestart();
}
super.onResume();
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
if(mapSurfaceView == null)
mapSurfaceView = new MapSurfaceView(getActivity());
return mapSurfaceView;
}
#Override
public void onPause() {
if (mapSurfaceView != null){
mapSurfaceView.surfaceDestroyed(mapSurfaceView.getHolder());
}
super.onPause();
}
private class MapSurfaceView extends SurfaceView implements SurfaceHolder.Callback {
private SurfaceThread surfaceThread;
public MapSurfaceView(Context context) {
super(context);
setFocusable(true);
surfaceThread = new SurfaceThread(this);
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
surfaceThread.restartSurface();
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
surfaceThread.terminateSurface();
}
public void surfaceRestart(){
if (surfaceThread != null){
surfaceThread.restartSurface();
}
}
public void doDraw(Canvas canvas){
canvas.drawColor(Color.YELLOW);
}
#Override
public boolean onTouchEvent(MotionEvent event){
return false;
}
}
private class SurfaceThread implements Runnable {
private SurfaceHolder mSurfaceHolder;
private MapSurfaceView mMapSurfaceView;
private Canvas mCanvas;
volatile Thread thread;
public SurfaceThread(MapSurfaceView mapSurfaceView){
mSurfaceHolder = mapSurfaceView.getHolder();
mMapSurfaceView = mapSurfaceView;
mCanvas = new Canvas();
thread = new Thread(this);
thread.start();
}
public void terminateSurface(){
this.thread.interrupt();
}
public void restartSurface() {
if (thread.getState() == Thread.State.TERMINATED){
thread = new Thread(this);
thread.start(); // Start a new thread
}
}
public SurfaceHolder getSurfaceHolder() {
return mSurfaceHolder;
}
#Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
try {
mCanvas = mSurfaceHolder.lockCanvas(null);
if (mCanvas == null){
mSurfaceHolder = mMapSurfaceView.getHolder();
} else {
synchronized (mSurfaceHolder) {
mapSurfaceView.doDraw(mCanvas);
}
}
} finally {
if (mCanvas != null) {
mSurfaceHolder.unlockCanvasAndPost(mCanvas);
}
}
}
}
}
}
I have got a problem
When hitting the home button, my game thread class gets paused. But when re-entering the app and the onResume() method gets called, it should resume the game thread... everythin i get is a force close.
Here is my MainActivity:
public class MainActivity extends Activity {
private RelativeLayout relativeLayout;
private GameView gameView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
gameView = new GameView(this, this);
setContentView(R.layout.activity_main);
relativeLayout = (RelativeLayout)findViewById(R.id.mainView);
relativeLayout.addView(gameView);
}
#Override
protected void onPause() {
super.onPause();
gameView.pause();
}
#Override
protected void onResume() {
super.onResume();
if(gameView.getGameThread() != null) {
if(gameView.getGameThread().isRunning == false) {
gameView.resume();
}
}
}
}
gameView is following class, a surfaceView getting rendered by GameThread:
public class GameView extends SurfaceView{
private GameThread gameThread;
public GameView(Context context, Activity activity) {
super(context);
gameThread = new GameThread(this);
holder = getHolder();
holder.addCallback(new SurfaceHolder.Callback() {
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
gameThread.setRunning(true);
gameThread.start();
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
}
});
}
public GameThread getGameThread() {
return gameThread;
}
public void update() {
}
public void pause() {
gameThread.pause();
}
public void resume() {
gameThread.resumeThread();
}
}
This is the GameThread class:
public class GameThread extends Thread{
private GameView view;
public boolean isRunning = false;
public GameThread(GameView view) {
this.view = view;
}
public void setRunning(boolean setRunning) {
isRunning = setRunning;
}
public void stopThread() {
isRunning = false;
}
public void run() {
while(isRunning) {
Canvas c = null;
view.update();
try {
c = view.getHolder().lockCanvas();
synchronized (view.getHolder()) {
view.draw(c);
}
}finally {
if(c != null) {
view.getHolder().unlockCanvasAndPost(c);
}
}
}
}
public void startThread() {
isRunning = true;
}
public void pause() {
isRunning = false;
}
public void resumeThread() {
isRunning = true;
}
}
I removed the irrelevant parts of the code.
When re-entering the app the android.view.Choreographer class opens
Any ideas?
Actually you're not pausing your thread (to be strict, there's no such thing as pausing a thread). You're ending your thread.
When you call pause() you set the isRunning flag to false, that makes the thread exit the loop and the thread ends. When you set your 'isRunning' to true again, the thread won't restart.
Actually you can't reuse threads in java, you have to create a new one, because once the thread loop ends, it's dead.
I've searched over and over in this forum & google about this issue, maybe it has to do something with my thread or the way how the game is starting.
This is the skeleton of the game application, please tell me what I'm doing wrong.
This is what I'm doing in thread;
public class GameThread extends Thread
{
static final long FPS = 30;
private GameView view;
private boolean running = false;
private SurfaceHolder surfaceHolder;
public boolean isSurfaceCreated = false;
private Object mPauseLock;
private boolean mPaused;
public GameThread(SurfaceHolder surfaceHolder, Handler handler, GameView view)
{
super();
mPauseLock = new Object();
mPaused = false;
this.surfaceHolder = surfaceHolder;
this.view = view;
}
public void setRunning(boolean run)
{
running = run;
}
/**
* pause thread.
*/
public void onPause() {
synchronized (mPauseLock) {
mPaused = true;
}
}
/**
* resume thread.
*/
public void onResume() {
synchronized (mPauseLock) {
mPaused = false;
mPauseLock.notifyAll();
}
}
#Override
public void run()
{
// run our thread on high priority to keep from getting slowdown
android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_DISPLAY);
// wait for surface to become available # start
while (!isSurfaceCreated && running)
{
try {
sleep(50);
} catch (InterruptedException e) {}
}
long ticksPS = 1000 / FPS;
long startTime;
long sleepTime;
while (running){
Canvas c = null;
startTime = System.currentTimeMillis();
//long msSinceLastTick = System.currentTimeMillis() - view.lastTickMs;
try {
c = surfaceHolder.lockCanvas(null);
synchronized (surfaceHolder)
{
/*
* stop updating when pause is pressed
*/
if(mPaused == false)
{
view.update();
}
view.onDraw(c);
}
} finally {
if (c != null)
{
surfaceHolder.unlockCanvasAndPost(c);
}
}
sleepTime = ticksPS - (System.currentTimeMillis() - startTime);
try {
if (sleepTime > 0)
sleep(sleepTime);
else
sleep(10);
} catch (Exception e) {}
}
}
This is the start activity code;
import android.app.Activity;
import android.content.pm.ActivityInfo;
import android.os.Bundle;
public class test_game extends Activity {
/** Called when the activity is first created. */
private GameView Game;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Game = new GameView(this);
setContentView(Game);
}
#Override
public void onBackPressed()
{
super.onBackPressed();
//KILL ALL
finish();
System.runFinalizersOnExit(true);
System.exit(0);
}
// ===========================================================
// onPause
// ===========================================================
protected void onPause() {
super.onPause();
}
// ===========================================================
// onStop
// ===========================================================
protected void onStop() {
super.onStop();
}
// ===========================================================
// onDestroy
// ===========================================================
#Override
protected void onDestroy() {
super.onDestroy();
}
}
Added extra lines in AndroidManifest.xml
android:launchMode="singleTask"
android:alwaysRetainTaskState="true"
GameView looks like this:
public class GameView extends SurfaceView {
private SurfaceHolder holder;
private GameThread Thread = null;
private int testVar = 0;
private boolean isPaused = false;
public GameView(Context context)
{
super(context);
loadGameFiles();
holder = getHolder();
Thread = new GameThread(holder, new Handler(), this);
holder.addCallback(new SurfaceHolder.Callback()
{
#Override
public void surfaceDestroyed(SurfaceHolder holder)
{
boolean retry = true;
Thread.setRunning(false);
while (retry)
{
try {
Thread.join();
retry = false;
} catch (InterruptedException e)
{
}
}
}
#Override
public void surfaceCreated(SurfaceHolder holder)
{
Thread.isSurfaceCreated = true;
if(isPaused == true)
{
onResume();
}
Thread.setRunning(true);
Thread.start();
GameMenu();
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height)
{
}
});
setFocusable(true);
}
public boolean isPaused()
{
return this.isPaused;
}
/**
* Pauses the physics update & animation.
*/
public void onPause() {
synchronized (holder)
{
if(isPaused == false)
{
isPaused = true;
Thread.onPause();
}
}
}
public void onResume()
{
synchronized (holder)
{
if(isPaused == true)
{
isPaused = false;
Thread.onResume();
}
}
}
etc..
Thanks for the replies, the forum commentbox doesn't let me post this; anyway, here it is:
Hi Frankenstein, I've tried what you suggested here, still doesn't work. My resume code in the GameView looks like this;
public void onResume(){ synchronized (holder)
{ if(isPaused == true){
isPaused = false; //resume notification to the gameview
Thread.onResume(); //resume the thread
}
}
}
This is on the Activity class;
#Override
protected void onPause() {
super.onPause();
Game.onPause();
}
#Override
protected void onResume() {
super.onResume();
Game.onResume();
}
I have not used logcat, not sure how to use it, I can sure call it in each steps in java but where does it displays the error/logs? thanks in advance guys!
When I resume it shows following error in LogCat;
Added this line to avoid the previous problem;
Thread.isSurfaceCreated = true;
Thread.setRunning(true);
Thread.start();
loadGame();
UPDATE:
The application returns to black screen, but it's not frozen.
Errors (updated) in logcat;
12-14 16:54:52.176: ERROR/MediaPlayerService(3937): create PVPlayer
12-14 16:55:03.172: ERROR/dalvikvm(4619): Failed to write stack traces to /data/anr/traces.txt (1093 of 3526): Unknown error: 0
12-14 16:55:03.910: ERROR/dalvikvm(25464): Failed to write stack traces to /data/anr/traces.txt (2401 of 3332): Math result not representable
12-14 16:55:03.918: ERROR/dalvikvm(25279): Failed to write stack traces to /data/anr/traces.txt (-1 of 2354): Math result not representable
12-14 16:55:32.394: ERROR/imdg81(19379): IsShutDownStarted()
12-14 16:55:32.590: ERROR/imdg81(19379): IsShutDownStarted()
12-14 16:55:37.902: ERROR/MediaPlayer(19379): error (100, 0)
12-14 16:55:37.930: ERROR/SoundPool(5264): Unable to load sample: (null)
12-14 16:55:39.281: ERROR/SoundBooster(5328): readSBTable: file open error!
12-14 16:55:39.281: ERROR/AcousticEQ(5328): [AEQ] aeqcoe.txt: file open error!
12-14 16:55:39.500: ERROR/AudioService(19379): Media server died.
12-14 16:55:39.500: ERROR/AudioService(19379): Media server started.
12-14 16:55:39.996: ERROR/MediaPlayerService(5328): create PVPlayer
In your onSurfaceCreated method you have this block of code:
if(isPaused == true){
onResume();
}
Thread.setRunning(true);
Thread.start();
your call to Thread.start() is called no matter what (even if isPaused is true). Since your thread is already started in this case, you're trying to start it twice and thus getting an illegal state exception.
Thanks guys!
I've fixed this issue, the black screen was showing because the thread isn't created again but being manipulated when I entered 2nd time to the screen. Therefore, it showed an empty/null thread being manipulated as black screen. I've added following line to OnResume()
if(!Thread.isAlive())
{
Thread = new GameThread(holder, new Handler(), this);
}
This is the fix for my issue, but now I've to implement onSaveInstanceState & onRestoreInstanceState to avoid the from re-starting from beginnig.
i have a thread problem with my surfaceView, its quite common but i have not found a solution anywhere.
I use a SurfaceView as a Background in a frame layout:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/blobmusic_layout"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<com.myapp.Background
android:id="#+id/background_blobmusic"
android:layout_width="fill_parent"
android:layout_height="fill_parent"/>
...
and my background class is the tipical surfaceview class, with the holder, the thread wich will call our onDraw method, etc. Some code to avoid questions (init() is called in the constructor methods):
public void init() {
ready = false;
holder = getHolder();
holder.addCallback( new Callback() {
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
// TODO Auto-generated method stub
boolean retry = true;
while (retry) {
try {
bgdt.setRunning(false);
bgdt.join();
retry = false;
} catch (InterruptedException e) {}
}
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
// TODO Auto-generated method stub
holder.setFormat(PixelFormat.RGBA_8888);
estrelles = new Estrelles(getResources(),getHeight());
bgdt.setRunning(true);
bgdt.start();
ready = true;
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
// TODO Auto-generated method stub
}
});
and my thread class (bgtd):
public class BGDrawThread extends Thread {
private Background view;
static final long FPS = 60;
private boolean running = false;
public BGDrawThread(Background view) {
this.view = view;
}
public boolean isRunning() {
return running;
}
public void setRunning(boolean run) {
running = run;
}
#Override
public void run() {
long ticksPS = 1000/FPS;
long startTime;
long sleepTime;
//fps checker
long contms=0;
long lasttimecheck = System.currentTimeMillis();
int fps=0;
while (running) {
if(contms>1000) {
//souts ??
contms=0;
fps=0;
lasttimecheck = System.currentTimeMillis();
}
else {
fps++;
contms+=System.currentTimeMillis()-lasttimecheck;
lasttimecheck=System.currentTimeMillis();
}
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) {}
}
}
}
This works fine, until another activity comes to front and then we come back from it, that crashes.
Here's my code of onpause/onresume methods of the class:
#Override
protected void onResume() {
super.onResume();
Background bmbg = (Background) findViewById(R.id.background_blobmusic);
bmbg.resumethread();
}
#Override
protected void onPause() {
super.onPause();
Background bmbg = (Background) findViewById(R.id.background_blobmusic);
bmbg.stopthread();
}
and the strop/resume thread in the surfaceview class:
public void stopthread() {
if(!bgdt.isAlive()) return;
boolean retry = true;
while (retry) {
try {
bgdt.setRunning(false);
bgdt.join();
retry = false;
} catch (InterruptedException e) {}
}
}
public void resumethread() {
if(ready && !bgdt.isRunning()) {
bgdt.setRunning(true);
bgdt.start();
}
}
All help is welcome :)
EDIT: i made it run (probably not an elegant solution, but i leave the code here):
(look at the comment below to see the other changes)
public void startthread() {
if(!surfacecreated) return;
if(threadrunning) return;
bgdt = new BGDrawThread(this);
bgdt.setRunning(true);
bgdt.start();
threadrunning = true;
}
public void stopthread() {
boolean retry = true;
while (retry) {
try {
bgdt.setRunning(false);
bgdt.join();
threadrunning = false;
retry = false;
} catch (InterruptedException e) {}
}
}
IIRC surface is ready for manipulations only after surfaceChanged() callback, not surfaceCreated() , and surfaceChanged() is called after onResume() - move your initialisation and thread startup there ( to surfaceChanged() )
I have a game I'm developing for Android 2.x. When the player looses, I want to automatically exit that and go back to the previous menu of the game. This may be a simple question, but how do I do that?
I have a MenuActivity, which calls GameActivity, which calls the GameView (implementation of SurfaceView) where the game logic is.
UPDATE: No, wait, it's more complex than that. To make this game I followed the MoonLander example, which used threads.
GameView:
public class GameActivity extends Activity
{
GameView gameView;
GameThread gThread;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
gameView = new GameView(this);
setContentView(gameView);
gThread = gameView.getThread();
gThread.doStart();
}
}
GameView:
package com.cp.balldrop;
public class GameView extends SurfaceView implements SurfaceHolder.Callback
{
class GameThread extends Thread
{
public GameThread(SurfaceHolder sHolder, Context context, Handler handler)
{
//Code
}
public void doStart()
{
synchronized (mSurfaceHolder)
{
}
}
#Override
public void run()
{
while (running)
{
Canvas c = null;
try
{
c = mSurfaceHolder.lockCanvas(null);
synchronized (mSurfaceHolder)
{
doDraw(c);
}
}
finally
{
if (c != null)
{
mSurfaceHolder.unlockCanvasAndPost(c);
}
}
}
}
protected void doDraw(Canvas canvas)
{
//code
}
}
/** Handle to the application context, used to e.g. fetch Drawables. */
private Context mContext;
/** Pointer to the text view to display "Paused.." etc. */
private TextView mStatusText;
/** The thread that actually draws the animation */
private GameThread gThread;
public GameView(Context context)
{
super(context);
// register our interest in hearing about changes to our surface
SurfaceHolder holder = getHolder();
holder.addCallback(this);
// create thread only; it's started in surfaceCreated()
gThread = new GameThread(holder, context, new Handler()
{
#Override
public void handleMessage(Message m)
{
// mStatusText.setVisibility(m.getData().getInt("viz"));
// mStatusText.setText(m.getData().getString("text"));
}
});
setFocusable(true);
}
public GameThread getThread()
{
return gThread;
}
public void surfaceCreated(SurfaceHolder holder)
{
gThread.start();
}
#Override
public void surfaceDestroyed(SurfaceHolder holder)
{
boolean retry = true;
gThread.setRunning(false);
while (retry)
{
try
{
gThread.join();
retry = false;
}
catch (InterruptedException e)
{
}
}
}
}
In my thread, I call the following method from my surfaceview's doTouch:
public void doLose() {
synchronized (mSurfaceHolder) {
//quit to mainmenu
((Activity) mContext).finish();
}
}
This seems to shut down the surfaceview and the activity hosting it, dumping me back in my main-menu activity. Note the casting and the predefined context.
What about calling finish() on the GameActivity?