Первое: (чтобы ответить на ваш вопрос) Используйте «время часов» для регулировки частоты кадров, а не для подсчета количества кадров.
Подсчет кадров всегда будет зависеть от мощности процессора - чем быстрее аппаратное обеспечение, тем больше раз оно может вызвать ваш цикл обновления.
Таким образом, вы хотите отрегулировать на основе фактического времени, которое требуется для выполнения одного прохода в цикле, а затем отрегулировать режим ожидания на основе результата.
Самый простой способ - отследить, сколько времени потребуется вашему коду обновления для выполнения (скажем, 10 миллисекунд).
private static final long ourTarget_millisPerFrame = 33; // ~30 FPS
public void MyUpdate() {
long startTime = SystemClock.uptimeMillis();
// ... Do your stuff here ...
long stopTime = SystemClock.uptimeMillis();
// How much time do *we* require to do what we do (including drawing to surface, etc)?
long howLongItTakesForUsToDoOurWork = stopTime - startTime;
// So, say we took 10 millis
// ... that leaves (33 - 10 =) 23 millis of "time to spare"
// ... so that's how long we'll sleep
long timeToWait = ourTarget_millisPerFrame - howLongItTakesForUsToDoOurWork;
// But apply a minimum wait time so we don't starve the rest of the system
if ( timeToWait < 2 )
timeToWait = 2;
// Lullaby time
sleep(timeToWait);
}
Часть II:
Также ... Возможно, рассмотрите возможность использования подхода Looper / Handler и публикации отложенных "Runnable" сообщений для выполнения вашего цикла, а не для спящего потока.
Это немного более гибко ... вам вообще не нужно иметь дело со сном потока ... и вы, вероятно, захотите публиковать другие сообщения
в любом случае (например, пауза / возобновление игры).
См. Документацию Android для примера фрагмента кода:
Looper прост - он просто зацикливается на ожидании сообщений для массива, пока вы не вызовете myLooper.quit ().
Чтобы обработать ваш цикл обновления временного шага, вызовите postDelayed (myRunnable, someDelay), где вы создадите класс, реализующий runnable.
Или просто поместите метод MyUpdate в класс и вызовите его, когда получите идентификатор сообщения. (Runnable версия более эффективна)
class MyUpdateThread extends Thread {
public static final int MSGID_RUN_UPDATE = 1;
public static final int MSGID_QUIT_RUNNING = 2;
public MyMessageHandler mHandler;
private MyUpdateRunnable myRunnable = new MyUpdateRunnable();
private boolean mKeepRunning;
// The *thread's* run method
public run() {
// Setup the Looper
Looper.prepare();
// Create the message handler
// .. handler is auto-magically attached to this thread
mHandler = new MyMessageHandler();
// Prime the message queue with the first RUN_UPDATE message
this.mKeepRunning = true;
mHandler.post(myRunnable);
// Start the Looper
Looper.loop();
} // end run fun (of MyThread class)
class MyMessageHandler extends Handler {
@Override
public void handleMessage(final Message msg) {
try {
// process incoming messages here
switch (msg.what) {
case MSGID_RUN_UPDATE:
// (optional)
// ... could call MyUpdate() from here instead of making it a runnable (do either/or but not both)
break;
case MSGID_QUIT_RUNNING:
mKeepRunning = false; // flag so MyRunnable doesn't repost
Looper.myLooper().quit();
break;
}
} catch ( Exception ex ) {}
}
class MyUpdateRunnable implements Runnable {
public void run() {
try {
// ---- Your Update Processing ----
// -- (same as above ... without the sleep() call) --
// Instead of sleep() ... we post a message to "ourself" to run again in the future
mHandler.postDelayed ( this, timeToWait );
} catch ( Exception ex ) {}
} // end run fun (my runnable)
} // end class (my runnable)
}
Следующая статья является более сложным решением с попытками компенсировать случайное «этот один шаг длился долго». Это для Flash ActionScript, но легко применяется к вашему Android-приложению. (Это немного сложнее, но помогает сгладить частоту кадров)