public class MainActivity extends Activity implements View.OnClickListener {
private TextView stateText;
private Button btn;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
stateText = (TextView) findViewById(R.id.tv);
btn = (Button) findViewById(R.id.btn);
btn.setOnClickListener(this);
}
@Override
public void onClick(View v) {
new WorkThread().start();
}
//工作线程
private class WorkThread extends Thread {
@Override
public void run() {
//......处理比较耗时的操作
//处理完成后改变状态
stateText.setText("completed");
}
}
}
public class MainActivity extends Activity implements View.OnClickListener {
private static final int COMPLETED = 0;
private TextView stateText;
private Button btn;
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (msg.what == COMPLETED) {
stateText.setText("completed");
}
}
};
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
stateText = (TextView) findViewById(R.id.tv);
btn = (Button) findViewById(R.id.btn);
btn.setOnClickListener(this);
}
@Override
public void onClick(View v) {
new WorkThread().start();
}
//工作线程
private class WorkThread extends Thread {
@Override
public void run() {
//......处理比较耗时的操作
//处理完成后给handler发送消息
Message msg = new Message();
msg.what = COMPLETED;
handler.sendMessage(msg);
}
}
}
通过上面这种方式,我们就可以解决线程安全的问题,把复杂的任务处理工作交给子线程去完成,然后子线程通过handler对象告知主线程,由主线程更新视图,这个过程中消息机制起着重要的作用。
class WorkThread extends Thread {
public Handler mHandler;
public void run() {
Looper.prepare();
mHandler = new Handler() {
public void handleMessage(Message msg) {
// 处理收到的消息
}
};
Looper.loop();
}
}
下面是消息机制中几个重要成员的关系图:
一个Activity中可以创建出多个工作线程,如果这些线程把他们消息放入Activity主线程的消息队列中,那么消息就会在主线程中处理了。因为主线程一般负责视图组件的更新操作,对于不是线程安全的视图组件来说,这种方式能够很好的实现视图的更新 。
那么,子线程如何把消息放入主线程的消息队列中呢?只要Handler对象以主线程的Looper创建,那么当调用Handler的sendMessage方法,系统就会把消息主线程的消息队列,并且将会在调用handleMessage方法时处理主线程消息队列中的消息 。
对于子线程访问主线程的Handler对象,你可能会问,多个子线程都访问主线程的Handler对象,发送消息和处理消息的过程中会不会出现数据的不一致呢?答案是Handler对象不会出现问题,因为Handler对象管理的Looper对象是线程安全的,不管是添加消息到消息队列还是从消息队列中读取消息都是同步保护的,所以不会出现数据不一致现象。
Android中的Looper类,是用来封装消息循环和消息队列的一个类,用于在android线程中进行消息处理。handler其实可以看做是一个工具类,用来向消息队列中插入消息的。
(1) Looper类用来为一个线程开启一个消息循环。 默认情况下android中新诞生的线程是没有开启消息循环的。(主线程除外,主线程系统会自动为其创建Looper对象,开启消息循环。) Looper对象通过MessageQueue来存放消息和事件。一个线程只能有一个Looper,对应一个MessageQueue。
(2) 通常是通过Handler对象来与Looper进行交互的。Handler可看做是Looper的一个接口,用来向指定的Looper发送消息及定义处理方法。 默认情况下Handler会与其被定义时所在线程的Looper绑定,比如,Handler在主线程中定义,那么它是与主线程的Looper绑定。 mainHandler = new Handler() 等价于new Handler(Looper.myLooper()). Looper.myLooper():获取当前进程的looper对象,类似的 Looper.getMainLooper() 用于获取主线程的Looper对象。
(3) 在非主线程中直接new Handler() 会报如下的错误: E/AndroidRuntime( 6173): Uncaught handler: thread Thread-8 exiting due to uncaught exception E/AndroidRuntime( 6173): java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare() 原因是非主线程中默认没有创建Looper对象,需要先调用Looper.prepare()启用Looper。
(4) Looper.loop(); 让Looper开始工作,从消息队列里取消息,处理消息。
注意:写在Looper.loop()之后的代码不会被执行,这个函数内部应该是一个循环,当调用mHandler.getLooper().quit()后,loop才会中止,其后的代码才能得以运行。This is a typical example of the implementation of a Looper thread, using the separation of prepare() and loop() to create an initial Handler to communicate with the Looper.
class LooperThread extends Thread
{
public Handler mHandler;
public void run()
{
Looper.prepare();
mHandler = new Handler()
{
public void handleMessage(Message msg)
{
// process incoming messages here
}
};
Looper.loop();
}
Handler 就是实现队列的形式,一个 Handler 共有两个队列:一个是线程队列,另一个是消息队列 。
要应用 Handler 进行线程队列,其流程主要是:
1. 要定义一个 Handler 对象;
2. 定义一个线程,并覆写其 run 方法;
3. 通过 Handler 对象把第二的线程对象压到队列中等待执行;
4. Handler 对象把第二的线程对象从队列中去除(当不想执行线程时 )。
注:如果需要循环执行线程,即可在处理线程后,再次利用 Handler 对象把线程对象压到队列中。
定义一个 Handler 对象的代码如下:
Handler myFirstHandler= new Handler
定义一个线程的代码如下:
Runnable myThread = new Runnable() {
public void run() {
// TODO Auto-generated method stub
}
};
myFirstHandler.post(myThread); //把一个线程放到队列里面去
Handler 对象把线程从队列中去除的方法是:
myFirstHandler.removeCallbacks(myThread);//把线程从队列中去除
通过上面我们的处理,虽然通过 Handler 对象往队列里面加入了一个新的线程,但实际上Handler 和它所属的 Activity 是处于同一个线程中。因为我们通过 Handler 把线程加到队列中,实际是直接执行了 Runable 里面的 run 方法,而且没有像 JAVA 经典多线程编程一样调用线程的start 方法,所以只是把线程对象加到 Activity 的线程中一起执行,而不是新开一个线程,即不是多真正的多线程。
更新UI采用Handle+Thread,需要发送消息,接受处理消息(在回调方法中处理),比较繁琐。除此之外,还可以使用runOnUiThread方法。
利用Activity.runOnUiThread(Runnable)把更新ui的代码创建在Runnable中,然后在需要更新ui时,把这个Runnable对象传给Activity.runOnUiThread(Runnable)。
Runnable对像就能在ui程序中被调用。如果当前线程是UI线程,那么行动是立即执行。如果当前线程不是UI线程,操作是发布到事件队列的UI线程。
public class TestActivity extends Activity {
Button btn;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.handler_msg);
btn = (Button) findViewById(R.id.button1);
btn.setOnClickListener(new OnClickListener() {
public void onClick(View view) {
new Thread(new Runnable() {
@Override
public void run() {
// 模拟耗时的操作。
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 更新主线程UI
TestActivity.this.runOnUiThread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
btn.setText("更新完毕!");
}
});
}
}).start();
}
});
}
Handler处理消息总是在创建Handler的线程里运行。而我们的消息处理中,不乏更新UI的操作,不正确的线程直接更新UI将引发异常。因此,需要时刻关心Handler在哪个线程里创建的。如何更新UI才能不出异常呢?SDK告诉我们,有以下4种方式可以从其它线程访问UI线程(也即线程间通信):
· Activity.runOnUiThread(Runnable)
· View.post(Runnable)
· View.postDelayed(Runnable, long)
· 在UI线程中创建的Handler
其中,重点说一下的是View.post(Runnable)方法。在post(Runnableaction)方法里,View获得当前线程(即UI线程)的Handler,然后将action对象post到Handler里。在Handler里,它将传递过来的action对象包装成一个Message(Message的callback为action),然后将其投入UI线程的消息循环中。在 Handler再次处理该Message时,有一条分支(未解释的那条)就是为它所设,直接调用runnable的run方法。而此时,已经路由到UI线程里,因此,我们可以毫无顾虑的来更新UI。
几点小结
· Handler的处理过程运行在创建Handler的线程里
· 一个Looper对应一个MessageQueue,一个线程对应一个Looper,一个Looper可以对应多个Handler
· 不确定当前线程时,更新UI时尽量调用View.post方法
· handler应该由处理消息的线程创建。
· handler与创建它的线程相关联,而且也只与创建它的线程相关联。handler运行在创建它的线程中,所以,如果在handler中进行耗时的操作,会阻塞创建它的线程。
· Android的线程分为有消息循环的线程和没有消息循环的线程,有消息循环的线程一般都会有一个Looper。主线程(UI线程)就是一个消息循环的线程。
· Looper.myLooper(); //获得当前的Looper
Looper.getMainLooper() //获得UI线程的Lopper
· Handle的初始化函数(构造函数),如果没有参数,那么他就默认使用的是当前的Looper,如果有Looper参数,就是用对应的线程的Looper。
· 如果一个线程中调用Looper.prepare(),那么系统就会自动的为该线程建立一个消息队列,然后调用Looper.loop();之后就进入了消息循环,这个之后就可以发消息、取消息、和处理消息。
在上面的总结中指出,Android的线程分为有消息循环的线程和没有消息循环的线程,有消息循环的线程一般都会有一个Looper。事实上Android提供了一个封装好的带有looper的线程类,即为HandlerThread,具体可参见下面的代码:
public class HandlerThreadActivity extends Activity {
private static final String TAG = "HandlerThreadActivity";
private HandlerThreadmHandlerThread;
private MyHandler mMyHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generatedmethod stub
super.onCreate(savedInstanceState);
TextView text = new TextView(this);
text.setText("HandlerThreadActivity");
setContentView(text);
Log.d(TAG, "The mainthread id = " + Thread.currentThread().getId());
//生成一个HandlerThread对象,实现了使用Looper来处理消息队列的功能,
//这个类由Android应用程序框架提供
mHandlerThread = new HandlerThread("handler_thread");
//在使用HandlerThread的getLooper()方法之前,必须先调用该类的start();
mHandlerThread.start();
//即这个Handler是运行在mHandlerThread这个线程中
mMyHandler = new MyHandler(mHandlerThread.getLooper());
mMyHandler.sendEmptyMessage(1);
}
private class MyHandler extends Handler {
public MyHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
Log.d(TAG, "MyHandler-->handleMessage-->threadid = " + Thread.currentThread().getId());
super.handleMessage(msg);
}
}
}