zoukankan      html  css  js  c++  java
  • Android线程间通信机制(Handler Looper )

    Android线程间通信机制

    当android应用程序运行时,一个主线程被创建(也称作UI线程),此线程主要负责处理UI相关的事件,由于Android采用UI单线程模型,所以只能在主线程中对UI元素进行操作,如果在非UI线程直接对UI进行了操作,则会报错,另外,对于运算量较大的操作和IO操作,我们需要新开线程来处理这些工作,以免阻塞UI线程,子线程与主线程之间是怎样进行通信的呢?此时就要采用消息循环机制(Looper)与Handler进行处理。

    一、基本概念

    Looper:每一个线程都可以产生一个Looper,用来管理线程的Message,Looper对象会建立一个MessgaeQueue数据结构来存放message。

    Handler:与Looper沟通的对象,可以push消息或者runnable对象到MessgaeQueue,也可以从MessageQueue得到消息。

    查看其构造函数:

    Handler()

    Default constructor associates this handler with the queue for the current thread.//如不指定Looper参数则默认利用当前线程的Looper创建

    Handler(Looper looper)

    Use the provided queue instead of the default one.//使用指定的Looper对象创建Handler

    线程A的Handler对象引用可以传递给别的线程,让别的线程B或C等能送消息来给线程A。

    线程A的Message Queue里的消息,只有线程A所属的对象可以处理。

    注意:Android里没有global的MessageQueue,不同进程(或APK之间)不能通过MessageQueue交换消息。

    二、Handler通过Message通信的基本方式

    使用Looper.myLooper可以取得当前线程的Looper对象。

    使用mHandler = new Handler(Looper.myLooper()); 可产生用来处理当前线程的Handler对象。

    使用mHandler = new Handler(Looper.getMainLooper()); 可诞生用来处理main线程的Handler对象。

    使用Handler传递消息对象时将消息封装到一个Message对象中,Message对象中主要字段如下:

    public int

    arg1

    当需要传递的消息是整形时arg1 和 arg2 是一种低成本的可选方案,他使用 setData()/getData()访问或修改字段。 

    public int

    arg2

    同上

    public Object

    obj

    可传送的任意object类型.

    public int

    what

    Int类型用户自定义的消息类型码

    Message对象可以通过Message类的构造函数获得,但Google推荐使用Message.obtain()方法获得,该方法会从全局的对象池里返回一个可复用的Messgae实例,API中解释如下:

    Message()

    Constructor (but the preferred way to get a Message is to call Message.obtain()).

    Handler发出消息时,既可以指定消息被接受后马上处理,也可以指定经过一定时间间隔之后被处理,如sendMessageDelayed(Message msg, long delayMillis),具体请参考API。

    Handler消息被发送出去之后,将由handleMessage(Message msg)方法处理。

     

    注意:在Android里,新诞生一个线程,并不会自动建立其Message Loop

    可以通过调用Looper.prepare()为该线程建立一个MessageQueue,再调用Looper.loop()进行消息循环

    下面举例说明:

    在main.xml中定义两个button及一个textview

    <Button

    android:id="@+id/a"

    android:layout_width
    ="80dp"

    android:layout_height
    ="60dp"

    android:padding
    ="6dp"

    android:layout_marginTop
    ="10dp"

    android:text
    ="Test looper"

    android:hint
    ="Test looper" />

    <Button

    android:id="@+id/b"

    android:layout_width
    ="80dp"

    android:layout_height
    ="60dp"

    android:padding
    ="6dp"

    android:layout_marginTop
    ="10dp"

    android:text
    ="Exit" />

    <TextView

    android:id="@+id/tv"

    android:layout_width
    ="fill_parent"

    android:layout_height
    ="wrap_content"

    android:layout_marginTop
    ="10dp"/>



     

    Activity源代码如下:

      1 public class HandlerTestActivity extends Activity implements Button.OnClickListener{
    2
    3 public TextView tv;
    4
    5 private myThread myT;
    6
    7 Button bt1, bt2;
    8
    9 /** Called when the activity is first created. */
    10
    11 @Override
    12
    13 public void onCreate(Bundle savedInstanceState) {
    14
    15 super.onCreate(savedInstanceState);
    16
    17 setContentView(R.layout.main);
    18
    19 bt1 = (Button)findViewById(R.id.a);
    20
    21 bt2 = (Button)findViewById(R.id.b);
    22
    23 tv = (TextView)findViewById(R.id.tv);
    24
    25 bt1.setId(1);//为两个button设置ID,此ID用于后面判断是哪个button被按下
    26
    27 bt2.setId(2);
    28
    29 bt1.setOnClickListener(this);//增加监听器
    30
    31 bt2.setOnClickListener(this);
    32
    33 }
    34
    35
    36
    37 @Override
    38
    39 public void onClick(View v) {
    40
    41 // TODO Auto-generated method stub
    42
    43 switch(v.getId()){//按键事件响应,如果是第一个按键将启动一个新线程
    44
    45 case 1:
    46
    47 myT = new myThread();
    48
    49 myT.start();
    50
    51 break;
    52
    53 case 2:
    54
    55 finish();
    56
    57 break;
    58
    59 default:
    60
    61 break;
    62
    63 }
    64
    65 }
    66
    67
    68
    69 class myThread extends Thread
    70
    71 {
    72
    73 private EHandler mHandler;
    74
    75 public void run()
    76
    77 {
    78
    79 Looper myLooper, mainLooper;
    80
    81 myLooper = Looper.myLooper();//得到当前线程的Looper
    82
    83 mainLooper = Looper.getMainLooper();//得到UI线程的Looper
    84
    85 String obj;
    86
    87 if(myLooper == null)//判断当前线程是否有消息循环Looper
    88
    89 {
    90
    91 mHandler = new EHandler(mainLooper);
    92
    93 obj = "current thread has no looper!";//当前Looper为空,EHandler用mainLooper对象构造
    94
    95 }
    96
    97 else
    98
    99 {
    100
    101 mHandler = new EHandler(myLooper);//当前Looper不为空,EHandler用当前线程的Looper对象构造
    102
    103 obj = "This is from current thread.";
    104
    105 }
    106
    107 mHandler.removeMessages(0);//清空消息队列里的内容
    108
    109 Message m = mHandler.obtainMessage(1, 1, 1, obj);
    110
    111 mHandler.sendMessage(m);//发送消息
    112
    113 }
    114
    115 }
    116
    117
    118
    119 class EHandler extends Handler
    120
    121 {
    122
    123 public EHandler(Looper looper)
    124
    125 {
    126
    127 super(looper);
    128
    129 }
    130
    131 @Override
    132
    133 public void handleMessage(Message msg) //消息处理函数
    134
    135 {
    136
    137 tv.setText((String)msg.obj);//设置TextView内容
    138
    139 }
    140
    141 }
    142
    143
    144
    145 }



    程序运行后点击TestLooper按键,TextView输出如下,说明新创建的线程里Looper为空,也就说明了新创建的线程并不会自己建立Message Looper。

     

     

     

    修改myThread类:

      

     1 class myThread extends Thread
    2
    3 {
    4
    5 private EHandler mHandler;
    6
    7 public void run()
    8
    9 {
    10
    11 Looper myLooper, mainLooper;
    12
    13 Looper.prepare();
    14
    15 myLooper = Looper.myLooper();
    16
    17 mainLooper = Looper.getMainLooper();
    18
    19 ......
    20
    21 ......
    22
    23 mHandler.sendMessage(m);
    24
    25 Looper.loop();
    26
    27 }
    28
    29 }

     

        Looper.prepare为当前线程创建一个Message Looper,Looper.loop()开启消息循环。这样修改是OK呢?

        答案是否定的!运行时Logcat将抛出CalledFromWrongThreadException异常错误,提示如下:

       

        意思就是说“只有原始创建这个视图层次的线程才能修改它的视图”,本例中的TextView是在UI线程(主线程)中创建,因此,myThread线程不能修改其显示内容!

           一般的做法是在子线程里获取主线程里的Handler对象,然后通过该对象向主线程的消息队列里发送消息,进行通信。

           如果子线程想修改主线程的UI,可以通过发送Message给主线程的消息队列,主线程经行判断处理再对UI经行操作,具体可以参考之前的代码。

     

    三、Handler通过runnable通信的基本方式

    我们可以通过Handler的post方法实现线程间的通信,API中关于post方法说明如下

    public final boolean post (Runnable r)

    Causes the Runnable r to be added to the message queue. The runnable will be run on the thread to which this handler is attached.

    Post方法将安排runnable对象在主线程的某个位置运行,但是并不会开启一个新的线程,验证代码如下:

     1 public class HandlerTestActivity extends Activity {
    2
    3
    4
    5 private Handler handlerTest;
    6
    7 Runnable runnableTest = new Runnable()
    8
    9 {
    10
    11 public void run()
    12
    13 {
    14
    15 String runID = String.valueOf(Thread.currentThread().getId());//输出Runnable 线程的ID号
    16
    17 Log.v("Debug",runID);
    18
    19 }
    20
    21 };
    22
    23 /** Called when the activity is first created. */
    24
    25 @Override
    26
    27 public void onCreate(Bundle savedInstanceState) {
    28
    29 super.onCreate(savedInstanceState);
    30
    31 setContentView(R.layout.main);
    32
    33 handlerTest = new Handler();
    34
    35 String mainID = String.valueOf(Thread.currentThread().getId());
    36
    37 Log.v("Debug",mainID);//输出主线程的ID号
    38
    39 handlerTest.post(runnableTest);
    40
    41 }
    42
    43 }



    Logcat里输出如下:

    说明只是把runnable里的run方法放到UI线程里运行,并不会创建新线程

    因此我们可以在子线程中将runnable加入到主线程的MessageQueue,然后主线程将调用runnable的方法,可以在此方法中更新主线程UI。

    将之前的代码修改如下:

     1 public class HandlerTestActivity extends Activity {
    2
    3 private Handler handlerTest;
    4
    5 private TextView tv;
    6
    7 /** Called when the activity is first created. */
    8
    9 @Override
    10
    11 public void onCreate(Bundle savedInstanceState) {
    12
    13 super.onCreate(savedInstanceState);
    14
    15 setContentView(R.layout.main);
    16
    17 tv = (TextView)findViewById(R.id.tv);//TextView初始化为空
    18
    19 handlerTest = new Handler();
    20
    21 myThread myT = new myThread();
    22
    23 myT.start();//开启子线程
    24
    25 }
    26
    27
    28
    29 class myThread extends Thread{
    30
    31 public void run(){
    32
    33 handlerTest.post(runnableTest);//子线程将runnable加入消息队列
    34
    35 }
    36
    37 }
    38
    39
    40
    41 Runnable runnableTest = new Runnable()
    42
    43 {
    44
    45 public void run()
    46
    47 {
    48
    49 tv.setText("此信息由子线程输出!");
    50
    51 }
    52
    53 };
    54
    55 }



    相当于在主线程中调用了runnalberun方法,更改了TextViewUI!

  • 相关阅读:
    VOA 2009/11/02 DEVELOPMENT REPORT In Kenya, a Better Life Through Mobile Money
    2009.11.26教育报道在美留学生数量创历史新高
    Java中如何实现Tree的数据结构算法
    The Python Tutorial
    VOA HEALTH REPORT Debate Over New Guidelines for Breast Cancer Screening
    VOA ECONOMICS REPORT Nearly Half of US Jobs Now Held by Women
    VOA ECONOMICS REPORT Junior Achievement Marks 90 Years of Business Education
    VOA 2009/11/07 IN THE NEWS A Second Term for Karzai; US Jobless Rate at 10.2%
    Ant入门
    Python 与系统管理
  • 原文地址:https://www.cnblogs.com/leon19870907/p/2367549.html
Copyright © 2011-2022 走看看