I have really wired problem..I searced a lot in google and here and con't solve my problem.
I have main activity which create and start 3 threads (only "gameThread" is relevant now).
When I click on the back buttom it seems ok , but when I come back to the activity and the thread continue running , it seems it run duplicate - I see only one ContentView on the screen, but there is more one running (maybe behind).
I hope I explain myselg good..
this is the code:
package com.example.newpingpong;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import android.view.Window;
import android.widget.Toast;
/*
* The main activity class of the Ping Pong game
*/
public class MainActivity extends Activity
{
/*
* The following class variables hold references to the objects the game is composed of -
* PingPongView - main view,
* gameBall - the ball in the game,
* gamePaddle - the paddle in the game,
* gameThread - main thread updating the position of the ball,
* paddleMoverThread - another thread which is responsible for moving the paddle.
*/
private PingPongView gameView;
private Tray gamePaddle;
private GameLevel gameLevel;
private GameLevel [] levels;
//threads
private PingPongGame gameThread;
private PaddleMover paddleMoverThread;
private PresentThread giftThread;
private Messages message;
//balls container
private BallView [] ballViewArr;
private Ball [] ballArr;
//gifts container
private Gift[] giftArr;
private GiftView[] GiftViewArr;
public GameSounds gameSounds;
private int screenWidth;
private int screenHeight;
private TrayView paddleView;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState)
{
requestWindowFeature(Window.FEATURE_NO_TITLE);
super.onCreate(savedInstanceState);
Toast.makeText(this, "onCreate!", Toast.LENGTH_SHORT).show();
// Get the message from the intent
Intent intent = getIntent();
String levelChosed = intent.getStringExtra(ImageAdapter.EXTRA_MESSAGE);
int levelChosed_int = Integer.parseInt(levelChosed);
Const.currentLevel = levelChosed_int;
Const.isCompleteThisLevel = false;
GameLevelView gameLevelView;
//for sounds
if(gameSounds == null){
gameSounds = new GameSounds();
gameSounds.addSound(Const.CATCH_GIFT, R.raw.cathc_gift, getBaseContext());
gameSounds.addSound(Const.GIFT_START_FALL, R.raw.gift_start_fall, getBaseContext());
gameSounds.addSound(Const.HIT_BLOCK, R.raw.hit_block, getBaseContext());
gameSounds.addSound(Const.HIT_KIR, R.raw.hit_kir, getBaseContext());
gameSounds.addSound(Const.GAME_OVER, R.raw.game_over, getBaseContext());
gameSounds.addSound(Const.HIT_PADDLE, R.raw.hit_paddle, getBaseContext());
}
if(this.ballViewArr == null){
this.ballViewArr = new BallView[Const.MAX_BALLS];
this.ballArr = new Ball[Const.MAX_BALLS];
this.giftArr = new Gift[Const.MAX_GIFTS];
this.GiftViewArr = new GiftView[Const.MAX_GIFTS];;
// Getting the screen width & height
screenWidth = this.getWindowManager().getDefaultDisplay().getWidth();
screenHeight = this.getWindowManager().getDefaultDisplay().getHeight();
for(int i=0 ; i< Const.MAX_BALLS ; i++){
this.ballArr[i] = new Ball(screenWidth / 2, screenHeight / 2, screenWidth, screenHeight);
this.ballViewArr[i] = new BallView(this, this.ballArr[i]);
this.ballArr[i].setMode(Const.NOT_ACTIVE);
}
this.ballArr[0].setMode(Const.ACTIVE);
for(int i=0 ; i< Const.MAX_GIFTS ; i++){
this.giftArr[i] = new Gift(screenWidth / 2, screenHeight / 4,screenWidth, null, Const.NOT_ACTIVE);
this.GiftViewArr[i] = new GiftView(this, this.giftArr[i]);
}
// Creating the paddle
gamePaddle = new Tray(screenWidth / 2, (int)(screenHeight * 0.75), screenWidth);
// Creating the paddle view, and giving it the gameBall as a parameter
paddleView = new TrayView(this, gamePaddle);
levels = new GameLevel[Const.LevelsAmount];
levels[0] = new GameLevel(Const.level0, screenWidth , screenHeight, this.giftArr);
levels[1] = new GameLevel(Const.level1, screenWidth , screenHeight, this.giftArr);
levels[2] = new GameLevel(Const.level2,screenWidth , screenHeight, this.giftArr);
}
gameLevel=levels[levelChosed_int];
gameLevelView = new GameLevelView(this,gameLevel);
// Creating the game view
this.gameView = new PingPongView(this);
message = new Messages(this , screenWidth , screenHeight);
// Give the gameView our ballView & paddleView.
gameView.setViews(paddleView,gameLevelView , message ,this.ballViewArr , this.GiftViewArr);
// Setting the gameView as the main view for the PingPong activity.
setContentView(gameView);
if(gameThread == null){
//create the main thread
gameThread = new PingPongGame(this.getResources(), gamePaddle, gameView, gameLevel , message , ballArr , gameSounds);
//create the thread responsible for moving the paddle
paddleMoverThread = new PaddleMover(gamePaddle, gameView);
//create the thread responsible for present
giftThread = new PresentThread(gamePaddle , gameView , gameLevel, message , giftArr , ballArr,gameSounds );
gameThread.start();
paddleMoverThread.start();
giftThread.start();
}
}
//This method is automatically called when the user touches the screen
#Override
public boolean onTouchEvent(MotionEvent event)
{
float destination;
// Toast.makeText(this, "try!", Toast.LENGTH_SHORT).show();
//get the x coordinate of users' press
destination = event.getX();
//notify the paddle mover thread regarding the new destination
gamePaddle.setPaddleDestination(destination);
return true;
}
#Override
protected void onPause() {
Toast.makeText(this, "onPause!", Toast.LENGTH_SHORT).show();
Const.toStop = true;
while(Const.toStopMessageHasRecieved == false){
} //the thread not see the message that he shoult stop
//here the thrad recived the message and stoped.
super.onPause();
}
#Override
public void onResume()
{
Toast.makeText(this, "onResume!", Toast.LENGTH_SHORT).show();
for(int i=0 ; i< Const.MAX_BALLS ; i++){
this.ballArr[i] = new Ball(screenWidth / 2, screenHeight / 2, screenWidth, screenHeight);
this.ballViewArr[i] = new BallView(this, this.ballArr[i]);
this.ballArr[i].setMode(Const.NOT_ACTIVE);
}
this.ballArr[0].setMode(Const.ACTIVE);
synchronized (Const.mPauseLock) {
Const.toStop = false;
Const.mPauseLock.notifyAll();
}
super.onResume();
}
}
AND THE THREAD:
package com.example.newpingpong;
import android.content.res.Resources;
import android.graphics.Color;
public class PingPongGame extends Thread {
private PingPongView gameView;
private Tray gamePaddle;
private GameLevel gameLevel;
private Messages message;
private Ball [] ballArr;
private GameSounds gameSounds;
private Resources res;
//private int screenWidth;
//private int screenHight;
public PingPongGame(Resources res ,Tray theTray , PingPongView mainView , GameLevel gameLevel ,
Messages message , Ball ballArr[] ,GameSounds gameSounds)
{
this.gameView = mainView;
this.ballArr = ballArr;
this.gamePaddle = theTray;
this.gameLevel=gameLevel;
this.message = message;
this.gameSounds=gameSounds;
this.res=res;
//this.screenWidth = screenWidth;
//this.screenHight = screenHight;
//for stop and resume threads
}
//main method of the current thread
#Override
public void run()
{
try {
PingPongGame.sleep(2000);
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
this.message.setMessage(res.getString(R.string.lives) + gameLevel.getLives());
gameView.postInvalidate();
//infinitely loop, and move the balls accordingly
while ((Const.isLose == false) && (Const.isCompleteThisLevel==false))
{
for(int i=0; i<Const.MAX_BALLS ; i++){
if(ballArr[i].getMode() != Const.NOT_ACTIVE){
//move the ball one step
if(ballArr[i].move()) //hit kir
gameSounds.play(Const.HIT_KIR,false);
gameView.postInvalidate();
//check if the ball hit some block
if(gameLevel.updateIfHit(ballArr[i])){
ballArr[i].down();
gameSounds.play(Const.HIT_BLOCK,false);
}
gameView.postInvalidate();
if(gameLevel.getLives() == 0){
//gameLevel.restart();
this.message.setMessage(res.getString(R.string.game_over));
gameView.postInvalidate();
gameSounds.play(Const.GAME_OVER,false);
Const.isLose = true;
try
{
PingPongGame.sleep(2000);
}
catch (InterruptedException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
break;
}
//check if the ball has reached the paddle
if(checkHitPaddle(i,ballArr[i]) == true){ //משתמש פספס and lives --
this.message.setMessage(res.getString(R.string.lives)+"" + gameLevel.getLives());
//send a request to refresh the display
gameView.postInvalidate();
}
}
}
try
{
PingPongGame.sleep(5);
}
catch (InterruptedException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
//public static boolean toStop = false;
//public static boolean toStopMessageHasRecieved = false;
//for stop and resume
//for stop and resume
synchronized (Const.mPauseLock) {
while (Const.toStop) {
Const.toStopMessageHasRecieved = true;
try {
Const.mPauseLock.wait();
} catch (InterruptedException e) {
}
}
}
}
// Const.toStopMessageHasRecieved = true;
}
//This method is responsible for handling the situation in which the ball has reached the height of the paddle
//return true if lives--
private boolean checkHitPaddle(int i , Ball ball)
{
// Checking if the ball had hit the paddle
if (ball.getBottom() >= gamePaddle.getTop())
{
// check if the paddle is in the right place
if ((ball.getRight() > gamePaddle.getLeft()) &&
(ball.getLeft() < gamePaddle.getRight()))
{
//The ball had hit the paddle - so it should be bounced
gameSounds.play(Const.HIT_PADDLE,false);
ball.bounceUp();
}
else
{
//The ball had missed the paddle - player looses - restart the game
gameSounds.play(Const.GAME_OVER,false); //not really game over - just lose live
boolean isMoreBall = false;
for(int j=0 ; j< Const.MAX_BALLS ; j++)
if( (j != i) && (ballArr[j].getMode() == Const.ACTIVE)){
isMoreBall = true;
break;
}
if(isMoreBall){
ball.setMode(Const.NOT_ACTIVE);
//for(int k=0; k<Const.MAX_BALLS ; k++){
//ballArr[k].downSpeed(0.25);
//}
}
else{
gameLevel.setLives(gameLevel.getLives()-1);
ball.restart();
return true;
}
}
}
return false;
}
}
Everytime the Activity is started you create a new gameThread in onCreate since the Activity is a new object then and gameThread is null. But you never stop them. You should probably insert something that ends your threads in onDestroy.
At the moment both Threads modify your Const object. Which leads me to the main problem:
I think the main problem is that all your communication is handled by some 'Global Variable Collection' in the Const object which can be very confusing the bigger the game gets. For now a solution could be to let the Const object handle the Threads too, but that would possibly only make it worse on the long run.
Try using
Timer rather than Thread.
And cancel timer using timer.cancel() in onBackPressed() method.
Related
I have used the following animation class because i have multiple images to make an animation. After the 7th animation i get OutofMemoryError. androidgraphics.Bitmap.createBitmap
public class AnimationsContainer {
public int FPS = 30; // animation FPS
// single instance procedures
private static AnimationsContainer mInstance;
private AnimationsContainer() {
};
public static AnimationsContainer getInstance() {
if (mInstance == null)
mInstance = new AnimationsContainer();
return mInstance;
}
// animation progress dialog frames
private int[] mProgressAnimFrames = {};
// animation splash screen frames
/**
* #param imageView
* #return progress dialog animation
*/
public FramesSequenceAnimation createProgressDialogAnim(ImageView imageView) {
return new FramesSequenceAnimation(imageView, mProgressAnimFrames);
}
/**
* #param imageView
* #return splash screen animation
*/
public FramesSequenceAnimation createSplashAnim(ImageView imageView, int[] n) {
return new FramesSequenceAnimation(imageView, n);
}
/**
* AnimationPlayer. Plays animation frames sequence in loop
*/
public class FramesSequenceAnimation {
private int[] mFrames; // animation frames
private int mIndex; // current frame
private boolean mShouldRun; // true if the animation should continue running. Used to stop the animation
private boolean mIsRunning; // true if the animation currently running. prevents starting the animation twice
private SoftReference<ImageView> mSoftReferenceImageView; // Used to prevent holding ImageView when it should be dead.
private Handler mHandler;
private int mDelayMillis;
private OnAnimationStoppedListener mOnAnimationStoppedListener;
private Bitmap mBitmap = null;
private BitmapFactory.Options mBitmapOptions;
public FramesSequenceAnimation(ImageView imageView, int[] frames) {
mHandler = new Handler();
mFrames = frames;
mIndex = -1;
mSoftReferenceImageView = new SoftReference<ImageView>(imageView);
mShouldRun = false;
mIsRunning = false;
mDelayMillis = 50;
imageView.setImageResource(mFrames[0]);
// use in place bitmap to save GC work (when animation images are the same size & type)
if (Build.VERSION.SDK_INT >= 11) {
Bitmap bmp = ((BitmapDrawable) imageView.getDrawable()).getBitmap();
int width = bmp.getWidth();
int height = bmp.getHeight();
Bitmap.Config config = bmp.getConfig();
mBitmap = Bitmap.createBitmap(width, height, config);
mBitmapOptions = new BitmapFactory.Options();
// setup bitmap reuse options.
mBitmapOptions.inBitmap = mBitmap;
mBitmapOptions.inMutable = true;
mBitmapOptions.inSampleSize = 1;
}
}
private int getNext() {
mIndex++;
if (mIndex == mFrames.length){
mIndex = mIndex - 1;
mShouldRun = false;
}
return mFrames[mIndex];
}
/**
* Starts the animation
*/
public synchronized void start() {
mShouldRun = true;
if (mIsRunning)
return;
Runnable runnable = new Runnable() {
#Override
public void run() {
ImageView imageView = mSoftReferenceImageView.get();
if (!mShouldRun || imageView == null) {
mIsRunning = false;
if (mOnAnimationStoppedListener != null) {
mOnAnimationStoppedListener.onAnimationStopped();
}
return;
}
mIsRunning = true;
mHandler.postDelayed(this, mDelayMillis);
if (imageView.isShown()) {
int imageRes = getNext();
if (mBitmap != null) { // so Build.VERSION.SDK_INT >= 11
Bitmap bitmap = null;
try {
bitmap = BitmapFactory.decodeResource(imageView.getResources(), imageRes, mBitmapOptions);
} catch (Exception e) {
e.printStackTrace();
}
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
} else {
imageView.setImageResource(imageRes);
mBitmap.recycle();
mBitmap = null;
}
} else {
imageView.setImageResource(imageRes);
}
}
}
};
mHandler.post(runnable);
}
/**
* Stops the animation
*/
public synchronized void stop() {
mShouldRun = false;
}
}
}
Can anyone explain me why and tell me how to fix it? I already added
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
to my manifest file.
It might help to be sure that you are not keeping references to unneeded images. This is happening because you probably have the "standard" memory heap of 32MB or something similar (maybe 64MB or maybe 16MB). If you consider that most images are 5MB or more, it's not surprising you are out of memory.
You can increase the heap size using android:largeHeap="true" like this:
How to increase heap size of an android application?
please help guys,, here is my code for my simple gallery android app for external images
I made the loader is in array , and I made swiping action ,,
what I think I have to chose Wither removechild nor enter frame but I can't apply it
coz when publish, things is mixed up and swiping is not working well :(
could you please help me to fix it it's AS3 at Flash 5.5
var pictureArray:Array = new Array;
var loader1 = new Loader();
loader1.load(new URLRequest("1.jpg"));
pictureArray.push(loader1);
var loader2 = new Loader();
loader2.load(new URLRequest("2.jpg"));
pictureArray.push(loader2);
var loader3 = new Loader();
loader3.load(new URLRequest("3.jpg"));
pictureArray.push(loader3);
addChild(pictureArray[0]);
pictureArray[0].x = 0; pictureArray[0].y = 0;
var n:int = 0;
Multitouch.inputMode = MultitouchInputMode.GESTURE;
var currentGalleryItem:Number = 1;
var totalGalleryItems:Number = 3;
stage.addEventListener (TransformGestureEvent.GESTURE_SWIPE, fl_SwipeToGoToNextPreviousFrame);
function fl_SwipeToGoToNextPreviousFrame(event:TransformGestureEvent):void
{
if(event.offsetX == 1)
{
if(currentGalleryItem > 1){
currentGalleryItem--;
slideRight();
removeChild(pictureArray[n]);
n = n+1;
if (n>pictureArray.length - 1)
n=0;
addChild(pictureArray[n]);
pictureArray[n].x = 0; pictureArray[n].y = 0;
}
}
else if(event.offsetX == -1)
{
if(currentGalleryItem < totalGalleryItems){
currentGalleryItem++;
slideLeft();
removeChild(pictureArray[n]);
n = n-1;
if (n<0)
n=pictureArray.length - 1;
addChild(pictureArray[n]);
pictureArray[n].x = 0; pictureArray[n].y = 0;
}
}
}
var slideCounter:Number = 0;
function slideLeft(){
(pictureArray[n]).addEventListener("enterFrame", moveGalleryLeft);
}
function slideRight(){
(pictureArray[n]).addEventListener("enterFrame", moveGalleryRight);
}
function moveGalleryLeft(evt:Event){
(pictureArray[n]).x -= 48;
slideCounter++;
if(slideCounter == 5){
(pictureArray[n]).removeEventListener("enterFrame", moveGalleryLeft);
slideCounter = 0;
}
}
function moveGalleryRight(evt:Event){
(pictureArray[n]).x += 48;
slideCounter++;
if(slideCounter == 5){
(pictureArray[n]).removeEventListener("enterFrame", moveGalleryRight);
slideCounter = 0;
}
}
I would recommend load images on demand, for animation use some tweening engine(in my example It's TweenLite), and dispose previous frames, It's mobile device you should honour memory. Most of other comments in the code, I don't have ability to test in on the device, also It's not complete code, It's only carcass ;)
package {
import com.greensock.TweenLite;
import flash.display.Bitmap;
import flash.display.Loader;
import flash.display.Sprite;
import flash.display.StageAlign;
import flash.display.StageScaleMode;
import flash.events.Event;
import flash.events.TransformGestureEvent;
import flash.net.URLRequest;
import flash.ui.Multitouch;
import flash.ui.MultitouchInputMode;
public class StackOverflow extends Sprite {
private var _loader:Loader;
private var _images:Array;
private var _position:int;
private var _currentFrame:Sprite;
public function StackOverflow() {
addEventListener(Event.ADDED_TO_STAGE, onAdded);
}
private function setup():void {
_loader = new Loader();
_images = ["1.jpg", "2.jpg", "3.jpg"];
_currentFrame = new Sprite();
_loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onLoadImage);
stage.addEventListener(TransformGestureEvent.GESTURE_SWIPE, onSwipe);
addChild(_currentFrame);
loadImageAtPosition(_position);
}
private function disposeFrame(frame:Sprite):void {
//You can cache loaded bitmaps, or dispose them
removeChild(frame);
var bitmap:Bitmap = frame.getChildAt(0) as Bitmap;
bitmap.bitmapData.dispose();
}
private function loadImageAtPosition(index:int):void {
//By using one loader for all tasks, you will have only last requested image
_loader.load(new URLRequest(_images[index]));
}
private function offsetScreenOn(direction:Number):void {
var offset:int;
var start:int;
if (direction > 0) {
//Direction to the right side
offset = stage.stageWidth;
} else {
//Direction to the left side
offset = -stage.stageWidth;
}
//Starting position for new frame
start = -offset;
//TweenLite is great for such task
if (_currentFrame != null) {
//Remove previous frame
TweenLite.to(_currentFrame, 0.8, {x: offset, onComplete: disposeFrame, onCompleteParams: [_currentFrame]});
}
//Initialise new frame, fill them, decor them...
_currentFrame = new Sprite();
if (_currentFrame != null) {
addChild(_currentFrame);
_currentFrame.x = start;
//Animate in new frame
TweenLite.to(_currentFrame, 0.8, {x: (start + offset)});
}
}
private function onLoadImage(e:Event):void {
if (_currentFrame != null) {
var image:Bitmap = _loader.content as Bitmap;
if (image != null) {
image.smoothing = true;
_currentFrame.addChild(image);
//Resize, displace image for your needs
//Add appear animation if you want
}
}
}
//If you want, you can postpone swipe, while in state of transition
private function onSwipe(e:TransformGestureEvent):void {
//Left to Right swipe
if (e.offsetX == 1) {
if (--_position < 0) {
_position = _images.length;
}
offsetScreenOn(e.offsetX);
loadImageAtPosition(_position);
}
//Right to Left swipe
if (e.offsetX == -1) {
if (++_position >= _images.length) {
_position = 0;
}
offsetScreenOn(e.offsetX);
loadImageAtPosition(_position);
}
}
private function onAdded(e:Event):void {
removeEventListener(Event.ADDED_TO_STAGE, onAdded);
stage.align = StageAlign.TOP_LEFT;
stage.scaleMode = StageScaleMode.NO_SCALE;
Multitouch.inputMode = MultitouchInputMode.GESTURE;
setup();
}
}
}
check this out
simple android gallery
it a basic implementation of a gallery app, it makes use of the android media provider to actually get images in the device storage and provides an image slider by using a viewPager and a RecyclerView as indicator, I will outline the important parts here, you can use a RecyclerView and a Recycler Adapter to display your images
public class picture_Adapter extends RecyclerView.Adapter<PicHolder> {
private ArrayList<pictureFacer> pictureList;
private Context pictureContx;
private final itemClickListener picListerner;
/**
*
* #param pictureList ArrayList of pictureFacer objects
* #param pictureContx The Activities Context
* #param picListerner An interface for listening to clicks on the RecyclerView's items
*/
public picture_Adapter(ArrayList<pictureFacer> pictureList, Context pictureContx,itemClickListener picListerner) {
this.pictureList = pictureList;
this.pictureContx = pictureContx;
this.picListerner = picListerner;
}
#NonNull
#Override
public PicHolder onCreateViewHolder(#NonNull ViewGroup container, int position) {
LayoutInflater inflater = LayoutInflater.from(container.getContext());
View cell = inflater.inflate(R.layout.pic_holder_item, container, false);
return new PicHolder(cell);
}
#Override
public void onBindViewHolder(#NonNull final PicHolder holder, final int position) {
final pictureFacer image = pictureList.get(position);
Glide.with(pictureContx)
.load(image.getPicturePath())
.apply(new RequestOptions().centerCrop())
.into(holder.picture);
setTransitionName(holder.picture, String.valueOf(position) + "_image");
holder.picture.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
picListerner.onPicClicked(holder,position, pictureList);
}
});
}
#Override
public int getItemCount() {
return pictureList.size();
}
}
the method below gets all the device images and load the info for each image in a imageFacer object which you can use to populate the recyclerview adapter
see the full post in the link above
public ArrayList<pictureFacer> getAllImagesByFolder(String path){
ArrayList<pictureFacer> images = new ArrayList<>();
Uri allVideosuri = android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
String[] projection = { MediaStore.Images.ImageColumns.DATA ,MediaStore.Images.Media.DISPLAY_NAME,
MediaStore.Images.Media.SIZE};
Cursor cursor = ImageDisplay.this.getContentResolver().query( allVideosuri, projection, MediaStore.Images.Media.DATA + " like ? ", new String[] {"%"+path+"%"}, null);
try {
cursor.moveToFirst();
do{
pictureFacer pic = new pictureFacer();
pic.setPicturName(cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DISPLAY_NAME)));
pic.setPicturePath(cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA)));
pic.setPictureSize(cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.SIZE)));
images.add(pic);
}while(cursor.moveToNext());
cursor.close();
ArrayList<pictureFacer> reSelection = new ArrayList<>();
for(int i = images.size()-1;i > -1;i--){
reSelection.add(images.get(i));
}
images = reSelection;
} catch (Exception e) {
e.printStackTrace();
}
return images;
}
I'm creating a face recognition app which draws rectangle when it detects a face. Currently, all the computations are done on the main thread because of which it's using up a lot of CPU.
I've decided to run the computations on a different thread. Now the question is, is it possible to draw the canvas from the background thread itself? Or do I have to send the parameters to the main thread and then draw canvas from there?
Yes, do render your canvas from another thread and not from the UI thread! This is will improve and optimize performance.
Here is some code I wrote that does just this:
package com.webstorms.framework;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.view.SurfaceView;
public class RenderView extends WSObject implements Runnable {
Bitmap gameScreen;
SurfaceView surfaceView;
Thread gameloop;
boolean running;
int sleepTime;
int numberOfFramesSkipped;
int maxFrameSkips;
long beginTime;
long endTime;
long lastTime;
int differenceTime;
int framePeriod;
Canvas frameBuffer;
int frameCount;
int realFPS;
int setFPS;
/**
* This class is the game loop that will update and render the game.
*
*/
public RenderView(Game game, Bitmap gameScreen, int fps, int maxFrameSkips) {
super(game);
this.gameScreen = gameScreen;
surfaceView = new SurfaceView(game);
this.setFPS = fps;
this.framePeriod = 1000/this.setFPS;
this.maxFrameSkips = maxFrameSkips;
lastTime = System.currentTimeMillis();
beginTime = System.currentTimeMillis();
}
public SurfaceView getView() {
return this.surfaceView;
}
#Override
public void run() {
while(running) {
if(this.surfaceView.getHolder().getSurface().isValid()) {
beginTime = System.currentTimeMillis();
this.getGame().getInput().update(); // Synchronize input and call all attached listeneres
this.getGame().getCurrentScreen().update();
this.renderFrameBuffer();
// Frame Per Second Count
frameCount++;
if(lastTime + 1000 < System.currentTimeMillis()) {
WSLog.d(Game.GAME_ENGINE_TAG, this, "REAL FPS: " + frameCount);
this.realFPS = frameCount;
lastTime = System.currentTimeMillis();
frameCount = 0;
}
endTime = System.currentTimeMillis();
differenceTime = (int) (endTime - beginTime);
sleepTime = (int) (framePeriod - differenceTime);
if(sleepTime > 0) {
try {
Thread.sleep(sleepTime);
}
catch (InterruptedException exception) {
exception.printStackTrace();
}
}
else {
while(sleepTime < 0 && numberOfFramesSkipped < this.maxFrameSkips) {
WSLog.d(Game.GAME_ENGINE_TAG, this, "Game thread is only updating the update method and is not rendering anything");
this.getGame().getCurrentScreen().update();
sleepTime += framePeriod;
numberOfFramesSkipped++;
}
}
}
}
}
public int getRealFPS() {
return this.realFPS;
}
public int getSetFPS() {
return this.setFPS;
}
private void renderFrameBuffer() {
// Update the current virtual screen image
this.getGame().getCurrentScreen().render();
// Render the current virtual screen to the real phone screen
frameBuffer = this.surfaceView.getHolder().lockCanvas();
if(frameBuffer != null) { // Fix for mysterious bug ( FATAL EXCEPTION: Thread)
frameBuffer.drawBitmap(this.gameScreen, null, this.getGame().getWSScreen().getGameScreendst(), null);
this.surfaceView.getHolder().unlockCanvasAndPost(frameBuffer);
}
else {
WSLog.e(Game.GAME_ENGINE_TAG, this, "Surface has not been created or otherwise cannot be edited");
}
}
public void resume() {
this.running = true;
gameloop = new Thread(this);
gameloop.start();
}
public void pause() {
this.running = false;
running = false;
while(true) {
try {
gameloop.join();
break;
}
catch (InterruptedException e) {
// retry
}
}
}
}
When creating a renderview object, pass a reference of our activity for the first parameter.
Also do this in your activity:
this.setContentView(this.renderView.getView());
It is, no problem with that. And it may help you with optimising the rendering.
I'm using a SurfaceView and I have to update it as fast as I can. Since the onDraw method isn't called automatically I have to call explicitly. So my first attempt was on threads, but I got some lag. There is a better way to do this? If there is a better solution, can you, please, explain me? I'm new on Android's World!
What I have so far
public void run() {
while(running){
Canvas c = null;
try {
c = view.getHolder().lockCanvas();
synchronized (view.getHolder()) {
view.onDraw(c);
}
} finally {
if (c != null) {
view.getHolder().unlockCanvasAndPost(c);
}
}
}
}
Here's my code that I use as a rendering thread. The fps is updated frame dependent. I usually run my games at 30 fps.
(There's some code you won't need, but I'm sire you'll be able to find the pieces you are looking for.)
public class RenderView extends SurfaceView implements Runnable {
Game game;
Bitmap gameScreen;
Thread gameloop;
SurfaceHolder holder;
boolean running;
int sleepTime;
int numberOfFramesSkipped;
int maxFrameSkips;
long beginTime;
long endTime;
long lastTime;
int differenceTime;
int framePeriod;
Canvas frameBuffer;
int frameCount;
public RenderView(int fps, int maxFrameSkips) {
super(game);
this.gameScreen = gameScreen;
this.holder = getHolder();
this.framePeriod = 1000/fps;
this.maxFrameSkips = maxFrameSkips;
lastTime = System.currentTimeMillis();
beginTime = System.currentTimeMillis();
}
#Override
public void run() {
while(running == true) {
if(holder.getSurface().isValid()) {
beginTime = System.currentTimeMillis();
// call your update method here
// render here
frameBuffer = holder.lockCanvas();
this.onDraw();
holder.unlockCanvasAndPost(frameBuffer);
// Frame Per Second Count
frameCount++;
if(lastTime + 1000 < System.currentTimeMillis()) {
lastTime = System.currentTimeMillis();
frameCount = 0;
}
endTime = System.currentTimeMillis();;
differenceTime = (int) (endTime - beginTime);
sleepTime = (int) (framePeriod - differenceTime);
if(sleepTime > 0) {
try {
Thread.sleep(sleepTime);
} catch (InterruptedException exception) {
exception.printStackTrace();
}
}
else {
while(sleepTime < 0 && numberOfFramesSkipped < this.maxFrameSkips) {
// Call your update method here
sleepTime += framePeriod;
numberOfFramesSkipped++;
}
}
}
}
}
private void renderFrameBuffer() {
// Update the current virtual screen image
game.getCurrentScreen().render();
// Render the current virtual screen to the real phone screen
frameBuffer = holder.lockCanvas();
if(frameBuffer != null) { // Fix for mysterious bug ( FATAL EXCEPTION: Thread)
// The viewing area of our virtual screen on our real screen
// Composition
// First layer (bottom)
frameBuffer.drawBitmap(this.gameScreen, null, game.getWSScreen().getGameScreendst(), null);
// Second layer (top)
frameBuffer.drawBitmap(this.gameScreenExtended, null, game.getWSScreen().getGameScreenextendeddst(), null);
holder.unlockCanvasAndPost(frameBuffer);
}
else {
gameEngineLog.e(classTAG, "Surface has not been created or otherwise cannot be edited");
}
}
public void resume() {
this.running = true;
gameloop = new Thread(this);
gameloop.start();
}
public void pause() {
this.running = false;
running = false;
while(true) {
try {
gameloop.join();
break;
} catch (InterruptedException e) {
// retry
}
}
}
}
public void onDraw() {
}
I suggest you take a look at this link or have a look at
http://code.google.com/p/beginning-android-games/
or http://www.edu4java.com/en/androidgame/androidgame3.html
I've been trying to figure this out for a while now... I need to place marks over top of a seekBar to show the user places that they bookmarked in the past. The data is stored in xml. The problem is making the little ovals appear over the seekBar... It just doesn't work...
Here's my code:
public class seekMark extends View {
private int seekLength; // in pixels
private int seekLeftPad; // in pixels
private int seekBottomPad; // in pixels
private int trackLength; // in ms
private float pxOverMs; // in px/ms
ShapeDrawable lmark;
private seekMark instance;
public seekMark(Context context){
super(context);
instance = this;
seekLength = progressBar.getWidth();
seekLeftPad = progressBar.getPaddingLeft();
seekBottomPad = progressBar.getBottom();
trackLength = player.getDuration();
pxOverMs = pxPerMs();
lmark = new ShapeDrawable(new OvalShape());
}
private float pxPerMs(){
return ((float) seekLength)/((float) trackLength);
}
private int[] markPxList() throws XmlPullParserException, IOException {
int bmStartTime = 0;
String bmNames[] = bmNameList(xmlPath);
int[] bmPos = new int[bmNames.length];
for(int i=0; i < bmNames.length; i++){
bmStartTime = getBookmark(xmlPath, bmNames[i]);
bmPos[i] = (int) (bmStartTime * pxOverMs);
}
return (bmPos);
}
public void markPlace() throws XmlPullParserException, IOException {
int y = seekBottomPad;
int x = 0;
int bmPos[] = markPxList();
for(int i = 0; i < bmPos.length; i++){
x = bmPos[i] + seekLeftPad;
lmark = new ShapeDrawable();
lmark.getPaint().setColor(0xff74AC23);
lmark.setBounds(x, y, x + 1, y + 1);
instance.invalidate();
}
}
protected void onDraw(Canvas canvas) {
lmark.draw(canvas);
}
}
It's called from onCreate using this code. I call it using in another thread to avoid the problem where the dimensions of progressBar aren't yet set in onCreate.
Display display = ((WindowManager) getSystemService(WINDOW_SERVICE)).getDefaultDisplay();
if (display.getRotation() == 1){ // if landscape
final Runnable runner = new Runnable() {
public void run() {
seekMark seekMarks = new seekMark(context);
try {
seekMarks.markPlace();
} catch (XmlPullParserException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
// runs in another thread to avoid the problem with calling
// seekMark directly from onCreate
}
};
handler.postDelayed(runner, 1000);
}
The program crashes whenever I try to call seekMark.markPlace()... I'm trying to draw this over top of my layout main.xml.
im not sure if this is what you are trying to do.
Customize Seekbar
this seems to be similar while the approach is different.