Handler全面总结

1.Handler的定义:

Handler,即“处理者”,用来发送和接受信息并且按照信息的匹配来实现执行功能代码。

Handler接收子线程发送的数据,并用此数据配合主线程更新UI,跟UI主线程交互用。

由于Handler运行在主线程中(UI线程中),与子线程可以通过Message对象来传递数据。

Handler承担着接受子线程传过来的(子线程用sedMessage()方法传递)Message对象(里面包含数据),把这些消息放入主线程队列中,配合主线程进行更新UI。

(除了用Handler完成界面更新外,还可以使用runOnUiThread()来更新,甚至更高级的事务总线。)

Handler可以分发Message对象和Runnable对象到主线程中,每个Handler实例都会绑定到创建它的线程中(一般是位于主线程)

Handler 作用:

  1. 安排Message在某个主线程中某个地方执行;
  2. 安排Runnable在某个主线程中某个地方执行;
  3. 安排一个动作在不同的线程中执行。

API:Handler
本文大部分内容来自于 http://blog.csdn.net/coder_pig/article/details/46997945 的整理


2.Handler类的引入:

Handler类的引入

3.Handler的执行流程图:

Handler的执行流程图

相关名词

  • UI线程:主线程,系统在创建UI线程的时候会初始化一个Looper对象,同时也会创建一个与其关联的MessageQueue;
  • MessageQueue:消息队列,先进先出管理Message,在初始化Looper对象时会创建一个与之关联的MessageQueue;
  • Message:Handler接收与处理的消息对象;
  • Handler:作用就是发送与处理信息,如果希望Handler正常工作,在当前线程中要有一个Looper对象
  • Looper每个线程只能够有一个Looper,管理MessageQueue,不断地从中取出Message分发给对应的Handler处理

Looper 类用来为一个线程跑一个消息循环。
线程在默认情况下是没有消息循环与之关联的,Thread类在run()方法中的内容执行完之后就退出了,即线程做完自己的工作之后就结束了,没有循环的概念。
调用Looper类的 prepare() 方法可以为当前线程创建一个消息循环,调用 loop() 方法使之处理信息,直到循环结束。

大多数和消息循环的交互是通过 Handler 类进行的Handler之所以能够实现对view的处理也是因为使用了Looper。

执行流程:

  1. 子线程想修改Activity中的UI组件时,新建一个Handler对象,通过这个对象向主线程发送信息;
  2. Handler给发送的信息msg的target赋值为handler自身,msg会先到主线程的MessageQueue进行等待;
  3. 由Looper按先入先出顺序取出,再根据message对象的what属性分发给对应的Handler进行处理,回调相应方法。
Looper , Handler , Message 三者关系

Android 异步消息处理机制 让你深入理解 Looper、Handler、Message三者关系

Handler不仅可以更新UI,你完全可以在一个子线程中去创建一个Handler,然后使用这个handler实例在任何其他线程中发送消息,最终处理消息的代码都会在你创建Handler实例的线程中运行。


4.Handler的相关方法:

  • void handleMessage(Message msg):处理消息的方法,通常是用于被重写
  • sendEmptyMessage(int what):发送空消息
  • sendEmptyMessageDelayed(int what,long delayMillis):指定延时多少毫秒后发送空信息
  • sendMessage(Message msg):立即发送信息
  • sendMessageDelayed(Message msg, long delayMillis ):指定延时多少毫秒后发送信息
  • final boolean hasMessage(int what):
    • 如果是参数为 (int what),检查消息队列中是否包含what属性为指定值的消息
    • 如果是参数为(int what,Object object):除了判断what属性,还需要判断Object属性是否为指定对象的消息
  • boolean post(Runnable r): 使Runnable r添加到消息队列
  • boolean postAtTime(Runnable r, long uptimeMillis): 在设定的时间再将Runnable r添加到消息队列
  • boolean postDelayed(Runnable r, long delayMillis):指定延时多少毫秒后将Runnable r添加到消息队列

5.Handler的使用示例:

1)Handler写在主线程中:传递message

