[[["容易理解","easyToUnderstand","thumb-up"],["確實解決了我的問題","solvedMyProblem","thumb-up"],["其他","otherUp","thumb-up"]],[["缺少我需要的資訊","missingTheInformationINeed","thumb-down"],["過於複雜/步驟過多","tooComplicatedTooManySteps","thumb-down"],["過時","outOfDate","thumb-down"],["翻譯問題","translationIssue","thumb-down"],["示例/程式碼問題","samplesCodeIssue","thumb-down"],["其他","otherDown","thumb-down"]],["上次更新時間:2025-07-27 (世界標準時間)。"],[],[],null,["# Learn about rendering in game loops\n\nA very popular way to implement a game loop looks like this: \n\n while (playing) {\n advance state by one frame\n render the new frame\n sleep until it's time to do the next frame\n }\n\nThere are a few problems with this, the most fundamental being the idea that the\ngame can define what a \"frame\" is. Different displays will refresh at different\nrates, and that rate may vary over time. If you generate frames faster than the\ndisplay can show them, you will have to drop one occasionally. If you generate\nthem too slowly, SurfaceFlinger will periodically fail to find a new buffer to\nacquire and will re-show the previous frame. Both of these situations can\ncause visible glitches.\n\nWhat you need to do is match the display's frame rate, and advance game state\naccording to how much time has elapsed since the previous frame. There are several\nways to go about this:\n\n- Use the Android Frame Pacing library (recommended)\n- Stuff the BufferQueue full and rely on the \"swap buffers\" back-pressure\n- Use Choreographer (API 16+)\n\nAndroid Frame Pacing library\n----------------------------\n\nSee [Achieve proper frame pacing](/games/optimize/frame-pacing) for information\non using this library.\n\nQueue stuffing\n--------------\n\nThis is very easy to implement: just swap buffers as fast as you can. In early\nversions of Android this could actually result in a penalty where\n`SurfaceView#lockCanvas()` would put you to sleep for 100ms. Now\nit's paced by the BufferQueue, and the BufferQueue is emptied as quickly as\nSurfaceFlinger is able.\n\nOne example of this approach can be seen in [Android Breakout](https://code.google.com/p/android-breakout/). It\nuses GLSurfaceView, which runs in a loop that calls the application's\nonDrawFrame() callback and then swaps the buffer. If the BufferQueue is full,\nthe `eglSwapBuffers()` call will wait until a buffer is available.\nBuffers become available when SurfaceFlinger releases them, which it does after\nacquiring a new one for display. Because this happens on VSYNC, your draw loop\ntiming will match the refresh rate. Mostly.\n\nThere are a couple of problems with this approach. First, the app is tied to\nSurfaceFlinger activity, which is going to take different amounts of time\ndepending on how much work there is to do and whether it's fighting for CPU time\nwith other processes. Since your game state advances according to the time\nbetween buffer swaps, your animation won't update at a consistent rate. When\nrunning at 60fps with the inconsistencies averaged out over time, though, you\nprobably won't notice the bumps.\n\nSecond, the first couple of buffer swaps are going to happen very quickly\nbecause the BufferQueue isn't full yet. The computed time between frames will\nbe near zero, so the game will generate a few frames in which nothing happens.\nIn a game like Breakout, which updates the screen on every refresh, the queue is\nalways full except when a game is first starting (or un-paused), so the effect\nisn't noticeable. A game that pauses animation occasionally and then returns to\nas-fast-as-possible mode might see odd hiccups.\n\nChoreographer\n-------------\n\nChoreographer allows you to set a callback that fires on the next VSYNC. The\nactual VSYNC time is passed in as an argument. So even if your app doesn't wake\nup right away, you still have an accurate picture of when the display refresh\nperiod began. Using this value, rather than the current time, yields a\nconsistent time source for your game state update logic.\n\nUnfortunately, the fact that you get a callback after every VSYNC does not\nguarantee that your callback will be executed in a timely fashion or that you\nwill be able to act upon it sufficiently swiftly. Your app will need to detect\nsituations where it's falling behind and drop frames manually.\n\nThe \"Record GL app\" activity in Grafika provides an example of this. On some\ndevices (e.g. Nexus 4 and Nexus 5), the activity will start dropping frames if\nyou just sit and watch. The GL rendering is trivial, but occasionally the View\nelements get redrawn, and the measure/layout pass can take a very long time if\nthe device has dropped into a reduced-power mode. (According to systrace, it\ntakes 28ms instead of 6ms after the clocks slow on Android 4.4. If you drag\nyour finger around the screen, it thinks you're interacting with the activity,\nso the clock speeds stay high and you'll never drop a frame.)\n\nThe simple fix was to drop a frame in the Choreographer callback if the current\ntime is more than N milliseconds after the VSYNC time. Ideally the value of N\nis determined based on previously observed VSYNC intervals. For example, if the\nrefresh period is 16.7ms (60fps), you might drop a frame if you're running more\nthan 15ms late.\n\nIf you watch \"Record GL app\" run, you will see the dropped-frame counter\nincrease, and even see a flash of red in the border when frames drop. Unless\nyour eyes are very good, though, you won't see the animation stutter. At 60fps,\nthe app can drop the occasional frame without anyone noticing so long as the\nanimation continues to advance at a constant rate. How much you can get away\nwith depends to some extent on what you're drawing, the characteristics of the\ndisplay, and how good the person using the app is at detecting jank.\n\nThread management\n-----------------\n\nGenerally speaking, if you're rendering onto a SurfaceView, GLSurfaceView, or\nTextureView, you want to do that rendering in a dedicated thread. Never do any\n\"heavy lifting\" or anything that takes an indeterminate amount of time on the\nUI thread. Instead, create two threads for the game: a game thread\nand a render thread. See [Improve your game's performance](/games/optimize#framerate-consistency)\nfor more information.\n\nBreakout and \"Record GL app\" use dedicated renderer threads, and they also\nupdate animation state on that thread. This is a reasonable approach so long as\ngame state can be updated quickly.\n\nOther games separate the game logic and rendering completely. If you had a\nsimple game that did nothing but move a block every 100ms, you could have a\ndedicated thread that just did this: \n\n run() {\n Thread.sleep(100);\n synchronized (mLock) {\n moveBlock();\n }\n }\n\n(You may want to base the sleep time off of a fixed clock to prevent drift --\nsleep() isn't perfectly consistent, and moveBlock() takes a nonzero amount of\ntime -- but you get the idea.)\n\nWhen the draw code wakes up, it just grabs the lock, gets the current position\nof the block, releases the lock, and draws. Instead of doing fractional\nmovement based on inter-frame delta times, you just have one thread that moves\nthings along and another thread that draws things wherever they happen to be\nwhen the drawing starts.\n\nFor a scene with any complexity you'd want to create a list of upcoming events\nsorted by wake time, and sleep until the next event is due, but it's the same\nidea."]]