AndroidHandler18.docx
《AndroidHandler18.docx》由会员分享,可在线阅读,更多相关《AndroidHandler18.docx(15页珍藏版)》请在冰豆网上搜索。
AndroidHandler18
1.本节主题Handler+Message和多线程
2.知识回顾
2.1开发Android应用时必须遵守单线程模型的原则:
2.1.1.AndroidUI操作并不是线程安全的,并且这些操作必须在UI线程中执行。
2.2单线程模型中始终要记住两条法则:
2.2.1不要阻塞UI线程;
2.2.2确保只在UI线程中访问AndroidUI控件。
2.3UI线程的主要工作
2.3.1界面的刷新显示
2.3.2向各个UI组件分发事件、触发对应的回调处理
2.4两个主要的错误
2.4.1ANR错误
2.4.2Android4.0以上版本中,主线程中不允许访问网络。
涉及到网络操作的程序一般都是需要开一个新线程完成网络访问。
但是在获得页面数据后,又不能将数据返回到UI界面中。
因为子线程(WorkerThread)不能直接访问UI线程中的成员,也就是说没有办法对UI界面上的内容进行操作,如果操作,将抛出异常:
CalledFromWrongThreadException。
2.4.3扩展对比一个错误OOM(内存溢出错误)
2.5android提供了几种在其他线程中访问UI线程的方法:
2.5.1Activity.runOnUiThread(Runnable),了解即可
2.5.2View.post(Runnable) ,了解即可
2.5.3View.postDelayed(Runnable,long),了解即可
2.5.4Timer定时;,理解即可
2.5.5ScheduledExecutorService调度任务;,理解即可
2.5.6AsyncTask异步,只能运行在主线程中;熟练掌握
2.5.7Handler消息传递机制熟练掌握并知其原理
2.6android两种事件类型:
2.6.1基于监听器回调的事件处理机制,以往的监听器
2.6.2基于消息回调的的事件处理机制,本节内容
3.Handler消息传递机制初步认识:
3.1什么是Handler?
handler通俗一点讲就是用来在【各个线程】之间【发送数据】的【处理对象】。
在任何线程中,只要获得了【另一个线程】的handler,则可以通过handler.sendMessage(message)方法向【另一个线程】发送消息数据。
基于这个机制,我们在处理多线程的时候可以新建一个thread,这个thread拥有UI线程中的一个handler。
当thread处理完一些耗时的操作后通过传递过来的handler向UI线程发送数据,由UI线程去更新界面。
主线程:
运行所有UI组件,内部封装了存放message的消息队列(MessageQueue)。
设备会将用户的每项操作转换为消息,并将它们放入正在运行的消息队列中。
主线程位于一个循环中,通过Looper不断查询并处理处理每条消息。
如果任何一个消息用时超过5秒,Android将抛出ANR。
所以一个任务用时超过5秒,应该在一个独立线程中完成它,或者延迟处理它,当主线程空闲下来再返回来处理它。
3.2、常用类:
(Handler、Looper、Message、MessageQueue)
3.2.1Message:
消息
其中包含了消息ID,消息处理对象以及处理的数据等,由MessageQueue统一列队,最终由Handler处理。
3.2.2Handler:
处理者
负责Message的发送及处理。
使用Handler时,需要实现handleMessage(Messagemsg)方法来对特定的Message进行处理,例如更新UI等。
Handler类的主要作用:
(1)、在工作线程中发送消息;
(2)、在主线程中获取、并处理消息。
3.2.3MessageQueue:
消息队列,
用来存放Handler发送过来的消息,并按照FIFO(先进先出)规则执行。
当然,存放Message并非实际意义的保存,而是将Message串联起来的,等待Looper的抽取。
3.2.4Looper:
消息泵,
不断地从MessageQueue中抽取Message执行。
因此,一个MessageQueue需要一个Looper。
3.2.5Thread:
线程
负责调度整个消息循环,即消息循环的执行场所。
3.3、Handler、Looper、Message、MessageQueue之间的关系:
3.3.1Looper和MessageQueue【一一对应】,创建一个Looper的同时,会创建一个MessageQueue;
3.3.2而Handler与它们的关系,只是【简单的聚集关系】,即Handler里会引用当前线程里的特定Looper和MessageQueue;
3.3.3在一个线程中,【只能有一个Looper和MessageQueue】,但是【可以有多个Handler】,而且这些Handler可以【共享】一个Looper和MessageQueue;
3.3.4Message被存放在 MessageQueue的队列中,通过Looper按照FIFO规则存取。
3.4主线程和子线程中异同
2.4.1异同点
Looper对象用来为一个线程开启一个消息循环,从而操作MessageQueue;
默认情况下,Android创建的线程没有开启消息循环Looper,但是主线程例外。
系统自动为主线程创建Looper对象,开启消息循环;所以主线程中使用new来创建Handler对象。
而子线程中不能直接new来创建Handler对象就会异常。
3.4.1子线程中创建Handler对象,步骤如下:
【考点】APILooper
classLooperThreadextendsThread{
privateHandlermHandler;
publicvoidrun(){
//第1步通过Looper的静态函数,
//生成一个Looper对应的MessageQueue消息队列
Looper.prepare();
//第2步生成一个Handler
mHandler=newHandler(){
publicvoidhandleMessage(Messagemsg){
//接着处理线程的其它事务
......
}
};
//第3步启动Looper内部的消息循环处理机制,自动触发mHandler
Looper.loop();
//线程在Looper.loop()处阻塞,因此下面执行不到,需要处理的程序只能在上面的handleMessage内处理
}
//为外部应用提供一个访问mHandler的方法
PublicHandlergetHandlerByRunNew(){
returnmHandler;
}
}
4.Handler和Message中常用方法、属性:
4.1Handler中常用的方法
4.1.1handleMessage(),用在主线程中,构造Handler对象时,重写handleMessage()方法。
该方法根据工作线程返回的消息标识,来分别执行不同的操作。
4.1.2sendEmptyMessage(),用在工作线程中,发送空消息。
4.1.3sendMessage(),用在工作线程中,立即发送消息。
4.2、Message消息类中常用属性:
4.2.1arg1 用来存放整型数据
4.2.2arg2 用来存放整型数据
4.2.3what 用于指定用户自定义的消息代码,这样便于主线程接收后,根据消息代码不同而执行不同的相应操作。
4.2.4obj 需要在消息中携带Object类型的数据时使用。
例如String、Bitmap、自定义的数据实体等。
4.3使用Message需要注意4点:
【重点】
4.3.1、Message虽然也可以通过new来获取,但是通常使用Message.obtain()或Handler.obtainMessage()方法来从消息池中获得空消息对象,以节省资源;
4.3.2、如果一个Message只需要携带简单的int型数据,应优先使用arg1和arg2属性来传递数据,这样比其他方式节省内存;
4.3.3、尽可能使用Message.what来标识信息,以便用不同的方式处理Message;
4.3.4、如果需要从工作线程返回很多数据信息,可以借助Bundle对象将这些数据集中到一起,然后存放到obj属性中,再返回到主线程。
5.常见的几种应用方式
5.1子线程向主线程的Handler发送Message,主线程处理消息;【大量使用,必须掌握】
5.2主线程向子线程的Handler发送Message,子线程处理消息;【少量使用,熟悉】
5.3一个子线程向另一个子线程的Handler发送Message,另一个子线程处理消息;【很少使用,了解】
6.常用的格式化套路模板
6.1子线程向主线程的Handler发送Message:
第1步,在Activity中定义一个成员变量privateHandlermHandler=null;
第2步,在Activity中为message的what特定值,定义一组常量,后续根据常量操作,不易出错。
第3步,在Activity的onCreate()中new一个Handler的实例,并重写其handleMessage()方法;
第4步,在handleMessage()方法中根据what值对应的含义,进行对应的处理业务逻辑处理;
第5步,按需生成线程或线程的子类,同时把Activity中的mHandler通过构造或者方法函数传递给子线程,并保存到子线程的成员变量mHandler中;
第6步,在子线程的run处理中,根据业务需要,生成一个有特定含义的message,然后通过子线程中的mHandler发送消息,发送后Activity中的mHandler就能收到消息,并触发handleMessage()。
6.2示例代码1:
进度百分比:
【重点】
思路:
利用多线程,子线程每隔1秒发送一个当前的进度给主线程,主线程中Handler接收消息,更新进度显示。
6.3、示例代码2:
图片定时切换:
【重点】
思路:
利用多线程,子线程每隔2秒发送一个消息给主线程,主线程中Handler接收消息,并更新ImageView中的图片。
这样就实现了循环切换的动态效果。
6.4、示例代码3:
打地鼠:
思路:
主界面中放置9副地鼠的照片,在子线程中每隔2秒,利用Random().nextInt(9)产生一个随机数,并把随机数发送到主线程,主线程收到消息后根据随机数显式对应的图片,隐藏其它的图片。
6.5、示例代码4:
从网上下载4副图片,并显示到界面的4副图片上:
【重点】
思路:
利用多线程,子线程中依次下载4副图片,并把每副图片的Bitmap对象发送到主线程中,主线程中Handler接收消息,并更新ImageView中的图片。
6.6、【扩展内容】主线程跟子线程之间消息互相传递
7、Handler:
7.1、Handler的概念:
7.1.1Handler是用于发送和处理消息和一个线程的MessageQueue相关联的Runable对象。
7.1.2每个Handler实例关联到一个单一线程和线程的messagequeue。
7.1.3当您创建一个Handler,从你创建它的时候开始,它就绑定到创建它的线程以及对应的消息队列,handler将发送消息到消息队列,并处理从消息队列中取出的消息。
7.1.4Handler的主要用途有两个:
(1)、在将来的某个时刻执行消息或一个runnable;
(2)、为运行在不同线程中的多个任务排队。
7.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的子类中实现)。
∙当Handlerpost或者send消息的时候,可以在消息队列准备好的时候立刻执行,或者指定一个延迟处理或绝对时间对它进行处理,后两个是实现了timeout、ticks或者其他timing-based的行为。
∙当你的应用创建一个进程时,其主线程(UI线程)会运行一个消息队列,负责管理优先级最高的应用程序对象(Activity、广播接收器等)和任何他们创建的windows。
你也可以创建自己的线程,通过handler与主线程进行通信,在新创建的线程中handler通过调用post或sendMessage方法,将传入的Runnable或者Message插入到消息队列中,并且在适当的时候得到处理。
7.3、Handler的用法:
当你实例化一个Handler的时候可以使用Callback接口来避免写自定义的Handler子类。
这里的机制类似与Thread与runable接口的关系。
在Handler里面,子类要处理消息的话必须重写handleMessage()这个方法,因为在handler里面它是个空方法:
7.4、源码分析:
7.4.1A、Handler.java:
(3个属性,9个方法)
7.4.2代码中的3个属性:
∙final MessageQueuemQueue;
∙final LoopermLooper;
∙final CallbackmCallback;
7.4.3代码中的9个方法:
∙publicboolean handleMessage(Messagemsg);
∙publicfinalMessage obtainMessage()
∙publicfinalboolean sendMessage(Messagemsg)
∙publicfinalboolean sendEmptyMessage(intwhat)
∙publicfinalboolean post(Runnabler)
∙publicfinalbooleanpostAtTime(Runnabler,longuptimeMillis)
∙publicvoiddispatchMessage(Messagemsg)
∙publicbooleansendMessageAtTime(Messagemsg,longuptimeMillis)
∙publicfinalbooleansendMessageDelayed(Messagemsg,longdelayMillis)
8、Looper的源代码分析:
8.1、Looper.JAVA:
(4个属性,4个方法)
每个ThreadLocal中只能有一个Looper,也就是说一个Thread中只有一个Looper
8.2源代码中4个属性:
∙staticfinal ThreadLocal sThreadLocal =newThreadLocal();
∙final MessageQueue mQueue;
∙final Thread mThread;
∙privatestatic Looper mMainLooper =null;
8.3源代码中4个方法:
∙publicstaticvoidprepare()
∙publicstaticvoidprepareMainLooper()
∙publicstaticvoidloop()
∙publicstaticLoopermyLooper()
9.Message的源代码
9.1、Message.java:
(8个属性,5个方法)
9.2源代码中的8个属性:
∙publicint what;
∙publicint arg1;
∙publicint arg2;
∙publicObject obj;
∙Handler target;
∙Message sPool;
∙int sPoolSize;
∙int MAX_POOL_SIZE=10;
9.2源代码中的5个方法:
∙publicstaticMessage obtain()
∙publicvoid recycle()
∙publicvoid setTarget(Handlertarget)
∙publicHandler getTarget()
∙publicvoid sendToTarget()
10.扩展1【定时器】
Timer与ScheduledExecutorService定时周期执行指定的任务的区别:
10.1、Timer当任务抛出异常时的缺陷:
如果TimerTask抛出RuntimeException,Timer会停止所有任务的运行;但是ScheduledExecutorService可以保证,task1出现异常时,不影响task2的运行。
10.2、Timer执行周期任务时依赖系统时间,如果当前系统时间发生变化会出现一些执行上的变化,ScheduledExecutorService基于时间的延迟,不会由于系统时间的改变发生执行变化。
10.3、因此建议使用ScheduledExecutorService来取代Timer。
11扩展2在非UI线程中,更新UI问题的方法.
publicvoidupdate(Viewv){
//创建一个非UI线程,也就是工作线程.
newThread(){
publicvoidrun(){
while(count<1000){
count++;
//以下三个方法,都是用来处理,在非UI线程中,更新UI问题的方法.
//android.view.ViewRootImpl$CalledFromWrongThreadException:
//Onlytheoriginalthreadthatcreatedaviewhierarchycantouchitsviews.
//如果直接在非UI线程中更新UI线程中的组件,那么就会爆上面那个异常.
//tv.setText("count="+count);
//CausestheRunnabletobeaddedtothemessagequeue.
//Therunnablewillberunontheuserinterfacethread.
//这个post方法,会将这个Runnable给添加到一个消息队列中.这个Runnable就会在ui线程中调用.
//tv.post(newRunnable(){//此时newRunnable()可以看做主线程.
//@Override
//publicvoidrun(){
//tv.setText("count="+count);
//}
//});
//也是将一个Runnable给添加到消息队列中,这个runnable对象也是在UI线程中运行.但是是在指定的时间之后去运行.
//CausestheRunnabletobeaddedtothemessagequeue,
//toberunafterthespecifiedamountoftimeelapses.
//Therunnablewillberunontheuserinterfacethread.
//tv.postDelayed(newRunnable(){
//
//@Override
//publicvoidrun(){
//tv.setText("count="+count);
//}
//},3000);
//RunsthespecifiedactionontheUIthread.
//在UI线程中执行一个指定的动作(任务)
//IfthecurrentthreadistheUIthread,
//thentheactionisexecutedimmediately.
//IfthecurrentthreadisnottheUIthread,
//theactionispostedtotheeventqueueoftheUIthread.
MainActivity.this.runOnUiThread(newRunnable(){
@Override
publicvoidrun(){
tv.setText("count="+count);
}
});
try{
Thread.sleep(500);
}catch(InterruptedExceptione){
e.printStackTrace();
}
}
};
}.start();
}
11扩展思考3
11.1场景模拟一个中转任务调度。
11.2需求
11.2.1点击Activity中的按钮1时,如果子线程mTA1没有运行则启动运行;mTA1没有接到停止运行时,每隔500毫秒休眠一次,持续运行。
11.2.2点击Activity中的按钮2时,获取mTA1中属于mT1的