在主线程中,因为系统已经初始化了一个Looper对象,所以直接创建Handler对象,就可以进行信息的发送与处理了。

  1. 在外面重写Handler以及内部的handleMessag()方法;
  2. 在特定时刻发出消息:myHandler.sendMessage(msg);
  3. 根据重写的handleMessage()方法中的定义,在收到对应的message时进行对应的操作;

代码示例:
SplashActivity作为闪屏界面,是程序进入的第一个界面,根据存储的accessToken在此进行判断,下一步是跳转到主界面还是登录界面。

public class SplashActivity extends BaseActivity {
    private static final int WHAT_INTENT2LOGIN = 1;
    private static final int WHAT_INTENT2MAIN = 2;
    private static final long SPLASH_DUR_TIME = 1000;
    private Oauth2AccessToken accessToken;
    private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            
            switch (msg.what) {
            case WHAT_INTENT2LOGIN:
                intent2Activity(LoginActivity.class);
                finish();
                break;
            case WHAT_INTENT2MAIN:
                intent2Activity(MainActivity.class);
                finish();
                break;
            default:
                break;
            }
        }
    };
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_splash);
        accessToken = AccessTokenKeeper.readAccessToken(this);
        
        //判断Token是否有效
        if(accessToken.isSessionValid()) {
            handler.sendEmptyMessageDelayed(WHAT_INTENT2MAIN, SPLASH_DUR_TIME);
        } else {
            handler.sendEmptyMessageDelayed(WHAT_INTENT2LOGIN, SPLASH_DUR_TIME);
        }
    }
}

2)Handler写在主线程中:传递Runnable

通过handler的延时发送message,可以延时处理一些事务的处理。
比如设计某一个应用的启动时有一个欢迎界面,让这个界面停留几秒后再跳转到里一个Activity主页

public class SplashActivity extends Activity{
     private Intent intent;
     @Override
     protected void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          setContentView(R.layout.splash);
          startMainAvtivity();
     }
    
     private void startMainAvtivity(){
          new Handler().postDelayed(new Runnable(){
               @Override
               public void run() {
                    intent = new Intent(SplashActivity.this,MainActivity.class);
                    startActivity(intent);
                    SplashActivity.this.finish();
               }
          }, 3000);
      }
}

上述Handler().postDelayed()代码的利用Handler来延迟启动另一个Activity。

值得注意的是:认为上面的代码在主线程里面启动了新线程,然后在新线程里面跳转了Activity,是错误的。

只要记住:java里面启动新线程有两种方法

  • 一种是利用一个类去继承Thread,然后实例化后执行start()方法;
  • 另一种是实现Runable接口,把Runnable对象作为参数去实例化一个Thread对象再执行start()方法。

3)Handler写在子线程中

如果是Handler写在了子线程中的话,我们就需要自己创建一个Looper对象

  1. 直接调用Looper.prepare()方法即可为当前线程创建Looper对象,而它的构造器会创建配套的MessageQueue;
  2. 创建Handler对象,重写handleMessage( )方法就可以处理来自于其他线程的信息了!
  3. 调用Looper.loop()方法启动Looper

使用示例:输入一个数,计算后通过Toast输出在这个范围内的所有质数
实现代码:

<LinearLayout 
    xmlns:android="http://schemas.android.com/apk/res/android" 
    android:layout_width="match_parent" 
    android:layout_height="match_parent" 
    android:orientation="vertical"> 
    <EditText 
        android:id="@+id/etNum" 
        android:inputType="number" 
        android:layout_width="match_parent" 
        android:layout_height="wrap_content" 
        android:hint="请输入上限"/> 
    <Button 
        android:layout_width="match_parent" 
        android:layout_height="wrap_content" 
        android:onClick="cal" 
        android:text="计算"/>   
</LinearLayout>
public class CalPrime extends Activity{
    static final String UPPER_NUM = "upper"; 
    EditText etNum; 
    CalThread calThread; 

