i´m new to Android programming and I have a Question.
I have a Countdown that will count down from 60 to zero when i am touching the screen.
I want now that this Countdown will survive the Activity lifecycling even when onDestroy is called. So I need a Service for that.
Wenn the countdown is finished a notification will be send. But if the user want to open the App again before the countdown in the Service is finished, no notification should be sended and the service has to be stopped.
What is the best practice to implement those features? What is here the way to go?
Thank you very much for answers!
My approach is that I have a class with the countdown methods and the service
//Countdownclass
TimerClass.java
public class TimerClass {
private static final String TAG = "MyLog";
private static final long TIMERINTERVAL = 1000;
//Eigenschaften --------------------------------------------------------------------
private boolean isActiv = false;
private Paint paint;
private long timerTime;
private double actualTime;
private int x;
private int y;
private CountDownTimer countDownTimer;
private String timeString;
//Konstruktor --------------------------------------------------------------------
public TimerClass(int x, int y) {
this.x = x;
this.y = y;
paint = new Paint();
paint.setStyle(Paint.Style.FILL);
paint.setColor(Color.YELLOW);
paint.setAntiAlias(true);
paint.setTextAlign(Align.RIGHT);
paint.setTextSize(70);
}
public TimerClass() {
}
//Methoden --------------------------------------------------------------------
public void draw(Canvas canvas) {
if(isActiv) {
canvas.drawText(timeString, x, y, paint);
}
}
public void createTimer(long timerTime) {
this.timerTime = timerTime;
countDownTimer = new CountDownTimer(timerTime, TIMERINTERVAL) {
#Override
public void onTick(long millisUntilFinished) {
actualTime = (double) millisUntilFinished / TIMERINTERVAL;
NumberFormat nf = new DecimalFormat("##.#");
nf.format(actualTime);
if(actualTime < 10) {
new DecimalFormat();
timeString = "0:0" + nf.format(actualTime - 1);
} else {
timeString = "0:" + nf.format(actualTime - 1);
}
}
#Override
public void onFinish() {
isActiv = false;
}
};
}
public void timerStart() {
countDownTimer.start();
isActiv = true;
}
public void timerStop() {
if(isActiv) {
countDownTimer.cancel();
}
isActiv = false;
}
public boolean isActiv() {
return isActiv;
}
public void setActiv(boolean isActiv) {
this.isActiv = isActiv;
}
public double getActualTime() {
return actualTime;
}
public void setActualTime(double actualTime) {
this.actualTime = actualTime;
}
And my Service:
public class TimerService extends Service {
//class with the countdowntimer
private TimerClass timerClass;
private long startTime;
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public void onCreate() {
super.onCreate();
timerClass = new TimerClass();
startTime = (long) timerClass.getActualTime();
timerClass.createTimer(startTime);
}
#Override
public void onDestroy() {
timerClass.timerStop();
super.onDestroy();
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
timerClass.timerStart();
if(timerClass.getActualTime() <= 0) {
// send the notification
}
return super.onStartCommand(intent, flags, startId);
}
I wanted to start the Service in my mainActivies onPause method and stop it when onResume is called. But i´ve have tried it many times and failed.. so i have no code for my service in my activity.
( Sorry for my broken english :( )
Related
This question already has answers here:
How to detect shake event with android?
(9 answers)
Closed 3 years ago.
I want to detect the shaking gesture of the device. When shaking, an alert should be made.
I want to make a service for this, but I am confused that which service I should use for the purpose.
You can use Accelerometer sensor to detect the device movement. Following is the implementation
public class ShakerService extends Service {
public static final String TAG = "ShakerService";
private Shaker mShaker;
#Override
public void onCreate() {
super.onCreate();
getApplicationContext().getSystemService(Context.VIBRATOR_SERVICE);
Log.d(TAG,"Starting service : onCreate()");
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG,"iniciando");
mShaker = new Shaker(this);
mShaker.setOnShakeListener(new Shaker.OnShakeListener() {
#Override
public void onShake() {
Toast.makeText(getApplicationContext(),"here", Toast.LENGTH_SHORT);
Log.d(TAG,"OnShake");
}
});
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
notifyit();
}
return START_STICKY;
}
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public void onDestroy() {
mShaker.pause();
super.onDestroy();
}
#RequiresApi(Build.VERSION_CODES.O)
private String createNotificationChannel(String channelId, String channelName) {
Log.d(TAG,"Notification channel is creating");
NotificationChannel chan = new NotificationChannel(channelId,
channelName, NotificationManager.IMPORTANCE_NONE);
NotificationManager service = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
service.createNotificationChannel(chan);
return channelId;
}
#RequiresApi(api = Build.VERSION_CODES.O)
public void notifyit() {
Log.d(TAG,"Notification starting");
Intent i = new Intent(this, SegmentTesterAct.class);
i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP
| Intent.FLAG_ACTIVITY_SINGLE_TOP);
PendingIntent pi = PendingIntent.getActivity(this, 0, i, 0);
String notificationChannel = createNotificationChannel("shakeitoff", "shakeitoff");
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, notificationChannel);
Notification notification = builder.setContentIntent(pi)
.setSmallIcon(R.drawable.nextome).setTicker("shake").setWhen(System.currentTimeMillis())
.setAutoCancel(true).setContentTitle(getText(R.string.app_name))
.setContentText(getText(R.string.intro)).build();
startForeground(1337, notification);
}
}
public class Shaker implements SensorListener , SensorEventListener {
public static float xShake = 0.0f;
public static float yShake = 0.0f;
public static float zShake = 0.0f;
public static final float NOISE = 0.01f;
public static final float OLDMIN = -10f;
public static final float OLDMAX = 10;
public static final float NEWMIN = 0;
public static final float NEWMAX = 1;
private static final int BASE_FORCE_THRESHOLD = 350;
private static final int TIME_THRESHOLD = 200;
private static final int SHAKE_TIMEOUT = 500;
private static final int SHAKE_DURATION = 1000;
private static final int SHAKE_COUNT = 3;
private SensorManager mSensorMgr;
private float mLastX = -1.0f, mLastY = -1.0f, mLastZ = -1.0f;
private long mLastTime;
private OnShakeListener mShakeListener;
private Context mContext;
private int mShakeCount = 0;
private long mLastShake;
private long mLastForce;
private SharedPreferences preferences;
public Shaker(Context context) {
mContext = context;
preferences = context.getSharedPreferences("shakeitoff", context.MODE_PRIVATE);
resume();
}
public void setOnShakeListener(OnShakeListener listener) {
mShakeListener = listener;
}
public void resume() {
mSensorMgr = (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE);
if (mSensorMgr == null) {
throw new UnsupportedOperationException("Sensors not supported");
}
boolean supported = mSensorMgr.registerListener(this, SensorManager.SENSOR_ACCELEROMETER, SensorManager.SENSOR_DELAY_GAME);
if (!supported) {
mSensorMgr.unregisterListener(this, SensorManager.SENSOR_ACCELEROMETER);
throw new UnsupportedOperationException("Accelerometer not supported");
}
}
public void onAccuracyChanged(int sensor, int accuracy) {
}
public void pause() {
if (mSensorMgr != null) {
mSensorMgr.unregisterListener(this, SensorManager.SENSOR_ACCELEROMETER);
mSensorMgr = null;
}
}
public void onSensorChanged(int sensor, float[] values) {
if (sensor != SensorManager.SENSOR_ACCELEROMETER) return;
long now = System.currentTimeMillis();
values[SensorManager.DATA_X] = -values[SensorManager.]
boolean preX =false;
boolean preY =false;
float xDiff = (mLastX - values[SensorManager.DATA_X]);
float yDiff = (mLastY - values[SensorManager.DATA_Y]);
float zDiff = (mLastZ - values[SensorManager.DATA_Z]);
//Based on xDiff, yDiff, zDiff you can detect shaking
mLastX = values[SensorManager.DATA_X];
mLastY = values[SensorManager.DATA_Y];
mLastZ = values[SensorManager.DATA_Z];
}
#Override
public void onSensorChanged(SensorEvent event) {
}
#Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
public interface OnShakeListener {
public void onShake();
}
}
You need to start ShakerService like this
startService(new Intent(this, ShakerService.class)); from your activity
I am running into an issue in my app. When my game ends (when life == 0) I am attempting to switch to a game over screen by using a different activity. When the game ends, the app simply crashes. I have included the XML for the activity I am trying to switch from as well as indicating where the app crashes. If anyone could help out, that would be great! Thanks.
activity_game.XML:
SurfaceView I am trying to switch from once game ends:
public class SVGameView extends SurfaceView implements Runnable {
private SurfaceHolder holder;
Thread thread = null;
volatile boolean running = false;
static final long FPS = 30;
private Sprite sprite;
private long lastClick;
private Bitmap ball, gameOver;
//private int x = 200, y = 200;
private int scorePosX = 100;
private int scorePosY = 100;
private int countScore = 0;
private int life = 1;
public SVGameView(Context context) {
super(context);
thread = new Thread(this);
holder = getHolder();
holder.addCallback(new SurfaceHolder.Callback() {
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
boolean retry = true;
running = false;
while (retry) {
try {
thread.join();
retry = false;
} catch (InterruptedException e) {
}
}
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
running = true;
thread.start();
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format,
int width, int height) {
}
});
ball = BitmapFactory.decodeResource(getResources(), R.drawable.ball2);
gameOver = BitmapFactory.decodeResource(getResources(),R.drawable.endscreen);
sprite = new Sprite(this, ball);
}
#Override
public void run() {
long ticksPS = 1000 / FPS;
long startTime;
long sleepTime;
while (running) {
Canvas c = null;
startTime = System.currentTimeMillis();
try {
c = getHolder().lockCanvas();
synchronized (getHolder()) {
update();
draw(c);
}
} finally {
if (c != null) {
getHolder().unlockCanvasAndPost(c);
}
}
sleepTime = ticksPS-(System.currentTimeMillis() - startTime);
try {
if (sleepTime > 0)
thread.sleep(sleepTime);
else
thread.sleep(10);
} catch (Exception e) {}
}
}
private void update(){
sprite.update();
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(Color.WHITE);
Paint paint = new Paint();
canvas.drawPaint(paint);
paint.setColor(Color.WHITE);
paint.setTextSize(48);
canvas.drawText("Score: " + countScore, scorePosX, scorePosY, paint);
canvas.drawText("Lives: " + life, 500, 100, paint);
sprite.onDraw(canvas);
//Crashes here
if(life == 0) {
getContext().startActivity(new Intent(getContext(), SVGameOver.class));
}
}
#Override
public boolean onTouchEvent(MotionEvent event) {
if(System.currentTimeMillis()-lastClick > 300){
lastClick = System.currentTimeMillis();
}
synchronized (getHolder()){
if(sprite.isHit(event.getX(), event.getY())){
countScore += 1;
sprite.increase();
}else{
life --;
}
}
return super.onTouchEvent(event);
}
}
Activity I am trying to reach once the game ends:
public class SVGameOver extends Activity {
private Bitmap gameOverScreen;
#Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_game);
gameOverScreen = BitmapFactory.decodeResource(getResources(), R.drawable.endscreen);
}
protected void onDraw(Canvas canvas){
canvas.drawBitmap(gameOverScreen, 0,0,null);
}
}
I think logcat is asking you the right question: "have you declared this activity in your AndroidManifest.xml" ?
If you think you did it, It's highly probable you did it in a wrong way, most of the times that you think you added an Activity to the manifest but you are receiving this kind of crash, 99,9% of the time you declared it with a wrong namespace
Declare SVGameOver activity in your AndroidManifest.xml:
<activity
android:name="com.example.welcome.assignment2.SVGameOver">
...
</activity>
I want to draw math-function like y=x^2+1 with androidPlot library.I have "SimpleXYPlot". It works but I don't know how to change it from sin to my function.
Here's the code:
public class DynamicXYPlotActivity extends Activity {
// redraws a plot whenever an update is received:
private class MyPlotUpdater implements Observer {
Plot plot;
public MyPlotUpdater(Plot plot) {
this.plot = plot;
}
#Override
public void update(Observable o, Object arg) {
plot.redraw();
}
}
private XYPlot dynamicPlot;
private MyPlotUpdater plotUpdater;
SampleDynamicXYDatasource data;
private Thread myThread;
#Override
public void onCreate(Bundle savedInstanceState) {
// android boilerplate stuff
super.onCreate(savedInstanceState);
setContentView(R.layout.dynamicxyplot_example);
// get handles to our View defined in layout.xml:
dynamicPlot = (XYPlot) findViewById(R.id.dynamicXYPlot);
plotUpdater = new MyPlotUpdater(dynamicPlot);
// only display whole numbers in domain labels
dynamicPlot.getGraphWidget().setDomainValueFormat(new DecimalFormat("0"));
// getInstance and position datasets:
data = new SampleDynamicXYDatasource();
SampleDynamicSeries sine1Series = new SampleDynamicSeries(data, 0, "Sine 1");
SampleDynamicSeries sine2Series = new SampleDynamicSeries(data, 1, "Sine 2");
LineAndPointFormatter formatter1 = new LineAndPointFormatter( Color.rgb(0, 0, 0), null, null, null );
formatter1.getLinePaint().setStrokeJoin(Paint.Join.ROUND);
formatter1.getLinePaint().setStrokeWidth(10);
dynamicPlot.addSeries( sine1Series,formatter1 );
LineAndPointFormatter formatter2 = new LineAndPointFormatter(Color.rgb(0, 0, 200), null, null, null);
formatter2.getLinePaint().setStrokeWidth(10);
formatter2.getLinePaint().setStrokeJoin(Paint.Join.ROUND);
//formatter2.getFillPaint().setAlpha(220);
dynamicPlot.addSeries(sine2Series, formatter2);
// hook up the plotUpdater to the data model:
data.addObserver(plotUpdater);
// thin out domain tick labels so they dont overlap each other:
dynamicPlot.setDomainStepMode(XYStepMode.INCREMENT_BY_VAL);
dynamicPlot.setDomainStepValue(5);
dynamicPlot.setRangeStepMode(XYStepMode.INCREMENT_BY_VAL);
dynamicPlot.setRangeStepValue(10);
dynamicPlot.setRangeValueFormat(new DecimalFormat("###.#"));
// uncomment this line to freeze the range boundaries:
dynamicPlot.setRangeBoundaries(-100, 100, BoundaryMode.FIXED);
// create a dash effect for domain and range grid lines:
DashPathEffect dashFx = new DashPathEffect(
new float[] {PixelUtils.dpToPix(3), PixelUtils.dpToPix(3)}, 0);
dynamicPlot.getGraphWidget().getDomainGridLinePaint().setPathEffect(dashFx);
dynamicPlot.getGraphWidget().getRangeGridLinePaint().setPathEffect(dashFx);
}
#Override
public void onResume() {
// kick off the data generating thread:
myThread = new Thread(data);
myThread.start();
super.onResume();
}
#Override
public void onPause() {
data.stopThread();
super.onPause();
}
class SampleDynamicXYDatasource implements Runnable {
// encapsulates management of the observers watching this datasource for update events:
class MyObservable extends Observable {
#Override
public void notifyObservers() {
setChanged();
super.notifyObservers();
}
}
private static final double FREQUENCY = 5; // larger is lower frequency
private static final int MAX_AMP_SEED = 100; //100
private static final int MIN_AMP_SEED = 10; //10
private static final int AMP_STEP = 1;
public static final int SINE1 = 0;
public static final int SINE2 = 1;
private static final int SAMPLE_SIZE = 30;
private int phase = 0;
private int sinAmp = 1;
private MyObservable notifier;
private boolean keepRunning = false;
{
notifier = new MyObservable();
}
public void stopThread() {
keepRunning = false;
}
//#Override
public void run() {
try {
keepRunning = true;
boolean isRising = true;
while (keepRunning) {
Thread.sleep(100); // decrease or remove to speed up the refresh rate.
phase++;
if (sinAmp >= MAX_AMP_SEED) {
isRising = false;
} else if (sinAmp <= MIN_AMP_SEED) {
isRising = true;
}
if (isRising) {
sinAmp += AMP_STEP;
} else {
sinAmp -= AMP_STEP;
}
notifier.notifyObservers();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public int getItemCount(int series) {
return SAMPLE_SIZE;
}
public Number getX(int series, int index) {
if (index >= SAMPLE_SIZE) {
throw new IllegalArgumentException();
}
return index;
}
public Number getY(int series, int index) {
if (index >= SAMPLE_SIZE) {
throw new IllegalArgumentException();
}
double angle = (index + (phase))/FREQUENCY;
double amp = sinAmp * Math.sin(angle);
switch (series) {
case SINE1:
return amp;
case SINE2:
return -amp;
default:
throw new IllegalArgumentException();
}
}
public void addObserver(Observer observer) {
notifier.addObserver(observer);
}
public void removeObserver(Observer observer) {
notifier.deleteObserver(observer);
}
}
class SampleDynamicSeries implements XYSeries {
private SampleDynamicXYDatasource datasource;
private int seriesIndex;
private String title;
public SampleDynamicSeries(SampleDynamicXYDatasource datasource, int seriesIndex, String title) {
this.datasource = datasource;
this.seriesIndex = seriesIndex;
this.title = title;
}
#Override
public String getTitle() {
return title;
}
#Override
public int size() {
return datasource.getItemCount(seriesIndex);
}
#Override
public Number getX(int index) {
return datasource.getX(seriesIndex, index);
}
#Override
public Number getY(int index) {
return datasource.getY(seriesIndex, index);
}
}
}
=======================================================
After what "Nick" said and other minor addition, I got this result:
but as we know :
https://www.google.com/search?q=y%3Dx%5E2%2B1&oq=y%3Dx%5E2%2B1&aqs=chrome..69i57j0l5.7056j0j7&sourceid=chrome&es_sm=93&ie=UTF-8
Now how to make the left side?
Using the code above can modify SampleDynamicXYDatasource to do what you want. All that code does is generate some data in a sine pattern. I don't know how your x values are going to be generated so here's a modified SampleDynamicXYDatasource.getY(...) that just uses the original code where x=index above and uses your function to generate the y-values:
public Number getY(int series, int index) {
if (index >= SAMPLE_SIZE) {
throw new IllegalArgumentException();
}
Number x = getX(series, index);
double y = Math.pow(x.doubleValue(), 2) + 1;
switch (series) {
case SINE1:
return y;
case SINE2:
return -y;
default:
throw new IllegalArgumentException();
}
}
You'll notice that when you make this change, the plot appears to no longer be animated. (it actually still is but that's besides the point) This is because y is now purely a function of x and the x values never change.
As far as how to stop the animation, the plot redraws whenever plot.redraw() is called, which in the example above is in response to an event being continuously fired by events generated by the thread being run on the Runnable instance of SampleDynamicXYDatasource. Using the example above, the simplest way to stop the animation is to replace:
#Override
public void onResume() {
// kick off the data generating thread:
myThread = new Thread(data);
myThread.start();
super.onResume();
}
with:
#Override
public void onResume() {
dynamicPlot.redraw();
super.onResume();
}
Try replace this>>
public Number getX(int series, int index) {
if (index >= SAMPLE_SIZE) {
throw new IllegalArgumentException();
}
return index;
}
To this>>
public Number getX(int series, int index) {
if (index >= SAMPLE_SIZE) {
throw new IllegalArgumentException();
}
return index - 15;
}
I am a newbie in android and started with livewallpaper development using Wallpaper Engine. My problem is the run() method inside Runnable is not called dont know why.... Am I missing something?
Here is my code:
public class Hearts extends WallpaperService {
public static boolean LANDSCAPE;
public static Paint backgroundPaint;
public static Point backgroundSize;
public static Point globalCanvasSize;
public static boolean landscapeMode = false;
public static int maxScreenSize;
Bitmap background;
private final Handler mHandler = new Handler();
public void onCreate() {
super.onCreate();
}
public WallpaperService.Engine onCreateEngine() {
return new HeartsEngine();
}
public void onDestroy() {
super.onDestroy();
}
class HeartsEngine extends Engine {
private final int amountOfFlakes = 40;
private final Paint backgroundPaint = new Paint();
private Canvas c;
public Flake[] flakeArray = new Flake[40];
final SurfaceHolder holder = getSurfaceHolder();
private final Runnable mDrawCube = new Runnable() {
#Override
public void run() {
Log.d("tag", "Inside Run Called");
drawFrame();
}
};
float mXOffset;
float mYOffset;
private float oldScreen;
Random random = new Random();
Resources res = Hearts.this.getResources();
HeartsEngine() {
super();
Log.d("tag", "Constructyor");
}
void drawFrame() {
Log.d("tag", "drawFrame");
this.oldScreen -= this.mXOffset;
Flake.scrollSpeed = 48.0F * this.oldScreen;
this.oldScreen = this.mXOffset;
this.c = null;
this.c = this.holder.lockCanvas();
try {
if (this.c != null)
this.c.drawBitmap(Hearts.this.background, 0.0F, 0.0F, null);
for (int i = 0;; i++) {
if (i >= 40) {
this.holder.unlockCanvasAndPost(this.c);
Hearts.this.mHandler.postDelayed(this.mDrawCube, 16L);
}
this.flakeArray[i].updateFlake(this.random, this.res,
this.c);
}
} catch (IllegalArgumentException localIllegalArgumentException) {
// break label94;
localIllegalArgumentException.printStackTrace();
return;
}
}
You call drawFrame() only inside the mDrawCube Runnable. So there is no a first time call for the drawFrame() method
I'm working on a Android game using SurfaceView, the game works fine, so I want to pause it when the user hits HOME key but when getting back the SurfaceView disappears(black background Edit: sometimes the drawing appears) and buttons are inactive in both cases then I get ANR.
public class MainActivity extends Activity {
private Game game;
MainPanel mp;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
game = new Game();
LoadLevel();
Init();
setContentView(R.layout.main);//contains surfaceview and buttons
mp = (MainPanel) findViewById(R.id.SurfaceView01);
mp.Init(game,this);
Button btnTop = (Button) findViewById(R.id.buttonTop);
Button btnBottom = (Button) findViewById(R.id.buttonBottom);
Button btnLeft = (Button) findViewById(R.id.buttonLeft);
Button btnRight = (Button) findViewById(R.id.buttonRight);
btnTop.setOnClickListener...
}
private void Init() {
...
}
public void LoadLevel() {
...
}
#Override
protected void onResume() {
super.onResume();
Log.d("Tag","Resume");
}
#Override
protected void onPause() {
super.onPause();
Log.d("Tag","Pause");
}
}
public class MainPanel extends SurfaceView implements SurfaceHolder.Callback {
private MainThread thread;
private Game game;
private MainActivity act;
public MainPanel(Context context, AttributeSet attrs) {
super(context,attrs);
getHolder().addCallback(this);
}
public MainThread getThread() {
return thread;
}
public void Init(Game game, MainActivity act){
this.game = game;
this.act = act;
}
public void Move(int dir) {
...
}
public void update() {
...
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,int height) {}
#Override
public void surfaceCreated(SurfaceHolder holder) {
if (thread == null) {
thread = new MainThread(getHolder(), this);
thread.Start();
thread.start();
}
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
thread.Stop();
boolean retry = true;
while (retry) {
try {
thread.join();
retry = false;
} catch (InterruptedException e) {}
}
thread = null;
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(Color.BLACK);
//drawing...
}
}
public class MainThread extends Thread {
private final static int MAX_FPS = 30;
private final static int MAX_FRAME_SKIPS = 3;
private final static int FRAME_PERIOD = 1000 / MAX_FPS;
private SurfaceHolder surfaceHolder;
private MainPanel gamePanel;
private boolean run = false;
private boolean start = false;
public MainThread(SurfaceHolder surfaceHolder, MainPanel gamePanel) {
super();
this.surfaceHolder = surfaceHolder;
this.gamePanel = gamePanel;
}
public void Start() {
this.run = true;
this.start = true;
}
public void Pause() {
this.start = false;
}
public void Resume() {
this.start = true;
}
public void Stop() {
this.run = false;
}
#Override
public void run() {
Canvas c = null;
long beginTime; // the time when the cycle begun
long timeDiff; // the time it took for the cycle to execute
int sleepTime; // ms to sleep (<0 if we're behind)
int framesSkipped; // number of frames being skipped
while(run) {
while(start) {
try {
c = surfaceHolder.lockCanvas(null);
synchronized (surfaceHolder) {
beginTime = System.currentTimeMillis();
framesSkipped = 0; // resetting the frames skipped
// update game state
gamePanel.update();
// render state to the screen draws the canvas on the panel
if(c!=null) gamePanel.onDraw(c);
// calculate how long did the cycle take
timeDiff = System.currentTimeMillis() - beginTime;
// calculate sleep time
sleepTime = (int)(FRAME_PERIOD - timeDiff);
if (sleepTime > 0) {
// if sleepTime > 0 we're OK
try {
// send the thread to sleep for a short period very useful for battery saving
Thread.sleep(sleepTime);
} catch (InterruptedException e) {}
}
while (sleepTime < 0 && framesSkipped < MAX_FRAME_SKIPS) {
// we need to catch up update without rendering
gamePanel.update();
// add frame period to check if in next frame
sleepTime += FRAME_PERIOD;
framesSkipped++;
}
}
} finally {
if (c != null) surfaceHolder.unlockCanvasAndPost(c);
}
}
}
}
}
Thx to this post why is my runnable giving ANR? I could solve the problem, I isolated the MainThread from the UI thread for further details check the best answer.