Mars Android视频学习笔记——01_14/15_Handler的使用

转载时请注明转自: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 对象的代码如下:

  1. Handler myFirstHandler= new Handler  

定义一个线程的代码如下:

  1. Runnable myThread = new Runnable() {  
  2.         public void run() {  
  3.             // TODO Auto-generated method stub   
  4. }  
  5.                 };  

Handler 对象把线程压入队列的方法是:

  1. myFirstHandler.post(myThread);  //把一个线程放到队列里面去  

Handler 对象把线程从队列中去除的方法是:

  1. myFirstHandler.removeCallbacks(myThread);//把线程从队列中去除  

 

在利用 Handler 对象实现线程队列的同时,还可以利用 Handler 对象消息队列进行处理,消息队列跟线程队列的一点不同,其处理实体是在 Handler 对象捕捉消息的方法 handleMessage 中处理,而线程队列则是在线程的 run 方法中处理。

利用 Handler 对象进行消息队列处理,需要的流程是:

1.        定义一个 Handler 对象,并覆写其 handleMessage ,用于捕捉消息并进行相应处理;

2.        定义一个消息对象,并对消息填内容;

3.        通过 Handler 对象发送消息,这样相应的 handleMessage 就会捕捉到这个消息。

Handler 对象的定义及覆写了 handleMessage 方法的代码如下:

  1. //声明一个Handler对象   
  2.     Handler myFirstHandler= new Handler(){  
  3.         @Override  
  4.         public void handleMessage(Message msg) {  
  5.         //消息处理过程       
  6.         }  
  7.       
  8.     };  

消息内容填充有两种方法,一种是利用消息的 arg1 arg2 填充消息,这两个参数只能进行简单的整形变量传递,但其效率比较高;另一种填充消息的方法是 setDate 方法,这个方法可以利用键值对传递各种数据类型消息,但其效率相对低。定义消息对象及填充消息内容(这里以第一种方式为例 )代码如下:

  1. Message msg = myFirstHandler.obtainMessage();  //创建一个消息   
  2.             msg.arg1 = i ; //填充消息内容  

Handler 对象发送消息,这样这个消息就会被 1 中覆写的 handleMessage 所捕捉,代码如下:

  1. myFirstHandler.sendMessage(msg);               //通过Handler把消息发送出去  

 

-------------------------------------分隔线------------------------------------------

下面开始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 )当中,这样就实现了真正的多线程。代码如下:

 

[c-sharp] view plain copy print ?
  1. //生成一个Handler类   
  2.     class secondHandler extends Handler{  
  3.           
  4.         public secondHandler() {  
  5.             super();  
  6.             // TODO Auto-generated constructor stub   
  7.         }  
  8.         public secondHandler(Looper looper) {  
  9.             super(looper);  
  10.             // TODO Auto-generated constructor stub   
  11.         }  
  12.         @Override  
  13.         public void handleMessage(Message msg) {  
  14.                     }  
  15.       
  16.     };  
  17. /*声明一个HandlerThread对象   构造函数的参数即是该线程的名字,即当myHandlerThread绑定HandlerThread 
  18.                     产生的Looper后,myHandlerThread所在的线程名字就叫做sam_HandlerThread 
  19.         */  
  20.         HandlerThread myHandlerThread = new HandlerThread("sam_HandlerThread");  
  21.         //启动HandlerThread   
  22.         myHandlerThread.start();  
  23.         //声明一个自定义的Handler对象   
  24.         mySecondHandler = new secondHandler(myHandlerThread.getLooper());  

 

发送消息的另一种方法:利用 Message 类的 sendToTarget() 方法,即可以消息发送到目标对象,即生成这个 Message Handler 对象,这样 Handler 对象通过其捕捉消息的方法也可以捕捉到这个消息。

 

填充消息内容的另一种方法:利用 Message setData ,当然当要传递一些简单的非整形数值时,可以利用 Message obj 属性来存放,但当要存放大量数据时,这些都无法满足了,所以就要用 setData 方法。 setData 方法的形参是一个 Bundle 对象,即一些键值对。这样就可以利用 Bundle 对象的 put ……方法存放任意数量、任意类型的数据了,再利用 Message setData Bundle )方法用 Bundle 所存数据填充消息内容,然后通过发送消息的方法发送出去sendToTarget() 方法前面已经介绍过两种发送消息的方法了 ),最后在 Handler 对象的 handleMessage 中把 Bundle 获取出来( get ……方法)。

 

 填充消息及发送消息代码如下:

 

 

[c-sharp] view plain copy print ?
  1.        Bundle msgContents=new Bundle(); //声明一个Bundle对象用于存放消息内容   
  2. Message msg = mySecondHandler.obtainMessage();  //声明一个与mySecondHandler的消息并填充消息内容   
  3. msgContents.putInt("age", 28);  
  4. msgContents.putString("name""Sam");  
  5. msg.setData(msgContents);           //填充消息内容   
  6. msg.sendToTarget();                 //把消息发送给mySecondHandler  

捕捉消息及取出消息的代码如下:

[c-sharp] view plain copy print ?
  1. public void handleMessage(Message msg) {  
  2.             txtDisplay.setText(  "收到的名字是:" + msg.getData().getString("name") +  
  3.                                "收到的年龄是:" + msg.getData().getInt("age"));  
  4.                 }  

 

调试过程中,想利用绑定在 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 的消息,则可通过以下代码实现

 

  1. msgMain.setData(msg.getData())  

 

注:在使用 HandlerThread getLooper 方法获取 Looper 对象时,一定要先调用 HandlerThread start 方法,不然的话,取得到 Looper 对象是一个空的对象。

 

 


注意 当通过Looper开启一个新的Handler时,虽然会创建一个新的线程,但在新的Handler的构造函数执行时,即没执行完新的Handler的构造函数,仍然还处于创建这个新Handler的主线程中。所以如果在新的Handler的构造函数中执行费时的操作时,不是一个明智的选择,因为这样可能因为执行时间太长而导致主线程(通常是主Activity)阻塞长时间等待,一方面用户感知不好,另一方面(如果是Activity)会因长时间没响应而出错。

因些明智的选择时,在创建完新的Handler后(执行完其构造函数),通过发消息的方式激活新线程,并在新Handler中捕获消息进行相应的费时操作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值