    // 定义一个线程类 
     class CalThread extends Thread {
        public Handler mHandler; 
        public void run(){
            //创建一个Looper对象,而它的构造器会创建配套的MessageQueue
            Looper.prepare(); 
            mHandler = new Handler(){
                // 定义处理消息的方法 
                @Override 
                public void handleMessage(Message msg){
                    if(msg.what == 0x123){
                        int upper = msg.getData().getInt(UPPER_NUM); 
                        List<Integer> nums = new ArrayList<Integer>(); 
                        // 计算从2开始、到upper的所有质数 

                        //" outer:"就是一个自己定义的标签名,通过break或continue来跳转到这个位置,一般是用来跳出循环。
                        //这个语法类似于C语言中goto。
                        outer:
                        for (int i = 2 ; i <= upper ; i++){
                            // 用i处于从2开始、到i的平方根的所有数
                            for (int j = 2 ; j <= Math.sqrt(i) ; j++){
                                 // 如果可以整除,表明这个数不是质数 
                                if(i != 2 && i % j == 0){ 
                                    continue outer; 
                                } 
                            } 
                            nums.add(i); 
                        } 
                        // 使用Toast显示统计出来的所有质数 
                         Toast.makeText(CalPrime.this , nums.toString() 
                            , Toast.LENGTH_LONG).show(); 
                    } 
                } 
            }; 
            //调用Looper.loop()方法启动Looper
            Looper.loop(); 
        } 
    } 

    @Override 
    public void onCreate(Bundle savedInstanceState){ 
        super.onCreate(savedInstanceState); 
        setContentView(R.layout.main); 
        etNum = (EditText)findViewById(R.id.etNum); 
        calThread = new CalThread(); 
        // 启动新线程 
         calThread.start(); 
    } 

    // 为按钮的点击事件提供事件处理函数 
     public void cal(View source){ 
        // 创建消息 
        Message msg = new Message(); 
        msg.what = 0x123; 
        Bundle bundle = new Bundle(); 
        bundle.putInt(UPPER_NUM , 
            Integer.parseInt(etNum.getText().toString())); 
        msg.setData(bundle); 
        // 向新线程中的Handler发送消息 
         calThread.mHandler.sendMessage(msg); 
    } 
}

在这里,Toast能够显示信息,是因为目前并非运行在UI主线程,Toast.show在Looper.prepare和Looper.loop之间,这段代码需要将自己的信息放松到消息队列中才能够被执行,Looper.prepare和Looper.loop的作用就是让Toast消息传入消息队列,起到同在UI线程调用一样的效果。

Handler之所以能够实现对view的处理也是因为使用了Looper


知识补充之Message怎么包含数据:

  • Message对象
    • Message对象携带数据,通常它用arg1,arg2来传递消息,当然它还可以有obj参数,可以携带Bundle数据。系统性能消耗非常少。
    • 初始化: Message msg=handler.obtainMessage();
  • Bundle对象
    • Bundle是Android提供的类,可以把它看做是特殊的Map,即键值对的包。
    • Bundle 特殊在键和值都必须要是基本数据类型或是基本数据类型的数组(Map的键值要求都是对象),特别的,键要求都是String类型。
    • 用Message来携带Bundle数据:
      • 放入:msg.setData(Bundle bundle);
      • 取出:msg.getData();

Bundle bundle = new Bundle();
bundle.putInt("myage", 21);
bundle.putString("myname", "yummylau");
msg.setData(bundler);
msg.sendToTarget();

Bundle myData = msg.getData();
int myage = myData.getInt("myage");
String myname = myData.getString("myname");

知识补充:不能在子线程中 new Handler

在使用Handler时,在子线程中 new Handler 会出现异常,

Can’t create handle inside thread that ha not called Looper.prepared

在没有调用 Looper.prepare()的时候不能创建 Handler;

当我们在主线程中创建Handler对象的时候没有问题,是因为主线程会自动调用Looper.prepare()方法去
给当前主线程创建并设置一个Looper对象,随意在Handler构造函数中从当前线程的对象身上拿到这个Looper。
但是子线程中并不会自动调用这个方法,所以要想在子线程中创建Handler对象就必须在创建之前手动调用Looper.prepare()方法,否则就会报错。

一个介绍比较详尽的文章:子线程中可以使用Handler吗?

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容