Android消息机制
一、基本使用
a) 概述:一个Android应用程序被创建的时候都会创建一个UI主线程,但是有时我们会有一些比较耗时的操作,为了防止阻塞UI主线程,我们会将耗时的操作放到子线程中进行处理,处理完之后操作UI,但是Android不允许子线程操作UI,违背了Android单线程模型的原则(即Android UI操作并不是线程安全的并且这些操作必须在UI线程中执行),所有Android通过Handler消息机制来实现线程之间的通讯。在项目实际开发中我们使用Handler消息机制最频繁的莫过于子线程中发送消息在主线程中进行View的更新了。
b) Handler机制主要角色
i. Message:消息,其中包含了消息ID,消息处理对象以及处理的数据等,有MessageQueue统一队列,最终由Handler处理。
ii. Handler:处理者,负责Message的发送及处理。使用Handler时,需要实现handleMessage(Message msg)方法来对特定的Message进行处理,例如更新UI等。
iii. MessageQueue:消息队列,用来存放Handler发送过来的消息,并按照FIFO规则执行。当然,存放Message并非实际意义的保存,而是将Message以链表的方式串联起来的,等待Looper的抽取。
iv. Looper:消息泵,不断地从MessageQueue中抽取Message执行。因此,一个MessageQueue需要一个Looper。
v. Thread:线程,负责调度整个消息循环,即消息循环的执行场所。
c) Handler机制主要运用
i. 发送消息,在不同的线程间发送消息,使用方法为sendXXX();
ii. 计划任务,在未来执行某任务,使用的方法为postXXX();
d) Handler机制扩展
i. Activity.runOnUiThread(Runnable)
ii. View.post(Runnable)
iii. 使用AsyncTask代替Thread
iv. Handler的post方法运行在handler依附的线程中,而View的post方法,则一定运行在主线程中。
二、原理探究
a) Message对象:Message在整个Android的消息机制中充当着消息体的角色,它用来包装存储我们的数据在消息之间进行传递。Message实现了Parcelable接口,它有一个无参构造函数,但系统并不建议我们使用它来创建Message对象,而是建议我们使用obtain()这个静态方法来创建Message对象。在obtain()方法中先判断sPool了Message对象是否为空,如果不为空,就将这个Message对象进行复用,同时对这个Message的标识信息进行初始化,去除标记,然后消息池size减1,如果为null,则通过构造函数创建一个新的Message对象。通过对比构造函数,我们发现通过obtain()方法进行创建对象,提高了效率,减少了不必要的Message对象的创建。而且在obtain()这个方法中,我们发现Message与Handler的绑定时通过target存储Handler的引用。若一个消息体完成它的使命后,它就会被销毁回收,所以Message通过recycle()方法进行回收销毁。在Message进行回收的时候,就是在recycle()方法中,会判断该Message是否正在使用,若它不处于正在使用的时候,系统通过recycleUnchecked()方法进行回收。回收的工作就是清除一个Message对象的细节属性,置空初始化。
b) MessageQueue对象:消息队列,在消息机制中充当仓库的作用,用于存储Message对象。它持有的Message对象通过Looper进行分发,同时Message对象不是直接通过MessageQueue对象方法进行添加,而是通过Handler进行添加。MessageQueue的enqueueMessage()用来添加Message,removeMessages用来删除指定Message,Message next()用来遍历Message对象。
i. boolean EnqueueMessage():用于添加我们的Message,其整体流程是:首先判断Message对象的target是否为null(所附属的Handler,说白了就是发送源是否为null),如果为null,抛出异常“Message must have a target”。接着判断该Message是否正在使用,如果是,抛出异常“ This message is already in use.”。当上面条件不符合,则该Message是合法的,才开始添加到MessageQueue中。接着判断mQuitting是否为true,如果为true,抛出异常“sending message to a Handler on a dead thread”,说明依附的Hanlder所在的线程已死。所有的检查都不符合,则开始进行添加,首先通过markInUse()方法设置Message的flags |= FLAG_IN_USE;接着设置when参数,最后插入到链表中。下面我们就开始分析这个Message的插入流程顺序:
ii. Void removeMessages():用于删除我们指定的Message。主体流程就是根据链表节点遍历Message对象,然后比较我们指定的what、object,如果相同则删除,再继续遍历下去,直到结束。
iii. Message next():同样根据链表节点进行MessageQueue的遍历,返回msg。
c) Looper对象:我想把Looper对象比作“电机”、“发动机”一点不为过。我们知道Thread默认情况下是不具备消息循环的。为了使它们具备,我们需要在线程中调用prepare()方法,同时通过loop()方法获取Message。
i. prepare():该方法用于将我们的线程转换为loop线程。它给当前Thread设置Looper对象,同时也可知道每个Thread只能有个Looper对象,其核心就是将线程依附的Looper对象转换为ThreadLocal类型;
ii. Loop():在此方法里,进行Message的遍历获取。首先通过myLooper()方法获取Looper对象,如果Looper对象不为null,则获取该Looper对象中的MessageQueue对象,进行遍历获取Message对象,同时将取出的Message对象进行回收消失。这里我们着重注意msg.target.dispatchMessage(msg);这个方法就是调用我们的Handler的dispatchMessage方法,然后进行消息的处理。dispatchMessage这个方法中首先判断msg.callback是否为null,这是针对我们使用post发送消息处理的。然后在进行mCallback是否为空,这是针对我们的sendxxMessage进行处理的。
iii.
iv. myLooper():获取Looper对象
v. myQueue():获取Looper对象中的MessageQueue。
小结:
1. Looper中包含一个MessageQueue用于存储我们的Message消息队列。
2. 每个线程只能有一个Looper对象,且该Looper对象为ThreadLocal类型
3. 通过Looper.prepare()方法可以将线程变成Looper线程。
d) Handle对象:Handler里面包含一个Looper对象、MessageQueue对象。
i. sendXXXXMessage():用于发送Message消息队列,它们最后都是调用了sendMessageAtTime()方法:该方法将消息插入队列,同时在enqueueMessage方法中进行Handler与msg的绑定。我们发送的消息,需要在handleMessage方法中进行处理,这是一个callback接口中的空方法,子类需要重写它进行业务逻辑的处理。构造函数中callback接口封装了我们的handleMessage,用于我们的消息回调。
ii. postXXXX():用于发送Runable的线程处理,在post方法中,同样是调用sendMessageXX系列,同时通过getPostMessage()方法获取一个Message对应,该Meesage的callback接口绑定到Runnable上。后面就是调用Looper中的dispatchMessage()方法,执行Runnable中的内容。
总结:1、首先创建Message对象,Message对象用Hanlder target字段存储Hanlder,实现该Message与发送源Handler的关系绑定,因为一个Handler可以发送多个消息,同样一个Message也可以被多个Handler发送,确保最后调用正确的Handler来处理该消息的回调。用Runnable callback存储Runnable。
2、创建Handler时,会指定Handler内部的Looper成员变量,同时制定成员变量mQueue = looper.mQueue;
3、Handler通过sendXXMessage()方法发送消息,该系列方法的本质是调用sendMessageAtTime方法,该方法显示判断MessageQueue是否为null,然后调用enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis)实现Message与Handler的绑定(msg.target = this),在调用MessageQueue的插入队列方法,形成MessageQueue消息组。该消息组就是Looper内部的mQueue。
4、通过第3步,Looper内部的MessageQueue就建立了,Looper内部通过loop()方法进行遍历MessageQueue,通过msg.target.dispatchMessage(msg);调用Handler的分发事件实现消息回调的处理。
异步任务AsyncTask
1.AsyncTask的常规使用
a) 简介:AsyncTask是一种轻量级的异步任务类,它可以在线程池中执行后台任务,然后会把执行的进度和最终结果传递给主线程并更新UI。AsyncTask本身是一个抽象类,它提供了Params、Progress、Result三个参数:
i. Params:启动任务执行的输入参数
ii. Progress:后台任务执行的百分比
iii. Result:后台执行任务最终返回的结果类型
b) 相关API
i. onPreExecute(),该方法在主线程中执行,将在execute(Params...params)被调用后执行,一般用来做一些UI的准备工作,如在界面上显示一个进度条。
ii. doInBackground(Params..params),抽象方法,该方法必须实现,该方法在线程池中执行,用于执行异步任务,将在onPreExecute()方法执行后执行。其参数是一个可变类型,表示异步任务的输入参数。在该方法中还可以通过publishProgress(Progress...values)来更新实时的任务进度,而publishProgress方法则会调用onProgressUpdate方法。此外doInBackground方法会将计算的返回结果传递给onPostExecute方法。
iii. onProgressUpdate(),在主线程中执行,该方法在publishProgress()方法被调用后执行,一般用于更新UI进度,如更新进度条的当前进度。
iv. onPostExecute(Result),在主线程中执行。在doInBackground执行完成后,onPostExecute方法将被UI线程调用,doInBackground方法的返回值将作为此方法的参数传递到UI线程中,并执行一些UI相关的操作,如更新UI视图。
v. onCancelled(),在主线程中执行,当异步任务被取消时,该方法将被调用,需注意该方法被调用后,onPostExecute将不会被执行。
vi. execute(Params):启动异步任务,该方法是一个final方法,参数类型是可变类型,实际上这里传递的参数和doInBackground(Params paras)方法中的参数是一样的,该方法最终返回一个AsyncTask实例的对象,可以使用该对象进行其他操作,比如结束线程之类。
c) 使用AsyncTask要遵守的规则
i. AsyncTask的实例必须在主线程(UI线程)中创建,execute方法也必须在主线程调用
ii. 不要在程序中直接的调用onPreExecute(),onPostExecute(),doInBackground(Params..)--方法
iii. 不要在doInBackground()中更新UI
iv. 一个Async对象只能被执行一次,也就是只能调用一次,否则多次调用将会抛出异常。
2.AsyncTask在不同android版本下的差异(线程池的差异)
在android3.0之前,AsyncTask处理任务时默认采用的是线程池里并行处理任务的方式,而在3.0之后,为了避免AsyncTask处理任务所带来的并发错误,AsyncTask则采用了单线程串行执行任务。但是这不意思着android3.0之后只能执行串行任务,我们仍然可以采用AsyncTask的executeOnExecutor()方法来并行执行任务。在Android 3.0之后我们可以通过下面代码让AsyncTask执行并行任务,其AsyncTask.THREAD_POOL_EXECUTOR为AsyncTask的内部线程池。
3.AsyncTask在使用中的一个特殊情况
我们在使用AsyncTask的时候,一般会在onPreExecute()和onPostExecute()进行UI的更新,当我们一个Activity正在使用AsyncTask进行文件的下载时,如果屏幕发生了旋转,Activity会进行re-onCreate,又会创建一个AsyncTask进行文件的下载,我们需要在onPause中进去取消cancel,将该任务设为取消状态,然后在doInBackground()方法中得到此状态来停止此方法的执行。这样仅仅是解决了发生等待的情况,因为Activity再次进入onCreate()方法,还是会进行文件的下载,为了解决整个问题,一种方案是通过判断onCreate()方法中的参数是否为null,只有为null时采取创建新的AsyncTask;
4.AsyncTask和Handler的比较
a) AsyncTask
i. 优点:AsyncTask是一个轻量级的异步任务处理类,轻量级体现在,使用方便,代码简洁上,而且整个异步任务的过程可以通过cancle()进行控制。
ii. 缺点:不适用于处理长时间的异步任务,一般整个异步任务的过程最好控制在几秒以内,如果是长时间的异步任务就需要考虑多线程的控制问题;当处理多个异步任务时,UI更新变得困难。
b) Handler
i. 优点:代码结构清晰,容易处理多个异步任务
ii. 缺点,当有多个异步任务,由于要配合Thread或Runnable,代码比较冗余
c) 总之,异步任务是一个很好用的异步任务处理类,只要不是频繁对大量UI进行更新,可以考虑使用;而Handler在处理大量UI更新的时候可以考虑使用。
5.AsyncTask原理分析(AsyncTask内部封装了异步任务队列和Handler)
AsyncTask主要是对异步任务和handler的封装,首先我们需要知道,在处理异步任务时,AsyncTask内部使用了两个线程池,一个线程池sDefalutExecutor是用来处理用户提交(执行AsyncTask的execute时)过来的异步任务,这个线程池中有一个Runnable异步任务队列ArrayDequemTasks,把提交过来的异步任务放到这个队列中;另一个线程池THREAD_POOL_EXECUTOR,用来真正执行异步任务。
具体来说,当我们调用execute()方法后,其内部直接调用executeOnExecutor方法,在sDefalutExecutor线程池中,其将提交过来的异步任务放到Runnable异步任务队列ArrayDequemTasks中,接着调用onPreExecute()方法,执行异步任务的WorkerRunnable对象(实质为callable对象)最终被封装成FutureTask实例,FutureTask实例将在线程池sDefalutExecutor去执行,这个过程中doInBackground()将被调用(在WorkerRunnable对象的call方法中调用),如果我们重写的doInBackground()方法中调用了publishProgress()方法,则通过自定义的InternalHandler实例sHandler发送一条MESSAGE_POST_PROGRESS消息,更新进度,sHandler处理消息时onProgressUpdate()方法将被调用;最后如果FutureTask任务执行成功并返回结果,则通过postResule方法发送一条MESSAGE_POST_RESULT的消息去执行AsyncTask的finish方法,在finish方法内部onPostExecute()方法被调用,这样就在onPostExecute方法中可以更新UI或者释放资源了。
AsyncTask执行的流程图解: