知识储备:Handler知识详解
2015-08-08 14:37
337 查看
Android中的两种线程
MainThread(主线程或者叫UI线程)所有的UI代码都是运行在主线程上的;主线程的工作主要是通过界面完成与用户的交互,譬如说接收用户的输入以及更新界面的操作。
WorkerThread(主线程之外的线程)
WorkerThread主要是用来处理一些比较耗时、或者容易产生阻塞的操作,譬如说访问网络。但是WorkThread在原则上是不允许操作UI的。
例如:点击Button,创建一个Thread,通过Thread修改TextView的值这样是会抛出异常的。
为了将WorkerThread的处理结果返回给主线程,这里就引入了Handler的概念,实现这MainThread和WorkerThread线程之间的通信。
Android中消息传递与消息处理机制
Handler、looper和MessageQueue三者构成了Android中最重要的消息传递以及消息处理机制。WorkerThread——>MainThread的通信(常用于UI的更新)
1. workerThread与MainThread消息传递与消息处理机制的实现过程:
1、首先在主线程中先创建一个Handler对象,重写handleMessage方法
2、在WorkerThread中,通过handler获得一个message对象并将Message对象发送 到MessageQueue中
2、Looper对象会一直从MessageQueue中取出Message对象,再调用主线程中Handler对象的handleMessage方法处理WorkerThread发送过来的消息(Android OS的Loop而对象会隐式调用handleMessage方法,我们要做的只是重写handleMessage方法即可)
2. 例子讲解:
为按钮设置监听——启动WorkerThread线程(获得Message对象并发送)——MainThread中实现handMessage
handler = new MyHandler(); button.setOnClickListener(new OnClickListener() { public void onClick(View v) { // TODO Auto-generated method stub Thread t = new NetworkThread(); t.start(); }); @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.activity_main, menu); return true; } class NetworkThread extends Thread { @Override public void run() { // TODO Auto-generated method stub //采用休眠来模拟访问网络 super.run(); try { Thread.sleep(2 * 1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } String str = "从网络当中获得的数据"; //直接设置 textView.setText(str)是不行的,原因不能再workerThread操作控件 Message msg = handler.obtainMessage(); //sendMessage(msg)无论是在主线程发送或是在WorkerThread发送,均可以被HandMessage处理 msg.obj = str; handler.sendMessage(msg); } } class MyHandler extends Handler { @Override public void handleMessage(Message msg) { // TODO Auto-generated method stub super.handleMessage(msg); String str = (String)msg.obj; textView.setText(str); }
总之,实现WorkerThread——>MainThread之间的通信要写的代码为:
1、new一个Handler对象(带有空的handleMessage(msg)方法)
2、在WorkerThread线程的run方法中,调用Handler.obtainMessage()获得消息对象
3、handler.sendMessge(msg)将消息对象发送到消息对象中
4、重写HandleMessage(msg)方法(Looper会在os的MainThread隐式调用)
(相当于sendMessage在WorkerThread中发送消息,handleMessage在MainThread中获得并处理消息)
MainThread——>WorkerThread的通信(流程比较固定)
通信实现过程
WorkerThread的实现流程:
1、在WorkerThread中准备Looper.prepare()对象:完成Looper对象的初始化工作
2、在WorkerThread中生成Handler对象,并重写handleMessage方法处理消息
3、调用Looper.loop()对象:Looper对象会不停的从MessageQueue对象中取出消息,并调用handleMessage处理对象
MainThread实现的流程:
通过handler.obtainMessage()获得message对象,并通过handler.sendMessage方法将消息发送到MessageQueue中
Handler相关源码相关分析
Looper.prepare()方法:sThreadLocal.set(newLooper(quitAllowed))做了以下两件事情:
1、生成了一个Looper对象(new Looper()) + 生成了一个与该Looper对象对应的消息队列(MessageQueue)对象
2、将Looper对象存在线程本地变量当中
(即ThreadLocal存入的信息为(key(当前线程)、value(Looper对象)))
构造一个Handler对象并实现handMessage方法
New Handler()当中有个Looper.myLooper():从线程本地变量中取出当前线程的Value(1中的Looper对象),赋值给handler对象的成员变量mLooper
再把1中的MessageQueue赋值给Handler的成员变量
(总之,通过ThreadLocal将Looper、Thread、MessageQueue和Handler联系起来了)
(一个线程当中只能有一个Looper对象,否则会报异常:Only one looper may be created per thread)
Looper.loop()
1、调用了myLooper();根据当前线程取出与之对应的Looper对象,再取出与之对应的消息队列(MessageQueue)对象
2、死循环,从消息队列中取出消息对象(queue.next());如果没有对象,则阻塞;
3、在循环中调用msg.target.dispatchMessage(msg):执行handleMessage(所以要重写该方法)
(message的target是一个Handler对象 )
1)先通过handler.obtainMessage()生成Message对象
2)obtainMessage()方法调用了obtain(this)(this代表当前的handler)
3)将当前的handler赋值给msg.target
总之就是:Looper.prepare()创建Looper对象和MessageQueue对象;创建Handler对象通过obtainMessage()获得消息对象,并将消息对象放到MessageQueue中;最后,Looper.loop()方法死循环从消息队列中获取Message并调用Handler的handleMessage处理
Handler中post(Runnable r)源码分析
要理解Handler的post方法,先要了解java中线程运行的两种机制:继承Thread类 + 实现Runnable接口Thread:代表的是一个线程
Runnable:代表的是一个线程体,单有线程体是不会运行的,还要把该线程体传到一个Thread对象中
post(Runnable r)方法的使用举例
例如:在主线程中生成Handler对象(就表示Looper、MessageQueue都在主线程中),点击按钮,启动线程调用handler.post(Runnable r)
class MyThread extends Thread { @Override public void run() { // TODO Auto-generated method stub //访问网络 super.run(); Runnable r = new Runnable() { //更新UI public void run() { // TODO Auto-generated method stub System.out.println("当前线程:" + Thread.currentThread().getName()); } }; //将Runnable线程体传给主线程,相当于是在主线程中执行了这段代码 handler.post(r); } }
执行打印的结果显示的线程为主线程。
3. post(Runnable r)方法的运行机制:Runnable对象添加到在消息队列当中,runnable会在Handler依附的那个线程中运行。(这个是官方文档的说法,有点抽象,下面来看源码)
①:getPostMessage(r):调用Message.obtain()方法获取Message对象,并将r赋值给msg对象的Runnable类型的callback属性。
public final boolean post(Runnable r) { return sendMessageDelayed(getPostMessage(r), 0); } private static Message getPostMessage(Runnable r) { Message m = Message.obtain(); m.callback = r; return m; }
②:之后调用sendMessagesendMessgeDelayed(msg,0)等同于,SendMessage(msg)方法将msg对象发送到消息队列中。
public final boolean post(Runnable r){ return sendMessageDelayed(getPostMessage(r), 0); }
③:主线程中Looper.loop()方法还是一样会调用msg.target.dispatchMessage(msg)不断从消息队列中获取消息并且处理,但处理的方法不再是调用handleMessage方法了,而调用的是handleCallBack()方法(因为msg对象的callBack属性不为空了)直接调用Runnable的run方法(直接调用run方法是不会开辟新线程的)
private static void handleCallback(Message message) { message.callback.run(); }
可以理解成将Runnable中的整个代码(更新UI)块传给主线程,让主线程执行
Android线程池详解
线程池的引入new Thread创建和启动线程的弊端如下:
a. 采用new Thread方式创建过多线程的话,每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后可能会死机或者OOM。
b. 采用new Thread的方式启动的线程缺乏统一管理,譬如说资源竞争的管理啊,定时执行、定期执行、单线程执行管理啊、并发数控制啊。
线程池的好处在于:
a. 重用已创建的线程,减少对象创建、消亡的开销,性能佳。
b. 对创建的线程有一个统一的管理,可有效控制最大并发线程数,减少过多资源竞争,避免堵塞,还提供了定时执行、定期执行、单线程执行管理等功能。
Android中提供的四种线程池
Java通过Executors提供四种线程池,分别为:
newCachedThreadPool创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程(有空闲的线程),那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务(任务多,而线程不够的时候)。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
newFixedThreadPool 创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。该线程池可控制线程最大并发数,超出的线程会在队列中等待。
newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
newSingleThreadExecutor创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
(JDK帮助文档强烈建议使用这四种线程池,而不要自己去定制)
线程池构造方法的底层实现
Executors.newXXXThreadPool()创建线程池对象底层都是调用了
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) //corePoolSize - 池中所保存的线程数,包括空闲线程。 //maximumPoolSize-池中允许的最大线程数。 //keepAliveTime - 当线程数大于核心时(即线程池中有多余的线程),此为终止前多余的空闲线程等待新任务的最长时间。 //unit - keepAliveTime 参数的时间单位。 //workQueue - 执行前用于保持任务的队列。此队列仅保持由 //execute方法提交的 Runnable任务。 //threadFactory - 执行程序创建新线程时使用的工厂。 //handler - 由于超出线程范围和队列容量而使执行被阻塞时所使用的处理程序。 ThreadPoolExecutor是Executors类的底层实现。
ExecutorService newFixedThreadPool (int nThreads):固定大小线程池。
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }
参数解释:
1、线程池中保存的线程个数corePoolSize(正在运行的 + 执行结束的空闲线程)和线程池中允许的最大线程个数,maximumPoolSize的大小是一样(这样就能使得线程池的大小固定了)
2、keepAliveTime为0,表示池中空闲线程立即终止执行
3、阻塞队列BlockingQueue选择的是无界的队列LinkedBlockingQueue,这样就能在线程池中没有空闲线程执行任务的时候讲任务添加到一个无界的队列中。
2. ExecutorService newSingleThreadExecutor():单线程线程
public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); }
ExecutorService newCachedThreadPool():缓存线程池,可以进行自动线程回收
public static ExecutorService newCachedThreadPool(){ return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }
1、线程池中保存的线程个数corePoolSize为初始化为0,因为这个参数会动态变化
2、线程池中允许的最大线程个数maximumPoolSize是无界的(所以也称为无界线程池)
3、keepAliveTime为60,表示池中空闲线程60S后还有有启动执行的话就终止该线程
4、阻塞队列BlockingQueue选择的是无界的队列SynchronousQueue,该QUEUE中,每个插入操作必须等待另一个线程的对应移除操作。
线程池中保存任务的队列
直接提交任务的同步队列SynchronousQueue
1、SynchronousQueue是无界的,也就是说他存数任务的能力是没有限制的,但是由于该Queue本身的特性,在某次添加元素后必须等待其他线程取走后才能继续添加
2、SynchronousQueue直接将任务直接提交给线程而不保存它们。如果线程池中不存在可用于立即运行任务的线程,则试图把任务加入队列将失败,因此会构造一个新的线程来执行相应的任务
3、同步队列SynchronousQueue通常要求线程池中允许的最大线程数maximumPoolSizes无界, 以避免拒绝新提交的任务。
无界队列LinkedBlockingQueue
1、在池中所有 线程都运行时新任务在队列中等待。这样,创建的线程就不会超过 corePoolSize。
有界队列ArrayBlockingQueue
1、无界线程池中线程大量创建 + 无界队列中任务的大量添加都很有可能导致内存溢出,防止耗费过多的内存,但这样也会降低线程的执行效率。
相关文章推荐
- 使用Genymotion调试出现错误INSTALL_FAILED_CPU_ABI_INCOMPATI
- RNN 入门学习资料整理
- scanf()函数的原理
- fedora20配置静态ip
- 从Sql Server通过HTTP推送数据到网页
- 解决“同一UIView中有多个UITableView时点击状态栏不能置顶”问题
- 关于接口与Object 类的关系
- HDU 3094 A tree game 树删边游戏
- 操作系统之页面置换算法
- [leetcode] Two Sum
- POJ 3007 Organize Your Train part II(枚举)
- 当webview遇到360wifi
- PHP模拟asp中response类实现方法
- django 快速搭建blog
- 程序员面试宝典(第四版)——读书笔记-1、第五章:程序设计基本概念
- 网站右侧导航条的玩法
- Codeforces Round #312 (Div. 2)
- NEUQ 1209: 内码对称
- 【NYIST】暑假训练赛 (一)----Problem F
- jsonp不能post!