你的位置:首页 > 操作系统

[操作系统]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   @Override17   protected void onCreate(Bundle savedInstanceState) {18     super.onCreate(savedInstanceState);19     setContentView(R.layout.activity_main);20 21     new Thread(new Runnable() {22       @Override23       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       @Override18       public void run() {19         Looper.prepare();20         Handler handler=new Handler(Looper.getMainLooper()){21           @Override22           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 wake29         // up the event queue unless there is a barrier at the head of the queue30         // 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.next44         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中的消息。