Handler机制用法概述

来源:互联网 发布:大闹天空坐骑进阶数据 编辑:程序博客网 时间:2024/06/05 10:40

大前提:

Android 中UI线程模型:

A:不能在UI线程中做耗时操作,即不要阻塞UI线程超过5秒,否则有出现ANR(Application not responsing)错误的危险.

B:不能在非UI线程中直接更新UI

一、Handler消息传递机制初步认识:

(一)、引入:

子线程没有办法对UI界面上的内容进行操作,如果操作,将抛出异常:CalledFromWrongThreadException
为了实现子线程中操作UI界面,Android中引入了Handler消息传递机制,目的是打破对主线程的依赖性。

什么是Handler?
handler通俗一点讲就是用来在各个线程之间发送数据的处理对象。在任何线程中,只要获得了另一个线程的handler,则可以通过 handler.sendMessage(message)方法向那个线程发送数据。基于这个机制,我们在处理多线程的时候可以新建一个thread,这个thread拥有UI线程中的一个handler。当thread处理完一些耗时的操作后通过传递过来的handler向UI线程发送数据,由UI线程去更新界面。
主线程:运行所有UI组件,它通过一个消息队列来完成此任务。设备会将用户的每项操作转换为消息,并将它们放入正在运行的消息队列中。主线程位于一个循环中,并处理每条消息。如果任何一个消息用时超过5秒,Android将抛出ANR。所以一个任务用时超过5秒,应该在一个独立线程中完成它,或者延迟处理它,当主线程空闲下来再返回来处理它。

(二)、常用类:(Handler、Looper、Message、MessageQueue)

Message:消息,其中包含了消息ID,消息处理对象以及处理的数据等,由MessageQueue统一列队,终由Handler处理。

Handler:处理者,负责Message的发送及处理。使用Handler时,需要实现handleMessage(Message msg)方法来对特定的Message进行处理,例如更新UI等。

Handler类的主要作用:(有两个主要作用)1)、在工作线程中发送消息;2)、在主线程中获取、并处理消息。

MessageQueue:消息队列,用来存放Handler发送过来的消息,并按照FIFO规则执行。当然,存放Message并非实际意义的保存,而是将Message串联起来的,等待Looper的抽取。

Looper:消息泵,不断地从MessageQueue中抽取Message执行。因此,一个MessageQueue需要一个Looper。

Thread:线程,负责调度整个消息循环,即消息循环的执行场所。

(三)、Handler、Looper、Message、MessageQueue之间的关系:

这里写图片描述
Looper和MessageQueue一一对应,创建一个Looper的同时,会创建一个MessageQueue;
而Handler与它们的关系,只是简单的聚集关系,即Handler里会引用当前线程里的特定Looper和MessageQueue;
在一个线程中,只能有一个Looper和MessageQueue,但是可以有多个Handler,而且这些Handler可以共享一个Looper和MessageQueue;
Message被存放在 MessageQueue中,一个 MessageQueue中可以包含多个Message对象。
【备注:】
Looper对象用来为一个线程开启一个消息循环,从而操作MessageQueue;
默认情况下,Android创建的线程没有开启消息循环Looper,但是主线程例外。
系统自动为主线程创建Looper对象,开启消息循环;
所以主线程中使用new来创建Handler对象。而子线程中不能直接new来创建Handler对象就会异常。
子线程中创建Handler对象,步骤如下:

