Android多线程——Handler

Android多线程——Handler

参考:

如下的例子,button的点击事件中,在子线程中修改UI:

        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                new Thread(new Runnable() {
                    @Override
                    public void run() {

                        try {
                            Log.w("MainActivity", Thread.currentThread().getName());
                            Thread.sleep(6000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }

                        textView.setText("Changed From Thread");

                    }
                }).start();

            }
        });

此时Logcat会提示如下的错误:

android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

在Android多线程中有如下的原则:

  • 不要阻塞UI线程
  • 不要在UI线程之外访问UI组件

1.Handler.sendXXXMessage()等方法

在上面的Activity中定义一个Handler,重写handleMessage

Handler mHandler = new Handler(){  
        @Override  
        public void handleMessage(Message msg){  
            if(msg.what == 0x123){  
                text.setText("Task Done!!");  
            }  
        }  
    };  

然后将工作线程的代码改为下面的样子

mRunnable = new Runnable() {  
            @Override  
            public void run() {  
                try {  
                    Thread.sleep(5000);//模拟耗时任务  
                } catch (InterruptedException e) {  
                    e.printStackTrace();  
                }  
                mHandler.sendEmptyMessage(0x123);//关于发消息的方法有很多,比如sendMessage(Message msg),sendMessageDelayed(Message msg, long delayMills)等等,可按具体需求选择,这里不作扩展  

            }  
        };  

一个线程只有一个Looper, 而一个Looper持有一个MessageQueue, 当调用Looper.prepare()时,Looper就与当前线程关联起来了(在Activity里没有显示调用Looper.prepare()是因为系统自动在主线程里帮我们调用了),而Handler是与Looper的线程是绑定的,查看Handler类的源码可以发现它几个构造函数,其中有接收一个Looper参数的,也有不接收Looper参数的,从上面的代码上看,我们没有为Handler指定Looper,那么Handler就默认更当前线程(即主线程)的Looper关联起来了,之所以啰嗦那么多就是因为这决定了Handler.handlerMessage(msg)方法体里的代码到底在哪个线程里执行,我们再梳理一下,Looper.prepare调用决定了Looper与哪个线程关联,间接决定了与这个Looper相关联的Handler.handlerMessage(msg)方法体里的代码执行的线程。(太啰嗦了)
现在回到上面的代码,我们的Handler是在主线程里的定义的,所以也默认跟主线程的Looper相关联,即handlerMessage方法的代码会在UI线程执行,因此更新TextView就不会报错了。下面这张图是弄清handlerMessage(msg)方法体里的代码的执行线程的思路
looper

2.Handler.post(Runnable)

只要将上面代码中的

 mHandler.sendEmptyMessage(0x123);  

改成

mHandler.post(new Runnable() {  
    @Override  
    public void run() {  
        text.setText("Task Done!!");                 
        }  
 });  

Handler由以下部分组成:

  • Handler
  • Message
  • Message Queue
  • Looper

Android 异步通信:手把手教你使用Handler消息传递机制(含实例Demo)有如下的一张图:
handler关系

Handler

在Activity中定义一个Handler

Handler允许你发送和处理与线程的MessageQueue关联的MessageRunnable对象。每个Handler实例都与一个线程和该线程的消息队列(message queue)相关联。当创建一个新的Handler时,它被绑定到创建它的线程/消息队列,它将messages和runnables传递给该消息队列并执行

There are two main uses for a Handler: (1) to schedule messages and runnables to be executed at some point in the future; and (2) to enqueue an action to be performed on a different thread than your own.
Handler有两个主要用途:(1)安排messages和runnables在将来的某个时刻执行; (2)在不同于自己的线程上执行的操作。

Handler可以把一个Message对象或者Runnable对象压入到消息队列中,进而在UI线程中获取Message或者执行Runnable对象,所以Handler把压入消息队列有两大体系,PostsendMessage

  • PostPost允许把一个Runnable对象入队到消息队列中。它的方法有:post(Runnable)postAtTime(Runnable,long)postDelayed(Runnable,long)
  • sendMessagesendMessage允许把一个包含消息数据的Message对象压入到消息队列中。它的方法有:sendEmptyMessage(int)sendMessage(Message)sendMessageAtTime(Message,long)sendMessageDelayed(Message,long)

