When using SurfaceView I am getting around 6 FPS, when I'm drawing pretty much nothing.
I am only drawing a color. So with pretty much NO drawing operations, I'm getting 6 fps, which is extremely horrible.
One thing to note however is that I am using an emulator(genymotion emulator) and have not tried on a real device. However even WITH an emulator when I am drawing nothing I should be getting way more than 6 fps..
So here's my code (import statements not included):
//Activity class which starts the SurfaceView
public class MainActivity extends Activity {
SView surfaceView;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
surfaceView = new SView(this);
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
setContentView(surfaceView);
}
}
//SurfaceView class which draws everything in another thread
class SView extends SurfaceView implements SurfaceHolder.Callback{
SurfaceHolder holder;
Canvas mainCanvas;
Paint mainPaint;
int width;
long start, end, fps;
public SView(Context context) {
super(context);
holder = getHolder();
holder.addCallback(this);
mainPaint = new Paint();
mainPaint.setColor(Color.BLACK);
fps = 0;
}
public void surfaceCreated(SurfaceHolder holder) {
new Thread(){
public void run() {
start = System.currentTimeMillis();
while (true) {
end = System.currentTimeMillis();
if ((end - start) >= 1000) {
Log.d("PERSONAL", "FPS: " + fps + " milliseconds " + Long.toString(end - start));
start = System.currentTimeMillis();
fps = 0;
}
customDraw();
}
}
}.start();
}
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
public void surfaceDestroyed(SurfaceHolder holder) {
}
public void customDraw(){
mainCanvas = holder.lockCanvas();
mainCanvas.drawRGB(0,150,0);
fps++;
holder.unlockCanvasAndPost(mainCanvas);
}
}
So my question is how can I solve this problem of having such a horrible fps?
Related
I am building an obstacles game and i want to show on screen multiple obstacles. The idea of "moving" the obstacles is to set the shapeable image view on and off until it comes to the bottom of the screen. these are all the relevant methods:
on create:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_game_screen);
gm = new GameManager();
findViews();
game_FAB_left.setOnClickListener(v -> moveLeft());
game_FAB_right.setOnClickListener(v -> moveRight());
initCharacterPositions();
startTimer();
playBackgroundMusic();
threadPool = Executors.newFixedThreadPool(2);
startTime = System.currentTimeMillis();
}
the timer with a 1 second delay
private Timer timer;
private void startTimer() {
timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
#Override
public void run() {
runOnUiThread(()->{
updateTimerUI();
runObstaclesTasks();
});
}
},DELAY,DELAY);
}
the thread pool execution:
private void runObstaclesTasks() {
threadPool.execute(new Runnable() {
#Override
public void run() {
createObstacle();
}
});
}
creating an obstacle and calling the movement function
private void createObstacle() {
Obstacle o = new Obstacle();
int index = getRandomNum(0,NUMBER_OF_POSITIONS);
LinearLayout chosenLayout = (LinearLayout) game_LL_obstaclesLayout.getChildAt(index);
int numberOfObstaclesPosition = gm.getNumberOfObstaclesPosition();
obstacleMovement(o, index, chosenLayout, numberOfObstaclesPosition);
}
the loop that move the obstacle. there is a delay (Thread.sleep) to make the movement effect (visible-invisible):
public void obstacleMovement(Obstacle o, int index, LinearLayout chosenLayout, int numberOfObstaclesPosition) {
for (int i = 0; i < numberOfObstaclesPosition; i++) {
ShapeableImageView obstacleImg = (ShapeableImageView) chosenLayout.getChildAt(o.getPosition());
showObstacle(obstacleImg);
if(gm.isCollision(index, o.getPosition())){
setLifeImages();
Vibrator v = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
String msg = "Ouch! " + String.valueOf(gm.getLifeCount()) + " lives left";
toast(msg);
gm.vibrate(v);
}
delay();
hideObstacle(obstacleImg);
o.setNextPosition();
}
}
game demonstration
My android app is crashing when using SharedPreferences to try and carry the final score over from when the game ends. The app crashes inside the onTouchEvent at the line.
SharedPreferences.Editor editor = mySharedPreferences.edit();
The idea is when the game ends it final score that is within the SVGameView to carry over into the SVGameOver class and display there. If anyone could give some advice that would be great!
SVGameView:
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;
SharedPreferences mySharedPreferences;
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);
context.getSharedPreferences("myPrefsFile",Context.MODE_PRIVATE);
}
#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();
onDraw(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);
}
#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 --;
}
}
if(life == 0) {
getContext().startActivity(new Intent(getContext(), SVGameOver.class));
//Intent intent;
//intent = new Intent(getContext(), SVGameView.class);
//intent.putExtra("scoreOutput", countScore);
//Crashes Here
SharedPreferences.Editor editor = mySharedPreferences.edit();
editor.putString("cScore", String.valueOf(countScore));
}
return super.onTouchEvent(event);
}
}
SVGameOver Class:
public class SVGameOver extends Activity implements View.OnClickListener{
Button btnBack;
#Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_game);
btnBack = (Button)findViewById(R.id.btnBack);
btnBack.setOnClickListener(this);
SharedPreferences mySharedPreferences = getSharedPreferences("myPrefsFile", 0);
String theScore = mySharedPreferences.getString("cScore","");
TextView textView = (TextView)findViewById(R.id.scoreOutput);
textView.setText(theScore);
//intent = getIntent();
//String uir = intent.getStringExtra("scoreOutput");
}
#Override
public void onClick(View v) {
}
}
XML Layout:
https://gyazo.com/8e49d02c66dde7ff0e7f09a4fa9eacd2
You're missing:
mySharedPreferences = context.getSharedPreferences("myPrefsFile", Context.MODE_PRIVATE);
in your SVGameView, so mySharedPreferences always null.
You missed assigning SharedPreferencesobject to your reference mySharedPreferences in SVGameView(Context context) -
context.getSharedPreferences("myPrefsFile",Context.MODE_PRIVATE);
Change it to
mySharedPreferences = context.getSharedPreferences("myPrefsFile",Context.MODE_PRIVATE);
You are not initializing the SharedPreference object right.
Use this library, which simplifies the use of SharedPreferences and will make life simpler for you.
Android-SharedPreferences-Helper simplifies usage of the default Android SharedPreferences Class. The developer can do in a few lines
of code which otherwise would have required several. Simple to
understand as compared to the default class and easy to use.
So I'm doing an Android game for an assignment resit. This is not my first app but it is my first game. I've never been that much of an expert and quite frankly this is hard for me. I hope someone here can help.
I actually have two problems to add a score (I sincerely hope it's not too much). The thing is, I want to display it in a TextView. When the Sprite "bad" is hit, I want the score to increase by 1.
My first problem is that said TextView doesn't appear on my activity even when I'm not putting anything else than text in it. I tried to put it in LinearLayout, or change some parameters for its position, but I didn't find any other helpful thing on internet.
My second problem is for the score, which even if I can't see it, probably doesn't work. I found some helps saying to put a JPanel or JLabel but I don't think this is what I need. I tried a simple thing where I just made it an int that increases in the isHit and then displays it on the page. But it doesn't display anything, and the program took the bad habit of crashing before I can't even do anything when I have these written in the code.
I don't actually know which other page would be useful, so here is my GameView.java, if any other might be needed, please tell me.
package com.example.proprietaire.assignmentna2;
import com.example.proprietaire.assignmentna2.R;
import com.example.proprietaire.assignmentna2.Sprite;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;
public class GameView extends SurfaceView implements Runnable {
private SurfaceHolder holder;
private int x = 0, xSpeed = 1;
private Bitmap bmp;
Thread thread = null;
volatile boolean running = false;
static final long FPS = 10;
private long lastClick;
private List<Sprite> sprites = new ArrayList<Sprite>();
private List<Sprite> sprites2 = new ArrayList<Sprite>();
private List<TempSprite> temps = new ArrayList<TempSprite>();
private Bitmap bmpSmoke;
int score = 0;
public GameView(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) {
createSprites();
running = true;
thread.start();
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format,
int width, int height) {
}
});
bmpSmoke = BitmapFactory.decodeResource(getResources(), R.drawable.smoke);
}
private void createSprites() {
sprites.add(createSprite(R.drawable.bad));
sprites2.add(createSprite(R.drawable.good));
}
private Sprite createSprite(int resource) {
bmp = BitmapFactory.decodeResource(getResources(), resource);
return new Sprite(this, bmp);
}
#Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(Color.WHITE);
for (int i = temps.size() - 1; i>= 0; i--) {
temps.get(i).onDraw(canvas);
}
for (Sprite sprite : sprites) {
sprite.onDraw(canvas);
}
for (Sprite sprite : sprites2) {
sprite.onDraw(canvas);
}
}
#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()) {
onDraw(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) {
}
}
}
#Override
public boolean onTouchEvent(MotionEvent event) {
if (System.currentTimeMillis() - lastClick> 300) {
lastClick = System.currentTimeMillis();
float x = event.getX();
float y = event.getY();
synchronized (getHolder()) {
for (int i = sprites.size() - 1; i >= 0; i--) {
Sprite sprite = sprites.get(i);
if (sprite.isHit(event.getX(), event.getY())) {
sprites.remove(sprite);
temps.add(new TempSprite(temps, this, x, y, bmpSmoke));
sprites.add(createSprite(R.drawable.bad));
sprites.add(createSprite(R.drawable.bad));
sprites2.add(createSprite(R.drawable.good));
score++;
break;
}
//TextView textView = (TextView)findViewById(R.id.textView);
//String s = "" + score;
//textView.setText((new Integer(score)).toString(Integer.parseInt(s)));
}
}
}
return true;
}
}
Here is GameActivity.java
package com.example.proprietaire.assignmentna2;
import android.app.Activity;
import android.os.Bundle;
public class GameActivity extends Activity {
GameView GV;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
GV = new GameView(this);
setContentView(GV);
}
}
The xml for the game is a simple TextView without any special thing added.
Thank you in advance.
I just checked some of my old code when I was making my own game, I made a dummy .xml file with nothing but a RelativeLayout in it. So your `GameActivity would look something like this:
private TextView tv;
private GameView gv;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.dummyXml);
initializeGameView();
}
From this point on, you can play around with your GameView and your TextView. I will give an example of the start:
private void initializeGameView(){
RelativeLayout rl = (RelativeLayout) findViewById(R.id.rl_dummy_xml);
LinearLayout ll = new LinearLayout(this);
//Layout stuff
ll.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
ll.setOrientation(LinearLayout.VERTICAL);
ll.setGravity(Gravity.CENTER_HORIZONTAL);
//add more stuff, play around with whatever you want to do here
gv = new GameView();
ll.addView(gv);
tv = new TextView(this);
tv.setText(gv.getScore());
ll.addView(tv);
//Finally, don't forget to add the linear layout to the relative layout
rl.addView(ll);
}
Add a getScore() method to your GameView class or alternatively in a more elegant location :P
If you still have questions (after you've tried and tried and tried), feel free to ask ahead! =)
EDIT:
Updated code example with init of the TextView tv. If you want to update the textview you added at any point, add a method toughly similar to this one:
public void updateTextViewScore(){
tv.setText(gv.getScore());
}
I made the GameView and TextView variables global, therefore you can access them in any method you wish.
You probably want to call this method (thus update the textview) inside the game loop e.g. whenever the score changes.
Good luck! Hope this clears it up a bit.
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!
For the past few days I've been playing around with sensors and canvas.
So far I've managed to control the location of a bitmap based on device's angles.
The way app works is it gets orientation data, and depending on how much the device is tilted, it moves the bitmap left or right on the screen.
I do most of my testing on my Samsung Galaxy S II GT-i9100 running android 4.2.2 (AOKP), and the app works pretty much flawlessly, apart from the app crashing when resuming it (I think I know what's causing that).
The problem I'm having is as follows:
When I try running the same code on a Sony Xperia Z (running android 4.1.2, stock from Sony) the whole app becomes choppy (the bitmap barely moves), and I think it's because the sensor data retrieval is choppy/slow. Same happens on my friend's Sony Xperia S.
I gave the app to my other friend who has a Nexus 4, he says he has no such problems.
GameView
public class GameView extends SurfaceView {
private Bitmap bmp;
private SurfaceHolder holder;
private GameLoopThread gameLoopThread;
private int x = 0;
private int xMultiplier = 0;
private Paint textPaint;
//lowPass
private float smoothVal = 0; //Main variable, check algorithm
private int smoothing = 5; //How strong the smoothing is, larger the value, more time is needed before the value reaches actual sensor value
//Sensors
private SensorManager sensorManager;
private SensorEventListener sensorEventListener;
//Rotation matrices for converting coordinate systems
private float[] rotationMatrixR = new float[9];
private float[] rotationMatrixI = new float[9];
//Arrays storing data for gravity and geomagnetic data needed to get device's angles
private float[] gravity = new float[3];
private float[] geomagnetic = new float[3];
//Array holding angles
private float[] angles = new float[3];
public GameView(Context context) {
super(context);
gameLoopThread = new GameLoopThread(this);
holder = getHolder();
textPaint = new Paint();
textPaint.setColor(Color.WHITE);
textPaint.setTextSize(20);
sensorManager = (SensorManager)getContext().getSystemService(Context.SENSOR_SERVICE);
sensorEventListener = new SensorEventListener() {
#Override
public void onSensorChanged(SensorEvent sensorEvent) {
Sensor sensor = sensorEvent.sensor;
if (sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
gravity = sensorEvent.values;
}
else if (sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) {
geomagnetic = sensorEvent.values;
}
SensorManager.getRotationMatrix(rotationMatrixR, rotationMatrixI, gravity, geomagnetic);
SensorManager.getOrientation(rotationMatrixR, angles);
}
#Override
public void onAccuracyChanged(Sensor sensor, int i) {
}
};
sensorManager.registerListener(sensorEventListener, sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER), SensorManager.SENSOR_DELAY_FASTEST);
sensorManager.registerListener(sensorEventListener, sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD), SensorManager.SENSOR_DELAY_FASTEST);
holder.addCallback(new SurfaceHolder.Callback() {
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
boolean retry = true;
gameLoopThread.setRunning(false);
while (retry) {
try {
gameLoopThread.join();
retry = false;
}
catch (InterruptedException e) {
//Shit hit the fan
Log.e("GameLoopThread", e.toString());
}
}
}
#Override
public void surfaceCreated(SurfaceHolder holder){
gameLoopThread.setRunning(true);
gameLoopThread.start();
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
});
bmp = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher);
}
#Override
protected void onDraw(Canvas canvas)
{
x = (int) ((canvas.getWidth() / 100) * ((lowPass(angles[2]) * 100) + 50));
canvas.drawColor(Color.DKGRAY); //This also clears the screen
canvas.drawBitmap(bmp, x, canvas.getHeight() - bmp.getHeight() - 20, null);
canvas.drawText("Azimuth (Z): " + Float.toString(angles[0]),25,25, textPaint);
canvas.drawText("Pitch (X): " + Float.toString(angles[1]),25,45, textPaint);
canvas.drawText("Roll (Y): " + Float.toString(angles[2]),25,65, textPaint);
canvas.drawText("X: " + Integer.toString(x),25,85,textPaint);
}
public static BigDecimal roundFloat(float d, int decimalPlace) {
BigDecimal bd = new BigDecimal(Float.toString(d));
bd = bd.setScale(decimalPlace, BigDecimal.ROUND_HALF_UP);
return bd;
}
private float lowPass(float curValue) {
smoothVal += (curValue - smoothVal) / smoothing;
return smoothVal;
}
}
GameLoopThread
public class GameLoopThread extends Thread {
static final long FPS = 25;
private GameView view;
private Boolean running = false;
public GameLoopThread(GameView view){
this.view = view;
}
public void setRunning(boolean run){
running = run;
}
#Override
public void run(){
long tickPS = 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);
}
}
catch (NullPointerException e) {
Log.e("GameLoopThread", e.toString());
}
finally {
if (c != null) {
view.getHolder().unlockCanvasAndPost(c);
}
}
sleepTime = tickPS - (System.currentTimeMillis() - startTime);
try {
if (sleepTime > 0) {
sleep(sleepTime);
}
else {
sleep(10);
}
}
catch (Exception e) {
Log.e("GameLoopThread", e.toString());
}
}
}
}