有没有办法将线程安全地作为参数传递?
我有一个Android游戏的开始。它有一个GameLoop
(线程)类,它跟踪一个fps值和一个GameView
(surfaceview),它将一个对象设置为一个对象并将其设置为等等。 现在我有一个单独的FpsText
对象,它知道如何将自己绘制到屏幕上,但更新,我需要做的是这样fpsText.setText(gameloop.getFps());
有没有办法将线程安全地作为参数传递?
,如果我在GameView
类做这个心不是一个问题,但我想尝试,并有一个FpsText
对象,可以照顾自己更新。我没有任何其他线程经验,我认为这可能适得其反,但我没有尝试通过fpsText
gameloop
参考作为参数。不出所料,我每次运行应用程序都会得到不同的不需要的结果,所以我想知道您认为处理这种情况的最佳方法是什么?
作为GameView
中更新fpsText
的替代方法,我总是可以在gameloop类中公开fps值,但我知道这是不好的做法!既不是好的解决方案,也许有更好的方法?
仅供参考,这里是我gameloop,由RedOrav建议的第二个变化来实现:
package biz.hireholly.engine;
import android.graphics.Canvas;
import android.util.Log;
import android.view.SurfaceHolder;
import biz.hireholly.gameplay.FpsText;
import java.util.ArrayList;
/**
* The GameLoop is a thread that will ensure updating and drawing is done at set intervals.
* The thread will sleep when it has updated/rendered quicker than needed to reach the desired fps.
* The loop is designed to skip drawing if the update/draw cycle is taking to long, up to a MAX_FRAME_SKIPS.
* The Canvas object is created and managed to some extent in the game loop,
* this is so that we can prevent multiple objects trying to draw to it simultaneously.
* Note that the gameloop has a reference to the gameview and vice versa.
*/
public class GameLoop extends Thread {
private static final String TAG = GameLoop.class.getSimpleName();
//desired frames per second
private final static int MAX_FPS = 30;
//maximum number of drawn frames to be skipped if drawing took too long last cycle
private final static int MAX_FRAME_SKIPS = 5;
//ideal time taken to update & draw
private final static int CYCLE_PERIOD = 1000/MAX_FPS;
private SurfaceHolder surfaceHolder;
//the gameview actually handles inputs and draws to the surface
private GameView gameview;
private boolean running;
private long beginTime = 0; // time when cycle began
private long timeDifference = 0; // time it took for the cycle to execute
private int sleepTime = 0; // milliseconds to sleep (<0 if drawing behind schedule)
private int framesSkipped = 0; // number of render frames skipped
private double lastFps = 0; //The last FPS tracked, the number displayed onscreen
private ArrayList<Double> fpsStore = new ArrayList<Double>(); //For the previous fps values
private long lastTimeFpsCalculated = System.currentTimeMillis(); //used in trackFps
FpsText fpsText;
public GameLoop(SurfaceHolder surfaceHolder, GameView gameview, FpsText fpsText) {
super();
this.surfaceHolder = surfaceHolder;
this.gameview = gameview;
this.fpsText = fpsText;
}
public void setRunning(boolean running) {
this.running = running;
}
@Override
public void run(){
Canvas c;
while (running) {
c = null;
//try locking canvas, so only we can edit pixels on surface
try{
c = this.surfaceHolder.lockCanvas();
//sync so nothing else can modify while were using it
synchronized (surfaceHolder){
beginTime = System.currentTimeMillis();
framesSkipped = 0; //reset frame skips
this.gameview.update();
this.gameview.draw(c);
//calculate how long cycle took
timeDifference = System.currentTimeMillis() - beginTime;
//good time to trackFps?
trackFps();
//calculate potential sleep time
sleepTime = (int)(CYCLE_PERIOD - timeDifference);
//sleep for remaining cycle
if (sleepTime >0){
try{
Thread.sleep(sleepTime); //saves battery! :)
} catch (InterruptedException e){}
}
//if sleepTime negative then we're running behind
while (sleepTime < 0 && framesSkipped < MAX_FRAME_SKIPS){
//update without rendering to catch up
this.gameview.update();
//skip as many frame renders as needed to get back into
//positive sleepTime and continue as normal
sleepTime += CYCLE_PERIOD;
framesSkipped++;
}
}
} finally{
//finally executes regardless of exception,
//so surface is not left in an inconsistent state
if (c != null){
surfaceHolder.unlockCanvasAndPost(c);
}
}
}
}
/* Calculates the average fps every second */
private void trackFps(){
synchronized(fpsStore){
long currentTime = System.currentTimeMillis();
if(timeDifference != 0){
fpsStore.add((double)(1000/timeDifference));
}
//If a second has past since last time average was calculated,
// it's time to calculate a new average fps to display
if ((currentTime - 1000) > lastTimeFpsCalculated){
for (Double fps : fpsStore){
lastFps += fps;
}
lastFps /= fpsStore.size();
synchronized(fpsText){ //update the Drawable FpsText object
fpsText.setText(String.valueOf((int)lastFps));
}
lastTimeFpsCalculated = System.currentTimeMillis();
//Log.d("trackFPS()", " fpsStore.size() = "+fpsStore.size()+"\t"+fpsStore.toString());
fpsStore.clear();
}
}
}
}
什么是你不想要的结果?让你的fps变量公开并设置一个getfps()就获得你的fps值而言是完全一样的,修改GameView中的fps值是没有任何意义的。
我怀疑你在做什么GameLoop是修改你的fps变量之间的秒,即获得中间值,您的GameView也可能读取。我建议每秒只修改一次fps,并在时间间隔中进行计算。假设你在开始时有0 fps。 GameLoop进行计算,每秒一次,你已经收集了它处理的帧数和时间(1秒)。在此之前,无论GameLoop在做什么,GameView总是读取0。在那之后,假设您完成了60 fps,fps变量将被修改,并且在下一秒之前保持不变。因此,对于第二个以下,即使GameView可以做更多的读取您的变量,它总是会得到60
// Inside GameLoop
private int fps; // Variable you're going to read
private int frameCount; // Keeps track or frames processed
private long lastUpdated;
while(running) {
if(System.currentTimeMillis() - lastUpdated > 1000) { // 1 second elapsed
fps = frameCount;
frameCount = 0;
}
frameCount++;
}
public int getFps() {
return fps;
}
做它传递给fpsText一个参考GameLoop的另一种方式,这将修改它只是在每秒过后。这样,你的渲染线程只需要担心渲染,而你的GameLoop关于每秒帧数。但请记住,您可能会遇到竞争条件(您的渲染方法中间的GameLoop中setText()的位置),您不必担心其他方式。在渲染时以及在修改它时,将fpsText对象包含在同步括号中应该为您解决问题。
让我们知道是否有帮助!
非常感谢您的回复!我意识到现在我应该包括我的源代码,因为我实际上认为我以某种方式实现了我的循环,而fps只是更新了第二个,如果你想检查它,我不完全是上面的源代码,因为我并不完全确保它的正确性是诚实的:/这个逻辑对我来说是有意义的,但是在屏幕上我得到800-1000之间的fps!所以即时担心它不应该睡觉时,应该。无论如何,我实现了你建议的第二个解决方案,并且应用程序再次正常运行,所以你回答了我的问题:)谢谢! – Holly 2012-08-04 12:27:52