Android------线程thread

来源:互联网 发布:淘宝神店 知乎 编辑:程序博客网 时间:2024/06/01 23:56

1.  本文的讲述内容和范围

    本文讲述的是Android中线程及创建线程的方法,涉及到的内容包括: Runable, Thread, Handler, Looper, HandlerThread, AsyncTask。将要详细讲述以下几个方面的内容:
    2) Runable和Thread的关系
    3) Handler, Thread, Looper, HandlerThread之间的关系

    4) Runable的一点说明
    5) Android中的便利类(AsyncTask等)

2. Runable和Thread的关系

在java中可有两种方法实现多线程,一种是继承Thread类,一种是实现Runnable接口;

  1)继承Thread类的方式

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();}}

2) Runnable接口

在实际开辟中一个多线程的操作很少 使用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();}}

3)实际中如何应用这两种实现模式

    在程序实现多线程应优先以实现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();//同一个mtnew Thread(mt).start();new Thread(mt).start();}};

现在程序中有三个线程,然而一共卖了10张票,也就是说使用Runnable实现多线程可以达到资源共享的目的。

 4)Runnable接口和Thread的关系总结

        (1) 说白了就是类和接口的区别。Thread是一个类,java中是不允许继承多个父类的,这就是Thread的一个局限性。而使用Runnable就不同了,可以implements多个接口,同时继承一个父类,这样会更加灵活。
        (2) 当多个线程需要共享资源时,用Thread很难达到目的,但是用Runnable接口就容易许多了。
        (3) 二者的联系:看源码可以发现,Thread其实就是继承了Runnable接口的子类。

3. Handler, Thread, Looper之间的关系

  1)概述

      在Android中,线程分为有消息循环的线程和没有消息循环的线程,有消息循环的线程一般都会有一个Looper,这个是android的新概念。我们的主线程(UI线程)就是一个有消息循环的线程。

      针对这种消息循环的机制,我们引入一个新的机制--Handler,

      我们有消息循环,就要往消息循环里面发送相应的消息,自定义消息一般都会有自己对应的处理,消息的发送和清除、消息的的处理,把这些都封装在Handler里面,注意Handler只是针对那些有Looper的线程,不管是UI线程还是子线程,只要你有Looper,我就可以往你的消息队列里面添加东西,并做相应的处理。

      一个线程对应一个或者零个Looper和MessageQueue。

     2)各自职责

      (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)接收并处理消息

3) Handler

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)。

 4)Looper

      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 中。

 5) Message

      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属性不重复就可以.

 6) MessageQueue

      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);        }}

7) Handler的用法例子

有了以上的叙述, 线程间的通信也就好理解了. 假如一个handler关联了A线程上的消息队列, 那么我们可以在B线程上调用handler的相关方法向A线程上的消息队列压入一个Message, 这个Message将在A线程上得到处理. 下面列举几个常用的例子:

通过Runnable在子线程中更新界面的例子


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在子线程中来更新界面

用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(); 
   }

8) HandlerThread

      上一节讲到,UI线程都是带Looper的线程,可以进行消息循环,而自己新建的子线程或线程都是没有Looper的,不能接收消息进行处理,如果想要往自己的线程中发送消息或post一个runable,那么必须在自己的线程中去调用Looper.prepare()。

      如果自己要实现Thread并手工建立Loop的话,则需要注意线程的同步等问题。具体参考《深入理解Android 卷一》

      但是,哈哈,Android是一个优秀易用的系统,对于这样的问题,它早就想到了,并提供了解决方案,那就是HandlerThread。大家直接用它就行。下面给一个例子:

private class TestHandlerThread extends HandlerThread{  // 注5public TestHandlerThread(String name) {super(name);// TODO Auto-generated constructor stub}@Overridepublic void run() {// TODO Auto-generated method stubsuper.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){@Overridepublic void handleMessage(Message msg) {// TODO Auto-generated method stubsuper.handleMessage(msg);Log.d("HandlerDemo", "TestHandlerThread---------");}}.sendMessage(msg);}}// (3)new HandlerThread TestHandlerThread testHandlerThread = new TestHandlerThread("testHandlerThread");testHandlerThread.start();

上面的例子,是在自己的线程中new Handler并给自己发消息, 当然你也可以在主线程或其它线程中创建Handler, 给它发消息,让它进行处理。 

4. Runable的一点说明

    大家如果能看到这里,那恭喜你,来点轻松的,哈哈。

    Runable在上面总共说到了两种用途。 还记得吗,请看:

        1) 实现一个Runable接口的子类,把它当实例传给Thread的构造函数,来创建一个新线程。

        2) 实现一个Runable接口的子类,用Handler把它post到相关联的线程中。

不用疑惑,上面的两个用法没有一点点关系,把它当成两类来看就是了。 原因就是Runable是一个接口,只定义了一个run()方法,具体用法可以多样化,呵呵~

5. Android中的便利类

太累了,不写了,自己去查吧,或者等我下次再开单章来单独写它们。

Task

AsynTask

HandlerThread

6. 参考文献

Handler的相关知识和应用

Android的Handler的使用方法

Handler的用法

android的Thread、Runnable、Asyntask的区别与联系

0 0
原创粉丝点击