本文讲述的是Android中线程及创建线程的方法,涉及到的内容包括: Runable, Thread, Handler, Looper, HandlerThread, AsyncTask。将要详细讲述以下几个方面的内容: 2) Runable和Thread的关系 3) Handler, Thread, Looper, HandlerThread之间的关系
4) Runable的一点说明 5) Android中的便利类(AsyncTask等)
在java中可有两种方法实现多线程,一种是继承Thread类,一种是实现Runnable接口;
Thread类是在java.lang包中定义的 。一个类只要继承了Thread类同时覆写了本类中的run() 步骤就可以实现多线程操作了,然而一个类只能继承一个父类,这是此种方法的的局限 。 下面看例子:
class MyThread extends Thread{ private String name; public MyThread(String name) { super(); this.name = name; } public void run() { for(int i=0;i<10;i++) { System.out.println("线程开端:"+this.name+",i="+i); } } } public class ThreadDemo01 { public static void main(String[] args) { MyThread mt1=new MyThread("线程a"); MyThread mt2=new MyThread("线程b"); mt1.start(); mt2.start(); } }在实际开辟中一个多线程的操作很少 使用Thread类,而是通过Runnable接口实现 。 public interface Runnable{ public void run(); }
例子:
class MyThread implements Runnable{ private String name; public MyThread(String name) { this.name = name; } public void run(){ for(int i=0;i<100;i++){ System.out.println("线程开端:"+this.name+",i="+i); } } };
然而在Runnable的子类中没有start() 方法,只有Thread类中才有 。此时视察Thread类,有一个构造函数:public Thread(Runnable targer) 此构造函数接受Runnable的子类实例,也就是说可以通过Thread类来启动Runnable实现多线程 。(start() 可以协调系统的资源):
public class ThreadDemo01 { public static void main(String[] args) { MyThread mt1=new MyThread("线程a"); MyThread mt2=new MyThread("线程b"); new Thread(mt1).start(); new Thread(mt2).start(); } }在程序实现多线程应优先以实现Runnable接口为主,由于实现Runnable接口相比继承Thread类有如下好处:
避免点继承的局限,一个类可以继承多个接口 。
利于资源的共享。
以卖票程序为例,通过Thread类实现:
class MyThread extends Thread { private int ticket=10; public void run(){ for(int i=0;i<20;i++) { if(this.ticket>0){ System.out.println("卖票:ticket"+this.ticket--); } } } };下面通过三个线程对象,同时卖票:
public class ThreadTicket { public static void main(String[] args) { MyThread mt1=new MyThread(); MyThread mt2=new MyThread(); MyThread mt3=new MyThread(); mt1.start();//每个线程都各卖了10张,共卖了30张票 mt2.start();//但实际只有10张票,每个线程都卖自己的票 mt3.start();//没有达到资源共享 } }假如用Runnable就 可以实现资源共享,下面看例子:
class MyThread implements Runnable{ private int ticket=10; public void run(){ for(int i=0;i<20;i++){ if(this.ticket>0){ System.out.println("卖票:ticket"+this.ticket--); } } } } public class RunnableTicket { public static void main(String[] args) { MyThread mt=new MyThread(); new Thread(mt).start();//同一个mt new Thread(mt).start(); new Thread(mt).start(); } };现在程序中有三个线程,然而一共卖了10张票,也就是说使用Runnable实现多线程可以达到资源共享的目的。
(1) 说白了就是类和接口的区别。Thread是一个类,java中是不允许继承多个父类的,这就是Thread的一个局限性。而使用Runnable就不同了,可以implements多个接口,同时继承一个父类,这样会更加灵活。 (2) 当多个线程需要共享资源时,用Thread很难达到目的,但是用Runnable接口就容易许多了。 (3) 二者的联系:看源码可以发现,Thread其实就是继承了Runnable接口的子类。
在Android中,线程分为有消息循环的线程和没有消息循环的线程,有消息循环的线程一般都会有一个Looper,这个是android的新概念。我们的主线程(UI线程)就是一个有消息循环的线程。
针对这种消息循环的机制,我们引入一个新的机制--Handler,
我们有消息循环,就要往消息循环里面发送相应的消息,自定义消息一般都会有自己对应的处理,消息的发送和清除、消息的的处理,把这些都封装在Handler里面,注意Handler只是针对那些有Looper的线程,不管是UI线程还是子线程,只要你有Looper,我就可以往你的消息队列里面添加东西,并做相应的处理。
一个线程对应一个或者零个Looper和MessageQueue。
(1) Message:消息,其中包含了消息ID,消息处理对象以及处理的数据等,由MessageQueue统一列队,终由Handler处理。
(2) MessageQueue:消息队列,用来存放Handler发送过来的消息,并按照FIFO规则执行。当然,存放Message并非实际意义的保存,而是将Message以链表的方式串联起来的,等待Looper的抽取。
(3) Handler:处理者,负责Message的发送及处理。使用Handler时,需要实现handleMessage(Message msg)方法来对特定的Message进行处理,例如更新UI等。
(4) Looper:消息泵,不断地从MessageQueue中抽取Message执行。因此,一个MessageQueue需要一个Looper。
(5) Thread:线程,负责调度整个消息循环,即消息循环的执行场所。
(6) Looper.myLooper()获得新线程的Looper,Looper.getMainLooper()是获得主线程的Looper
(7) 通过new MyHandler(Looper)有参构造函数来让Looper和Handler进行沟通 无参的构造函数,默认获取的是当前线程的Looper Message message = mHandler.obtainMessage(1, 1, 1, msg); mHandler.sendMessage(message); // 发送消息 定义一个类继承自Handler,改写方法handleMessage(Message msg)接收并处理消息
Handler在android里负责发送和处理消息。它的主要用途: 1)按计划发送消息或执行某个Runnanble(使用POST方法),类似定时器; 2)从其他线程中发送来的消息放入消息队列中,避免线程冲突(常见于更新UI线程); 默认情况下,Handler接受的是当前线程下的消息循环实例(使用Handler(Looper looper)、Handler(Looper looper, Handler.Callback callback)可以指定线程),同时一个消息队列(MessageQueue和Looper封装)可以被当前线程中的多个对象进行分发、处理(在UI线程中,系统已经有一个Activity来处理了,你可以再起若干个Handler来处理)。
在实例化Handler的时候,Looper可以是任意线程的,只要有Handler的指针,任何线程也都可以sendMessage。
Handler对于Message的处理不是并发的。一个Looper 只有处理完一条Message才会读取下一条,所以消息的处理是阻塞形式的(handleMessage()方法里不应该有耗时操作,可以将耗时操作放在其他线程执行,操作完后发送Message(通过sendMessges方法),然后由handleMessage()更新UI)。
Looper类用来创建消息队列. 每个线程最多只能有一个消息队列, android中UI线程默认具有消息队列, 但非UI线程在默认情况下是不具备消息队列的. 如果需要在非UI线程中开启消息队列, 需要调用Looper.prepare()方法, 在该方法的执行过程中会创建一个Looper对象, 而Looper的构造函数中会创建一个MessageQueue instance(Looper的构造函数是私有的, 在Looper类之外无法创建其对象). 此后再为该线程绑定一个Handler instance, 然后调用Looper.loop()方法, 就可以不断的从消息队列中取出消息和处理消息了. Looper.myLoop()方法可以得到线程的Looper对象, 如果为null, 说明此时该线程尚未开启消息队列.
创建一个 Looper 对象时,会同时创建一个 MessageQueue 对象(一个looper对应一个MessageQueue)。除了主线程有默认的 Looper ,其他线程默认是没有 MessageQueue 对象的,所以,不能接受 Message 。如需要接受,自己定义 一个 Looper 对象 ( 通过 prepare 函数 ), 这样该线程就有了自己的 Looper 对象和 MessageQueue 数据结构了。 Looper 从 MessageQueue 中取出 Message 然后,交由 Handler 的 handleMessage 进行处理。处理完成后,调用 Message.recycle() 将其放入 Message Pool 中。
Message :消息对象, Message Queue 中的存放的对象。一个 Message Queue 中包含多个 Message 。
Message 实例对象的取得,通常使用 Message 类里的静态方法 obtain(), 该方法有多个重载版本可供选择;它的创建并不一定是直接创建一个新的实例,而是先从 Message Pool( 消息池 ) 中看有没有可用的 Message 实例,存在则直接取出返回这个实例。如果 Message Pool 中没有可用的 Message 实例,则才用给定的参数创建一个 Message 对象。调用 removeMessages() 时,将 Message 从 Message Queue 中删除,同时放入到 Message Pool 中。除了上面这种方式,也可以通过 Handler 对象的 obtainMessage() 获取 一个 Message 实例。 Message类用于表示消息. Message对象可以通过arg1, arg2, obj字段和setData()携带数据, 此外还具有很多字段.
when字段决定Message应该何时出对处理,;
target字段用来表示将由哪个Handler对象处理这个消息;
next字段表示在消息队列中排在这个Message之后的下一个Message;
callback字段如果不为null表示这个Message包装了一个runnable对象;
what字段表示code, 即这个消息具体是什么类型的消息. 每个what都在其handler的namespace中, 我们只需要确保将由同一个handler处理的消息的what属性不重复就可以.
MessageQueue类用于表示消息队列. 队列中的每一个Message都有一个when字段, 这个字段用来决定Message应该何时出队处理. 消息队列中的每一个Message根据when字段的大小由小到大排列, 排在最前面的消息会首先得到处理, 因此可以说消息队列并不是一个严格的先进先出的队列。 主线程创建时,会创建一个默认的 Looper 对象,而 Looper 对象的创建,将自动创建一个 Message Queue 。其他非主线程,不会自动创建 Looper ,要需要的时候,通过调用 prepare 函数来实现。
Message对象的target字段关联了哪个线程的消息队列, 这个消息就会被压入哪个线程的消息队列中.
a 调用Handler类中以send开头的方法可以将Message对象压入消息队列中;
b 调用Handler类中以post开头的方法可以将一个runnable对象包装在一个Message对象中, 然后再压入消息队列, 此时入队的Message其callback字段不为null, 值就是这个runnable对象. 调用Handler对象的这些方法入队的Message, 其target属性会被赋值为这个handler对象.
c 调用Message对象的sendToTarget()方法可以将其本身压入与其target字段(即handler对象)所关联的消息队列中.
所有在消息队列中的消息, 都具有target字段. 消息是在target所关联的线程上被取出和处理的.
1. 如果取出的Message对象的callback字段不为null, 那么就调用callback字段的run()方法(callback字段的类型是runnable). 注意此时并不开启一个新的线程运行run()方法, 而是直接在handler对象(即Message的target字段)所关联的线程上运行.
2. 如果取出的Message对象的callback字段为null, 且Handler对象中的callback字段也为null, 那么这个消息将由Handler对象的handleMessage(msg)方法处理. 注意Message对象的callback字段是Runnable类型的而Handler对象的callback字段是Callback类型的, Handler对象的callback字段是在创建Handler instance的时候指定的, 如果没有指定则这个字段为null, 详见Handler类的四个构造方法.
3. 如果取出的Message对象的callback字段为null, 且Handler对象中的callback字段不为null, 那么这个消息将由Handler对象中的callback字段的handleMessage方法处理.
以上的述说太麻烦,下面看一下源代码中的实现吧, 加深一下印象:
public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } }有了以上的叙述, 线程间的通信也就好理解了. 假如一个handler关联了A线程上的消息队列, 那么我们可以在B线程上调用handler的相关方法向A线程上的消息队列压入一个Message, 这个Message将在A线程上得到处理. 下面列举几个常用的例子:
a. 在onCreate中创建Handler
public class HandlerTestApp extends Activity { Handler mHandler; TextView mText; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); mHandler = new Handler();//创建Handler mText = (TextView) findViewById(R.id.text0);//一个TextView }
b. 构建Runnable对象,在runnable中更新界面,此处,我们修改了TextView的文字.此处需要说明的是,Runnable对象可以再主线程中创建,也可以再子线程中创建。我们此处是在子线程中创建的。
Runnable mRunnable0 = new Runnable() { @Override public void run() { // TODO Auto-generated method stub mText.setText("This is Update from ohter thread, Mouse DOWN"); } };
c. 创建子线程,在线程的run函数中,我们向主线程的消息队列发送了一个runnable来更新界面。
private void updateUIByRunnable(){ new Thread() { //Message msg = mHandler.obtainMessage(); public void run() { //mText.setText("This is Update from ohter thread, Mouse DOWN");//这句将抛出异常 mHandler.post(mRunnable0); } }.start(); }
用Message更新界面与Runnable更新界面类似,只是需要修改几个地方。
a. 实现自己的Handler,对消息进行处理 private class MyHandler extends Handler { @Override public void handleMessage(Message msg) { // TODO Auto-generated method stub super.handleMessage(msg); switch(msg.what) { case UPDATE://在收到消息时,对界面进行更新 mText.setText("This update by message"); break; } } }
b. 在新的线程中发送消息
private void updateByMessage() { //匿名对象 new Thread() { public void run() { //mText.setText("This is Update from ohter thread, Mouse DOWN"); //UPDATE是一个自己定义的整数,代表了消息ID Message msg = mHandler.obtainMessage(UPDATE); mHandler.sendMessage(msg); } }.start(); }
上一节讲到,UI线程都是带Looper的线程,可以进行消息循环,而自己新建的子线程或线程都是没有Looper的,不能接收消息进行处理,如果想要往自己的线程中发送消息或post一个runable,那么必须在自己的线程中去调用Looper.prepare()。
如果自己要实现Thread并手工建立Loop的话,则需要注意线程的同步等问题。具体参考《深入理解Android 卷一》
但是,哈哈,Android是一个优秀易用的系统,对于这样的问题,它早就想到了,并提供了解决方案,那就是HandlerThread。大家直接用它就行。下面给一个例子:
private class TestHandlerThread extends HandlerThread{ // 注5 public TestHandlerThread(String name) { super(name); // TODO Auto-generated constructor stub } @Override public void run() { // TODO Auto-generated method stub super.run(); Message msg = mTestHandler1.obtainMessage(); Bundle b = new Bundle(); b.putString("color", "red"); // 给handler发送的数据,放入message中 msg.setData(b); msg.what = MESSAGE_ID; Looper looper = this.getLooper(); new Handler(looper){ @Override public void handleMessage(Message msg) { // TODO Auto-generated method stub super.handleMessage(msg); Log.d("HandlerDemo", "TestHandlerThread---------"); } }.sendMessage(msg); } } // (3)new HandlerThread TestHandlerThread testHandlerThread = new TestHandlerThread("testHandlerThread"); testHandlerThread.start();上面的例子,是在自己的线程中new Handler并给自己发消息, 当然你也可以在主线程或其它线程中创建Handler, 给它发消息,让它进行处理。
大家如果能看到这里,那恭喜你,来点轻松的,哈哈。
Runable在上面总共说到了两种用途。 还记得吗,请看:
1) 实现一个Runable接口的子类,把它当实例传给Thread的构造函数,来创建一个新线程。
2) 实现一个Runable接口的子类,用Handler把它post到相关联的线程中。
不用疑惑,上面的两个用法没有一点点关系,把它当成两类来看就是了。 原因就是Runable是一个接口,只定义了一个run()方法,具体用法可以多样化,呵呵~~
5. Android中的便利类
太累了,不写了,自己去查吧,或者等我下次再开单章来单独写它们。
Task
AsynTask
HandlerThread
6. 参考文献
Handler的相关知识和应用
Android的Handler的使用方法
Handler的用法
android的Thread、Runnable、Asyntask的区别与联系
