Android用户界面线程和消息处理程序混淆

3
以下代码来自于《Android Developer's Cookbook》第58-61页。本书将该代码放在了消息传递的背景下介绍,它描述道:“计时器在后台线程中运行,因此不会阻塞UI线程,但需要在时间更改时更新UI。”我有些困惑,因为我没有看到两个线程。在我看来,主UI线程将一个可运行消息发布到其自己的消息队列中(然后该消息再以延迟时间重新发布自己)。我是否漏掉了什么?
package com.cookbook.background_timer;

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.SystemClock;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class BackgroundTimer extends Activity {
    //keep track of button presses, a main thread task
    private int buttonPress=0;
    TextView mButtonLabel;

    //counter of time since app started, a background task
    private long mStartTime = 0L;
    private TextView mTimeLabel;

    //Handler to handle the message to the timer task
    private Handler mHandler = new Handler();

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

        if (mStartTime == 0L) {
            mStartTime = SystemClock.uptimeMillis(); 
            mHandler.removeCallbacks(mUpdateTimeTask);
            mHandler.postDelayed(mUpdateTimeTask, 100);
        }

        mTimeLabel = (TextView) findViewById(R.id.text);
        mButtonLabel = (TextView) findViewById(R.id.trigger);

        Button startButton = (Button) findViewById(R.id.trigger);
        startButton.setOnClickListener(new View.OnClickListener() {
            public void onClick(View view){
                mButtonLabel.setText("Pressed " + ++buttonPress + " times");
            }
        });        
    }

    private Runnable mUpdateTimeTask = new Runnable() {
        public void run() {
            final long start = mStartTime;
            long millis = SystemClock.uptimeMillis() - start;
            int seconds = (int) (millis / 1000);
            int minutes = seconds / 60;
            seconds     = seconds % 60;

            mTimeLabel.setText("" + minutes + ":" + String.format("%02d",seconds));
            mHandler.postDelayed(this, 200);
        }
    };

    @Override
    protected void onPause() {
        mHandler.removeCallbacks(mUpdateTimeTask);
        super.onPause();
    }

    @Override
    protected void onResume() {
        super.onResume();
        mHandler.postDelayed(mUpdateTimeTask, 100);
    }
}
4个回答

2
第二个线程有点隐蔽。在onCreate()中调用postDelayed(mUpdateTImeTask,100)时,就启动了这个线程。Handler类在其内部维护了一个线程,它倒计时延迟时间(这里是100毫秒),然后运行mUpdateTimeTask。需要注意的是,在mUpdateTimeTask的run()方法末尾,通过再次调用postDelayed()将其放回到handler的定时器线程中。
Android API中有许多类,如Handler和AsyncTask,使得多线程更加容易。这些类隐藏了很多线程的细节(这就是使用它们方便的原因)。不幸的是,这也使得学习过程中很难掌握其中的运作机制,你需要先了解其背后的原理才能够学会使用。 :)

那么你的意思是Handler有一个不可见的线程(与Handler传递消息的线程不同),而不可见线程的工作是在延迟时间到期时向处理程序队列发布延迟消息。主/非不可见线程仍然执行实际消息。正确吗? - user592503
1
我认为这是正确的。实际上,处理程序将工作交给了您应用的Looper。这是运行整个事件循环的对象;它还设置用于定时向您的代码传递消息的线程。 - Ted Hopp

0

Runnable类本质上是一个用于线程的类。当调用它的接口(Handler)时,将会调用run()方法,并在这个实现中,应用程序设置Handler在执行该行代码后100毫秒运行mUpdateTimeTask,然后运行Runnablerun()方法中的所有内容。

当调用onCreate()时,您的应用程序从视图中获取mTimeLabel对象,并使用Runnable中的setText()方法进行更新。这将在UI线程上更新时间,然后在另外200毫秒后再次注册自己。


据我所知... Android的主UI线程实例化了BackgroundTimer活动,其中包括一个Handler。Handler将消息传递给创建它的线程,在这种情况下是主UI线程。在执行BackgroundTimer的onCreate方法时,主UI线程向自己的处理程序发布一条消息。在执行onCreate之后,主UI线程处理事件/消息,例如它先前发布的可运行消息。因此,主UI线程最终执行可运行代码,而不是其他线程。 - user592503
啊,是的。我应该多读一些API文档。我认为当他们说“两个线程”时,他们基本上是在说在Runnable中运行的代码不会阻塞主UI线程,因此您仍然可以与UI交互,并且在Handler等待再次执行Runnable时不会收到ANR消息。 - SpencerElliott
我稍微修改了我的答案,我最初是在考虑AsyncTask类与Runnable接口的比较。对此我感到抱歉。 - SpencerElliott

0

0

这里没有第二个线程!您可以通过在可运行的代码中放置一些昂贵的代码来轻松测试,这将阻塞UI线程。您必须创建一个new Thread(Runnable)并从那里开始。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接