转载时请注明转自:http://blog.csdn.net/sam_zhang1984
交流可加新浪微博:Android开发人
本节会用到 JAVA 的内部类和匿名内部类,这些知识在另一篇转载的文章里详细讲述。
http://blog.csdn.net/sam_zhang1984/archive/2011/02/26/6209899.aspx
Handler 就是实现队列的形式,一个 Handler 共有两个队列:一个是线程队列,另一个是消息队列 。
要应用 Handler 进行线程队列,其流程主要是:
1. 要定义一个 Handler 对象;
2. 定义一个线程,并覆写其 run 方法;
3. 通过 Handler 对象把第二的线程对象压到队列中等待执行;
4. Handler 对象把第二的线程对象从队列中去除(当不想执行线程时 )。
注:如果需要循环执行线程,即可在处理线程后,再次利用 Handler 对象把线程对象压到队列中。
定义一个 Handler 对象的代码如下:
定义一个线程的代码如下:
Handler 对象把线程压入队列的方法是:
Handler 对象把线程从队列中去除的方法是:
在利用 Handler 对象实现线程队列的同时,还可以利用 Handler 对象消息队列进行处理,消息队列跟线程队列的一点不同,其处理实体是在 Handler 对象捕捉消息的方法 handleMessage 中处理,而线程队列则是在线程的 run 方法中处理。
利用 Handler 对象进行消息队列处理,需要的流程是:
1. 定义一个 Handler 对象,并覆写其 handleMessage ,用于捕捉消息并进行相应处理;
2. 定义一个消息对象,并对消息填内容;
3. 通过 Handler 对象发送消息,这样相应的 handleMessage 就会捕捉到这个消息。
Handler 对象的定义及覆写了 handleMessage 方法的代码如下:
消息内容填充有两种方法,一种是利用消息的 arg1 和 arg2 填充消息,这两个参数只能进行简单的整形变量传递,但其效率比较高;另一种填充消息的方法是 setDate 方法,这个方法可以利用键值对传递各种数据类型消息,但其效率相对低。定义消息对象及填充消息内容(这里以第一种方式为例 )代码如下:
Handler 对象发送消息,这样这个消息就会被 1 中覆写的 handleMessage 所捕捉,代码如下:
-------------------------------------分隔线------------------------------------------
下面开始Handler更深入的讨论
-------------------------------------分隔线------------------------------------------
通过上面我们的处理,虽然通过 Handler 对象往队列里面加入了一个新的线程,但实际上 Handler 和它所属的 Activity 是处于同一个线程中。因为我们通过 Handler 把线程加到队列中,实际是直接执行了 Runable 里面的 run 方法,而且没有像 JAVA 经典多线程编程一样调用线程的 start 方法,所以只是把线程对象加到 Activity 的线程中一起执行,而不是新开一个线程,即不是多真正的多线程。
补充知识: Bundle 对象
Bundle 对象实际就是一个键值对,只是它是一个比较特殊的键值对,因为它的键只能是 String 类型的数。它有存放( put ……)和读取( get ……)各种数据类型的方法。
多线程对于程序来说是很重要、很常用的一个机制,既然我们前面通过 Bundle 对象把线程加到队列中无法实际真正的多线程,那么我们就要寻找一个能实现真正多线程的方法,这就是 Looper 。
Looper 一般我们不需要自己创建, Android 应用程序框架提供了一个 HandlerThread 类,通过 HandlerThread 可以得到一个 Looper ,通过 Looper 构造一个 Handler 对象,即可把 Handler 对象绑定在这个 Looper 对象上,这样这个 Handler 对象就在另一个线程(循环取消息的 Looper )当中,这样就实现了真正的多线程。代码如下:
发送消息的另一种方法:利用 Message 类的 sendToTarget() 方法,即可以消息发送到目标对象,即生成这个 Message 的 Handler 对象,这样 Handler 对象通过其捕捉消息的方法也可以捕捉到这个消息。
填充消息内容的另一种方法:利用 Message 的 setData ,当然当要传递一些简单的非整形数值时,可以利用 Message 的 obj 属性来存放,但当要存放大量数据时,这些都无法满足了,所以就要用 setData 方法。 setData 方法的形参是一个 Bundle 对象,即一些键值对。这样就可以利用 Bundle 对象的 put ……方法存放任意数量、任意类型的数据了,再利用 Message 的 setData ( Bundle )方法用 Bundle 所存数据填充消息内容,然后通过发送消息的方法发送出去sendToTarget() 方法 (前面已经介绍过两种发送消息的方法了 ),最后在 Handler 对象的 handleMessage 中把 Bundle 获取出来( get ……方法)。
填充消息及发送消息代码如下:
捕捉消息及取出消息的代码如下:
调试过程中,想利用绑定在 Looper 的 Handler 把线程压到线程队列,并在这个线程中更新 Activity 里面的 TextView 控件,出现了以下错误提示:
Only the original thread that created a view hierarchy can touch its views
出现这个提示是因为我们通过 Looper 创建的 Handler 已经实现了多线程,假设这个线程叫 B ,即它不再是属于主线程 main ,这是不具有线程安全的,所以它无法改变主线程上的控件。
解决办法,在主线程中创建一个 Handler 对象 A ,并覆写其 handleMessage 方法(就如第一部分那样创建一个 Handler ),然后在子线程的 Handler 需要改变主线程控件时,向主线程的这个 Handler 对象 A 发送消息,最后在 A 的捕捉消息方法中对主线程控件进行更新。
在测试过程中,曾想在 B 的 handleMessage 中把消息 msg ( B 的消息),直接使用 msgMain=msg ,把 msg 的内容赋给 msgMain ( A 的消息),但一直出错,后来通过测试发现,通过这种方法的赋值,得到的 msgMain 与 msg 是同一个对象,即使声明其是 A 的消息,但仍然是 B 的消息。如果要实现 把 B 的消息内容赋给 A 的消息,则可通过以下代码实现
注:在使用 HandlerThread 的 getLooper 方法获取 Looper 对象时,一定要先调用 HandlerThread 的 start 方法,不然的话,取得到 Looper 对象是一个空的对象。
注意 : 当通过Looper开启一个新的Handler时,虽然会创建一个新的线程,但在新的Handler的构造函数执行时,即没执行完新的Handler的构造函数,仍然还处于创建这个新Handler的主线程中。所以如果在新的Handler的构造函数中执行费时的操作时,不是一个明智的选择,因为这样可能因为执行时间太长而导致主线程(通常是主Activity)阻塞长时间等待,一方面用户感知不好,另一方面(如果是Activity)会因长时间没响应而出错。
因些明智的选择时,在创建完新的Handler后(执行完其构造函数),通过发消息的方式激活新线程,并在新Handler中捕获消息进行相应的费时操作。