Looper.prepare();Handler handler = new Handler() {    //handlemessage(){}}Looper.loop();

(四)、Handler类中常用方法:

handleMessage() 用在主线程中,构造Handler对象时,重写handleMessage()方法。该方法根据工作线程返回的消息标识,来分别执行不同的操作。
sendEmptyMessage() 用在工作线程中,发送空消息。
sendMessage() 用在工作线程中,立即发送消息。

(五)、Message消息类中常用属性:

arg1 用来存放整型数据
arg2 用来存放整型数据
obj 用来存放Object数据
what 用于指定用户自定义的消息代码,这样便于主线程接收后,根据消息代码不同而执行不同的相应操作。

【重点】:使用Message需要注意4点:

1、Message虽然也可以通过new来获取,但是通常使用Message.obtain()或Handler.obtainMessage()方法来从消息池中获得空消息对象,以节省资源;

2、如果一个Message只需要携带简单的int型数据,应优先使用arg1和arg2属性来传递数据,这样比其他方式节省内存;

3、尽可能使用Message.what来标识信息,以便用不同的方式处理Message;

4、如果需要从工作线程返回很多数据信息,可以借助Bundle对象将这些数据集中到一起,然后存放到obj属性中,再返回到主线程。

示例代码MainActivity

public class MainActivity extends AppCompatActivity {    private TextView text_main_info;    private ProgressDialog pDialog;    private ImageView image_main;    private Handler handler;    private String urlString = "https://www.baidu.com/img/bd_logo1.png";    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        //初始化各种控件        initViews();        //开启子线程完成耗时操作(从网络下载图片),下文中子线程即工作线程        loadImgFromIntent();    }    private void initViews() {        text_main_info = (TextView) findViewById(R.id.text_main_info);        pDialog = new ProgressDialog(MainActivity.this);        pDialog.setMessage("Loading...");        image_main = (ImageView) findViewById(R.id.image_main);        // 主线程中的handler对象会处理工作线程中发送的Message。根据Message的不同编号进行相应的操作。        handler = new Handler() {            public void handleMessage(Message msg) {                // 工作线程中要发送的信息全都被放到了Message对象中,也就是上面的参数msg中。要进行操作就要先取出msg中传递的数据。                switch (msg.what) {                    case 0:                        // 工作线程发送what为0的信息代表线程开启了。主线程中相应的显示一个进度对话框                        pDialog.show();                        break;                    case 1:                        // 工作线程发送what为1的信息代表要线程已经将需要的数据加载完毕。本案例中就需要将该数据获取到,显示到指定ImageView控件中即可。                        image_main.setImageBitmap((Bitmap) msg.obj);                        break;                    case 2:                        // 工作线程发送what为2的信息代表工作线程结束。本案例中,主线程只需要将进度对话框取消即可。                        pDialog.dismiss();                        break;                    default:                        break;                }            }        };    }    private void loadImgFromIntent() {        new Thread(new Runnable() {            @Override            public void run() {                // 当工作线程刚开始启动时,希望显示进度对话框,此时让handler发送一个空信息即可。                // 当发送这个信息后,主线程会回调handler对象中的handleMessage()方法。handleMessage()方法中                // 会根据message的what种类来执行不同的操作。                handler.sendEmptyMessage(0);                // 工作线程执行访问网络,加载网络图片的任务。                byte[] data = HttpClientHelper.loadByteFromURL(urlString);                // 工作线程将网络访问获取的字节数组生成Bitmap位图。                Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0,                        data.length);                // 工作线程将要发送给主线程的信息都放到一个Message信息对象中。                // 而Message对象的构建建议使用obtain()方法生成,而不建议用new来生成。                Message msgMessage = Message.obtain();                // 将需要传递到主线程的数据放到Message对象的obj属性中,以便于传递到主线程。                msgMessage.obj = bitmap;                // Message对象的what属性是为了区别信息种类,而方便主线程中根据这些类别做相应的操作。                msgMessage.what = 1;                // handler对象携带着Message中的数据返回到主线程                handler.sendMessage(msgMessage);                // handler再发出一个空信息,目的是告诉主线程工作线程的任务执行完毕。一般主线程会接收到这个消息后,                // 将进度对话框关闭                handler.sendEmptyMessage(2);            }        }).start();    }}

二、Handler、Looper源码分析:

(一)、Handler的概念:

Handler是用于发送和处理消息和一个线程的MessageQueue相关联的Runable对象。
每个Handler实例关联到一个单一线程和线程的messagequeue。
当您创建一个Handler,从你创建它的时候开始,它就绑定到创建它的线程以及对应的消息队列,handler将发送消息到消息队列,并处理从消息队列中取出的消息。

Handler的主要用途有两个:
(1)、在将来的某个时刻执行消息或一个runnable;
(2)、为运行在不同线程中的多个任务排队。

主要依靠以下方法来完成消息调度:

post(Runnable)postAtTime(Runnable, long)postDelayed(Runnable, long)sendEmptyMessage(int)sendMessage(Message)sendMessageAtTime(Message)sendMessageDelayed(Message, long) 

【备注:】
post方法是当到Runable对象到达就被插入到消息队列;
sendMessage方法允许你把一个包含有信息的Message插入消息队列,它会在Handler的handlerMessage(Message)方法中执行(该方法要求在Handler的子类中实现)。
当Handler post或者send消息的时候,可以在消息队列准备好的时候立刻执行,或者指定一个延迟处理或绝对时间对它进行处理,后两个是实现了timeout、ticks或者其他timing-based的行为。
当你的应用创建一个进程时,其主线程(UI线程)会运行一个消息队列,负责管理优先级最高的应用程序对象(Activity、广播接收器等)和任何他们创建的windows。你也可以创建自己的线程,通过handler与主线程进行通信,在新创建的线程中handler通过调用post或sendMessage方法,将传入的Runnable或者Message插入到消息队列中,并且在适当的时候得到处理。

(二)、Handler的用法:

当你实例化一个Handler的时候可以使用Callback接口来避免写自定义的Handler子类。这里的机制类似与Thread与runable接口的关系。
在Handler里面,子类要处理消息的话必须重写handleMessage()这个方法,因为在handler里面它是个空方法:

(三)、源码分析:

A、Handler.java:(3个属性,9个方法)

3个属性:final MessageQueue mQueue;final Looper mLooper;final Callback mCallback;
9个方法:public boolean handleMessage(Message msg); public final Message obtainMessage()public final boolean sendMessage(Message msg)public final boolean sendEmptyMessage(int what)public final boolean post(Runnable r)public final boolean postAtTime(Runnable r, long uptimeMillis)public void dispatchMessage(Message msg)public boolean sendMessageAtTime(Message msg, long uptimeMillis)public final boolean sendMessageDelayed(Message msg, long delayMillis)

B、Looper.JAVA:(4个属性,4个方法)

每个ThreadLocal中只能有一个Looper,也就是说一个Thread中只有一个Looper

4个属性:   static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();    final MessageQueue  mQueue;    final Thread  mThread;    private static Looper mMainLooper = null;
4个方法:public static void prepare()public static void prepareMainLooper()public static void loop()public static Looper myLooper()

C、Message.java:(8个属性,5个方法)

8个属性:public int what;public int arg1;public int arg2;public Object obj;Handler target; Message sPool;int sPoolSize;int MAX_POOL_SIZE=10;
5个方法:public static Message obtain()public void recycle()public void setTarget(Handler target)public Handler getTarget()public void sendToTarget()
1 0
原创粉丝点击