Android<我所理解的Handler机制>

1.为什么需要Handler机制

在我的<线程和进程在Android中的工作方式>中,明确地说明了应用启动时,系统会为应用创建一个名为“主线程”的执行线程。 此线程非常重要,因为它负责将事件分派给相应的用户界面小部件,其中包括绘图事件。 此外,它也是应用与 Android UI 工具包组件(来自 android.widget 和 android.view 软件包的组件)进行交互的线程。所以我们所有的涉及UI的操作都必须在这个线程执行,这是Android的一套机制。
最根本的原因:
是解决多线程并发问题。如果在一个activity中有多个线程去更新UI,并且没有加锁机制,那样会造成更新界面错乱。 也不能对所有更新UI的操作进行加锁,否则会导致性能下降 。

2.Handler机制原理

a).问题:

如果应用启动的时候UI线程和工作线程同时启动了起来,当工作线程执行最后需要通知更新UI以便告诉用户工作线程执行完毕了。但是Android中并不支持在工作线程(非UI线程)中去更新UI啊,怎么办?

比如现在我们有一个按钮,按钮的监听事件中我们开启了一个新的工作线程去处理我们的业务,处理完毕之后,我们需要改变按钮上的文字以便通知用户工作线程需要做的事情已经完毕:

public class MainActivity extends AppCompatActivity {
    private Button button;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        button = (Button) findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //开启了新的线程
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        //这里是其他的代码


                        //最后更新UI
                        button.setText("处理完毕");
                    }
                }).start();
            }
        });
    }
}

我们需要的是这个效果:
这里写图片描述
显然我们这监听事件是会抛出异常的:
这里写图片描述
b).Handler登场
Handler的出现就是为了解决问题a)的。那么它是如何解决的呢。。。。。
Handler会在UI线程中实例化一个对象,然后在工作线程中使用这个引用,然后直接通过这个引用来发送消息,然后UI线程中的handler接收到这个消息之后会处理消息。

这就好比,一个人在办公司说我内急了,我要上厕所,但是办公司不是厕所啊,所以他得到厕所去。但是解决内急这个事始终是它自己在解决的,只是换了一个场所。

而Handler就是这个人,Message就是上厕所这件事;

解决办法:

package com.example.geekp.mhandler;

import android.os.Handler;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextClock;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {
    private Button button;
    private Handler handler = new Handler();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        button = (Button) findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //开启了新的线程
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        //这里是其他的代码
                        handler.post(new Runnable() {
                            @Override
                            public void run() {
                                button.setText("处理完毕");
                            }
                        });
                    }
                }).start();
            }
        });
    }
}

3.Handler机制是如何实现的

这里写图片描述

a).部分源码
我们打开Handler源码,

    final Looper mLooper;
    final MessageQueue mQueue;

看到有一个Looper和一个MessageQueue引用,这就是Handler机制的两个核心成员变量了。
i.Looper

Looper是用来使一个线程变成循环工作的线程的,具体他是怎么在Handler机制中体现的,在这里就不细说了,看一个Looper源码中的例子吧:

  *  class LooperThread extends Thread {
  *      public Handler mHandler;
  *
  *      public void run() {
  *          Looper.prepare();
  *
  *          mHandler = new Handler() {
  *              public void handleMessage(Message msg) {
  *                  // process incoming messages here
  *              }
  *          };
  *
  *          Looper.loop();
  *      }
  *  }

ii.MessageQueue 是一个消息队列

这是一个可以装载有消息的队列,所谓队列就是遵循 “先进先出“顺序的数据结构

iii.Handler、Looper和MessageQueue的关系
这里还是以更细UI的目的来说明问题:

在系统创建UI线程的时候,开启了工作线程 ,在UI线程中实例化了一个Handler对象,在工作线程中对它进行引用,

4.一个线程只能有一个Looper

打开Looper类的源码,可以看到:
这里写图片描述
这里很明确地指出了一个线程只能创建一个Looper。很容易理解,如果一个线程能够创建多个Looper的话,那就意味着一个线程可以创建多个消息循环队列。想一想这会造成什么后果?
如果有多个消息循环队列的话,那么Handler又怎么稳准狠地从队列中取出消息。
那么Android是如何保证只能创建一个Looper的:

这主要归功于ThreadLocal.
看看JDK中对ThreadLocal的解释:
这里写图片描述
举一个例子,假入我们有三个线程同时操作了一个变量,但是我们又要保证各个线程之间的操作互不影响,我们可以这样使用ThreadLocal来实现:

package jdk.lang;

public class MThreadLocal {
    // 通过匿名内部类覆盖ThreadLocal的initialValue()方法,指定初始值

