如何从SurfaceView正确更新Android UI

时间:2012-10-07 23:41:41

标签: java android multithreading

所以我有一个带TextView的活动来保持得分如下:

public class PlayGameActivity extends Activity {
    private GameThread mGameThread;
    private GameView mGameView;

    private Handler mHandler = new Handler();

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_play_game);

        final TextView scoreView = (TextView) findViewById(R.id.textView2);

        mGameView = (GameView) findViewById(R.id.gameView1);
        mGameThread = mGameView.getThread();

        mHandler.post(new Runnable() {
            @Override
            public void run() {
                if (mGameView != null) {
                    scoreView.setText(mGameThread.getScore());
                }
            }
        });
    }
}

然后在我的GameView中我有这样的函数声明:

class GameView extends SurfaceView implements SurfaceHolder.Callback {
    class GameThread extends Thread {
        int score = 0;

        public String getScore(){
            return Integer.toString(score);
        }

        @Override
        public void run() {
            synchronized (mSurfaceHolder) {
                if (something){
                    score++
                }
            }
        }
    }
}

问题是mGameThread.getScore()总是返回0,好像得分当然没有更新。

如果有人有任何煽动,我们非常感谢。我也从上面的片段中挑出了许多不必要的代码。如果你想看到全文,你可以在这里看到:

3 个答案:

答案 0 :(得分:2)

您的处理程序仅运行一次getScore(),因此您始终只能获得初始值。您应该尝试使用postDelayed()

mHandler.postDelayed(new Runnable() {
    @Override
    public void run() {
        if (mGameView != null) {
            scoreView.setText(mGameThread.getScore());
        }
        mHandler.postDelayed(this, 1000); // run again in 1sec
    }
}, 1000);

正如托马斯提到Handler不应该以这种方式使用这里引用doc

  

Handler有两个主要用途:(1)安排消息和runnables作为将来的某个点执行; (2)将要在不同于自己的线程上执行的动作排入队列。

完全符合(1)!

答案 1 :(得分:2)

有更好的方法来更新UI数据,使用事件/侦听器机制。

坐在循环线程中不是OOP风格的编程。如果您的应用中的分数发生变化,那么您肯定会有一个检测变化的句柄。检测到后,通过对textview的一个简单的setText调用来更新UI。

不要过度拥挤你的UI事件队列堆栈(因为你可能在不知不觉中)通过调用post(....),这会使你的UI对实际用户交互的响应性降低,特别是如果你在定时循环中这样做。请记住,UI事件队列堆栈对于整个系统来说是一个,它用于处理用户与设备的交互。

如果您需要从非UI线程使用中更改UI组件

     activity.runOnUiThread(Runnable); 

方法而不是post(Runnable)。这样,您可能会在Runnable中定义更长的执行代码(尽管不推荐)。否则,请确保post(Runnable)中的代码执行和完成非常快。

答案 2 :(得分:1)

您从哪里拨打mHandler.post(Runnable)?您发布的代码会将一条消息(来自您的onCreate)添加到主线程的消息队列中,这没有多大意义。处理此消息,将打印0,然后就是它。因此,如果您不定期从某个地方调用它,它将不会在初始0之后打印任何内容。

如果您也从其他地方拨打Handler.post(Runnable),请务必从正确的位置拨打电话。每次要更新UI时,都必须从GameThread调用它。因此,您需要在GameThread中引用mHandler。例如。 GameThread的构造函数是一个很好的地方(我不会将GameThread实现为内部类,而是实现自己的内部类)。在GameThread引用mHandler后,您可以使用mHandler.post(Runnable)更新您的用户界面。例如:

    @Override
    public void run() {
        synchronized (mSurfaceHolder) {
            if (something){
                // IMPORTANT: see "important note" at the end of this Answer                          

                mHandler.post(mRunnable); // mRunnable is preinstantiated somewhere
                score++
            }
        }
    }

重要提示:您的GameThread可能会使用一种方法来衡量实时(即,如果没有任何Thread.sleep(...),它就不会在无限循环中调用所有内容)。因此,Handler的“更新分数”消息不会被过度发送。如果您的游戏基于回合(而非实时),则此问题甚至根本不会出现。

注意2::我检查了您的完整代码,似乎您的游戏代码没有任何FPS规定,即您不使用例如Thread.sleep(33)或类似的东西。因此,如果您的盗版发生冲突,我上面写的解决方案会向您的UI发送很多消息(并且您的分数会在很短的时间内达到很高的价值 - 我怀疑这是您想要的 )。实际上,除非碰撞非常快,否则您的score值可能会很快溢出。因此,我建议您像所有游戏一样在代码中添加FPS平衡。