zoukankan      html  css  js  c++  java
  • Android消息机制之Handler

    Android为什么要提供Handler

    Android建议我们不要在UI线程中执行耗时操作,因为这很容易导致ANR异常(在Android源码中我们可以看到,UI如果对用户的操作超过5秒无响应,就会报ANR异常)。因此,一些耗时操作都会在子线程中完成。当我们在子线程中获取了数据,要将其显示到UI中,如果没有Handler,这将很难完成。因此,Android之所以提供Handler,就是为了解决子线程访问UI的问题。
    为什么Android不允许在子线程中访问UI呢?显然这样做不安全,多线程访问UI是不安全的(学过操作系统的盆友应该都了解线程互斥,这里我就不详细介绍了)。有人就会说了,可以通过设置信号量来解决啊。这中方法不是不可以,因为这种方法会使访问UI的逻辑变得复杂;其次这会降低UI的访问效率。而使用Handler就比较简单高效。Handler是同个Message来通讯的。

    Handler的用法

    使用Handler时,需要重写handleMessage方法,在handleMessage中接受新线程发来的Message,并做相应的处理。在新线程中则是通过Message来传递消息,Message中往往也携带着需要传递的数据以及消息的类型。还要强调一点,如果当前线程有Looper就不需要执行Looper.prepare(),如果没有,就需要在新线程内执行Looper.prepare(),否则会报错。具体使用代码如下:

     1 public class MainActivity extends AppCompatActivity {
     2     private Handler mHandler=new Handler(){
     3         @Override
     4         public void handleMessage(Message msg) {
     5             switch (msg.what)
     6             {
     7                 case 1:
     8                     //执行需要修改的UI操作
     9                     break;
    10                 default:
    11                     break;
    12             }
    13         }
    14     };
    15 
    16     @Override
    17     protected void onCreate(Bundle savedInstanceState) {
    18         super.onCreate(savedInstanceState);
    19         setContentView(R.layout.activity_main);
    20 
    21         new Thread(new Runnable() {
    22             @Override
    23             public void run() {//在新线程中执行耗时操作
    24 
    25                 //如果当前线程有Looper就不需要执行Looper.prepare();
    26                 Looper.prepare();
    27                 try {
    28                     Thread.sleep(1000);//睡眠1秒
    29                 } catch (InterruptedException e) {
    30                     e.printStackTrace();
    31                 }
    32 
    33                 //操作完成之后通过发送Message,来通知Handler进行UI操作
    34 
    35                 Message msg=new Message();
    36                 msg.what=1;
    37 
    38                 /*这部分是伪代码,value 是想通过Message传递的值
    39                 Bundle data=new Bundle();
    40                 data.putSerializable("key",value);
    41                 msg.setData(data);
    42 
    43                 */
    44 
    45                 //设置好数据后,发送消息
    46                 mHandler.sendMessage(msg);
    47             }
    48         }).start();
    49     }
    50 
    51 }

    当然,handler也可以在子线程中创建,代码如下:

     1   private TextView tv_test;
     2 
     3     @Override
     4     protected void onCreate(Bundle savedInstanceState) {
     5         super.onCreate(savedInstanceState);
     6         setContentView(R.layout.handler_test_layout);
     7 
     8         tv_test= (TextView) findViewById(R.id.tv_test);
     9 
    10 
    11     }
    12 
    13     //button点击的函数
    14     public void click(View v)
    15     {
    16         new Thread(new Runnable() {
    17             @Override
    18             public void run() {
    19                 Looper.prepare();
    20                 Handler handler=new Handler(Looper.getMainLooper()){
    21                     @Override
    22                     public void handleMessage(Message msg) {
    23                         switch (msg.what)
    24                         {
    25                             case 1:
    26                                 tv_test.setText("receive msg");
    27                         }
    28                     }
    29                 };
    30                 Message msg=new Message();
    31                 msg.what=1;
    32                 handler.sendMessage(msg);
    33             }
    34         }).start();
    35     }

    上面的代码是,当点击按钮后,就会创建一个新的线程,在新线程中创建handler,并发送消息、接受消息。这里需要注意的是,在新线程中创建handler需要使用Handler handler=new Handler(Looper.getMainLooper())这样的写法,Looper.getMainLooper()将主线程中的Looper传过去,这样handler才能访问UI。运行效果如下:

    Handler的内部机制

    Handler创建时会采用Looper来建立消息循环。所以,当前线程必须要有Looper。当Handler创建完成后,其内部的Looper以及MessageQueue既可以和Handler一起协同工作了。Handler通过sendMessage将消息发送给内部的MessageQueue,而MessageQueue会调用queue.enqueueMessage(msg, uptimeMillis)方法,它的源码如下:

     1 boolean enqueueMessage(Message msg, long when) {
     2         if (msg.target == null) {
     3             throw new IllegalArgumentException("Message must have a target.");
     4         }
     5         if (msg.isInUse()) {
     6             throw new IllegalStateException(msg + " This message is already in use.");
     7         }
     8 
     9         synchronized (this) {
    10             if (mQuitting) {
    11                 IllegalStateException e = new IllegalStateException(
    12                         msg.target + " sending message to a Handler on a dead thread");
    13                 Log.w(TAG, e.getMessage(), e);
    14                 msg.recycle();
    15                 return false;
    16             }
    17 
    18             msg.markInUse();
    19             msg.when = when;
    20             Message p = mMessages;
    21             boolean needWake;
    22             if (p == null || when == 0 || when < p.when) {
    23                 // New head, wake up the event queue if blocked.
    24                 msg.next = p;
    25                 mMessages = msg;
    26                 needWake = mBlocked;
    27             } else {
    28                 // Inserted within the middle of the queue.  Usually we don't have to wake
    29                 // up the event queue unless there is a barrier at the head of the queue
    30                 // and the message is the earliest asynchronous message in the queue.
    31                 needWake = mBlocked && p.target == null && msg.isAsynchronous();
    32                 Message prev;
    33                 for (;;) {
    34                     prev = p;
    35                     p = p.next;
    36                     if (p == null || when < p.when) {
    37                         break;
    38                     }
    39                     if (needWake && p.isAsynchronous()) {
    40                         needWake = false;
    41                     }
    42                 }
    43                 msg.next = p; // invariant: p == prev.next
    44                 prev.next = msg;
    45             }
    46 
    47             // We can assume mPtr != 0 because mQuitting is false.
    48             if (needWake) {
    49                 nativeWake(mPtr);
    50             }
    51         }
    52         return true;
    53     }

    通过源码,我们发现,queue.enqueueMessage(msg, uptimeMillis)将消息放入了MessageQueue里。Looper则会一直处理MessageQueue中的消息。

    
    
    
    
  • 相关阅读:
    css去掉点击连接时所产生的虚线边框技巧兼容符合w3c标准的浏览器
    html中<a href> </a>的用法
    点击页面其他地方关闭弹出层
    CSS文字两端对齐
    mouseover和mouseenter的区别
    jquery中的$("#id")与document.getElementById("id")的区别
    console.log
    ie6中margin失效问题
    渐变
    CSS 清除浮动的4种方法
  • 原文地址:https://www.cnblogs.com/yxx123/p/5256190.html
Copyright © 2011-2022 走看看