    private static ThreadLocal<Integer> seqNum = new ThreadLocal<Integer>() {
        public Integer initialValue() {
            return 0;
        }
    };
    // 获取下一个序列值
    public int getNextNum() {
        seqNum.set(seqNum.get() + 1);
        return seqNum.get();
    }

    public static void main(String[] args) {
        MThreadLocal sn = new MThreadLocal();
        //  3个线程共享sn,各自产生序列号
        TestClient t1 = new TestClient(sn);
        TestClient t2 = new TestClient(sn);
        TestClient t3 = new TestClient(sn);
        t1.start();
        t2.start();
        t3.start();
    }

    private static class TestClient extends Thread {
        private MThreadLocal sn;

        public TestClient(MThreadLocal sn) {
            this.sn = sn;
        }

        public void run() {
            // 每个线程打出5个序列值
            for (int i = 0; i < 5; i++) {
                System.out.println(Thread.currentThread().getName() + ": " + sn.getNextNum());
            }
        }
    }
}

这时候控制台输出:
这里写图片描述
我们可以看到控制台分别依次输出了各个线程的”局部变量“的值(1-5)。
然后我们再看看Looper中的代码:

    // sThreadLocal.get() will return null unless you've called prepare().
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

    private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }

可以看到在Looper的类中,使用了ThreadLocal以保证每个线程只能创建一个Looper对象。

5.Handler的几种常见的用法

a).boolean post (Runnable r)
示例代码:
在主线程中实例化一个Handler对象,然后工作线程中调用post函数,参数是一个Runnable 对象,可以在Runnable 对象的run()方法中写你的要进行的操作。

public class MainActivity extends AppCompatActivity {
    private Button button;
    private Handler handler = new Handler();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        button = (Button) findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //开启了新的线程
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        //这里是其他的代码
                        handler.post(new Runnable() {
                            @Override
                            public void run() {
                                button.setText("处理完毕");
                            }
                        });
                    }
                }).start();
            }
        });
    }
}

b).boolean postDelayed (Runnable r, long delayMillis)
这个方法和方法a)差不多,只不过这个方法推迟了Runnable 对象的run()函数中的执行,推迟的时间为第二个参数long delayMillis,如下面推迟了5秒:

 handler.postDelayed(new Runnable() {
                            @Override
                            public void run() {
                                button.setText("处理完毕");
                            }
                        }, 5000);

c).boolean sendMessage (Message msg)

当有很多个工作线程需要同时更新UI的时候,这个方法很有用。

下面来看一个例子,activity中有两个按钮,两个按钮被点击之后都将触发监听事件给UI线程发送消息。UI线程可以根据Message对象在工作线程传递过来之前的信息来区分到底是哪个线程发送过来的消息,使用Message.what属性。

public class MainActivity extends AppCompatActivity {
    private Button button1;
    private Button button2;

    private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case 1:
                    Toast.makeText(getApplicationContext(), "UI线程收到了工作线程一的消息", Toast.LENGTH_SHORT).show();
                    break;
                case 2:
                    Toast.makeText(getApplicationContext(), "UI线程收到了工作线程二的消息", Toast.LENGTH_SHORT).show();
                    break;
                default:
                    break;
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        button1 = (Button) findViewById(R.id.button1);
        button2 = (Button) findViewById(R.id.button2);
        button1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //开启了新的线程一
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        //这里是其他的代码
                        //创建消息
                        Message message = new Message();
                        message.what = 1;
                        message.obj = "这是线程一的消息";
//                        发送到消息循环队列
                        handler.sendMessage(message);
                    }
                }).start();
            }
        });


        button2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Message message = handler.obtainMessage();
                message.what = 2;
                message.obj = "这是线程二的消息";
//                handler.sendMessage(message);
                message.sendToTarget();
            }
        });
    }
}

看下效果:
这里写图片描述

这里需要提的是,我再两个按钮的监听事件中使用了两种不同的Message写法:

方法一:
Message message = new Message();
message.what = 1;
message.obj = “这是线程一的消息”;
// 发送到消息循环队列
handler.sendMessage(message);

方法二:
Message message = handler.obtainMessage();
message.what = 2;
message.obj = “这是线程二的消息”;
// handler.sendMessage(message);
message.sendToTarget();

注意:使用方法一的时候发送消息只能够使用handler.sendMessage(message);,但是在第二种方法却可以使用handler.sendMessage(message);或者message.sendToTarget();

d).boolean sendMessageDelayed (Message msg,
long delayMillis)
这个方法和c)中的方法是差不多的,唯一不同的就是将方法一的操作延迟执行,延迟时间为第二个参数long delayMillis

  • 11
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值