Post

对于HandlerPost方式来说,它会传递一个Runnable对象到消息队列中,在这个Runnable对象中,重写run()方法。一般在这个run()方法中写入需要在UI线程上的操作

        btnMes1.setOnClickListener(new View.OnClickListener() {
            
            @Override
            public void onClick(View v) {
                // 新启动一个子线程
                new Thread(new Runnable() {                    
                    @Override
                    public void run() {
                        // tvMessage.setText("...");
                        // 以上操作会报错,无法再子线程中访问UI组件,UI组件的属性必须在UI线程中访问
                        // 使用post方式修改UI组件tvMessage的Text属性
                        handler.post(new Runnable() {                    
                            @Override
                            public void run() {
                                tvMessage.setText("使用Handler.post在工作线程中发送一段执行到消息队列中,在主线程中执行。");                        
                            }
                        });                                
                    }
                }).start();
            }
        });

Message

Message是容纳任意数据的容器。生产线程发送消息给 Handler,Handler 将消息加入到消息队列中。

如果对于一般的数据,Message提供了getData()setData()方法来获取与设置数据,其中操作的数据是一个Bundle对象

还有另外一种方式在Message中传递对象,那就是使用Message自带的obj属性传值,它是一个Object类型,所以可以传递任意类型的对象,Message自带的有如下几个属性:

  • int arg1:参数一,用于传递不复杂的数据,复杂数据使用setData()传递。
  • int arg2:参数二,用于传递不复杂的数据,复杂数据使用setData()传递。
  • Object obj:传递一个任意的对象。
  • int what:定义的消息码,一般用于设定消息的标志。

对于Message对象,一般并不推荐直接使用它的构造方法得到,而是建议通过使用Message.obtain()这个静态的方法或者Handler.obtainMessage()获取

package com.bgxt.datatimepickerdemo;

import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;

import android.app.Activity;
import android.app.ProgressDialog;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;

public class HandlerMessageActivity1 extends Activity {
    private Button btnDown;
    private ImageView ivImage;
    private static String image_path = "http://ww4.sinaimg.cn/bmiddle/786013a5jw1e7akotp4bcj20c80i3aao.jpg";
    private ProgressDialog dialog;
    private static int IS_FINISH = 1;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.asynctask_activity);

        btnDown = (Button) findViewById(R.id.btnDown);
        ivImage = (ImageView) findViewById(R.id.ivSinaImage);

        dialog = new ProgressDialog(this);
        dialog.setTitle("提示信息");
        dialog.setMessage("正在下载,请稍后...");
        dialog.setCancelable(false);

        btnDown.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                    new Thread(new MyThread()).start();
                    dialog.show();
            }
        });
    }

    private  Handler handler = new Handler() {
        // 在Handler中获取消息,重写handleMessage()方法
        @Override
        public void handleMessage(Message msg) {            
            // 判断消息码是否为1
            if(msg.what==IS_FINISH){
                byte[] data=(byte[])msg.obj;
                Bitmap bmp=BitmapFactory.decodeByteArray(data, 0, data.length);
                ivImage.setImageBitmap(bmp);
                dialog.dismiss();
            }
        }
    };

    public class MyThread implements Runnable {

        @Override
        public void run() {
            HttpClient httpClient = new DefaultHttpClient();
            HttpGet httpGet = new HttpGet(image_path);
            HttpResponse httpResponse = null;
            try {
                httpResponse = httpClient.execute(httpGet);
                if (httpResponse.getStatusLine().getStatusCode() == 200) {
                    byte[] data = EntityUtils.toByteArray(httpResponse
                            .getEntity());
                    // 获取一个Message对象,设置what为1
                    Message msg = Message.obtain();
                    msg.obj = data;
                    msg.what = IS_FINISH;
                    // 发送这个消息到消息队列中
                    handler.sendMessage(msg);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值