Handler机制分析

出于性能优化考虑,Android的UI操作并不是线程安全的,这意味着如果有多个线程并发操作UI组件,可能导致线程安全问题。为了解决这个问题,Android制定了一条简单的原则:只允许UI线程(亦即主线程)修改Activity中的UI组件。

当一个程序第一次启动时,Android会同时启动一条主线程,主线程主要负责处理与UI相关的事件,如用户的按键事件、用户接触屏幕的事件、屏幕绘图事件,并把相关的事件分发到相应的组件进行处理,所以主线程通常又叫做UI线程。

Handler的概念:

1)执行计划任务,可以在预定的时间执行某些任务,可以模拟定时器
2)线程间通信。在Android的应用启动时,会创建一个主线程,主线程会创建一个消息队列来处理各种消息。当你创建子线程时,你可以在你的子线程中拿到父线程中创建的Handler 对象,就可以通过该对象向父线程的消息队列发送消息了。由于Android要求在UI线程中更新界面,因此,可以通过该方法在其它线程中更新界面。

Handler类包含如下方法用于发送、处理消息:

  • void handlerMessage(Message msg):处理消息的方法,该方法通常用于被重写。
  • final boolean hasMessage(int what):检查消息队列中是否包含what属性为指定值的消息。
  • sendEmptyMessage(int what):发送空消息
  • final boolean sendMessage(Message msg):立即发送消息,注意这块返回值,如果message成功的被放到messagequeue里面则返回true,反之,返回false;(个人建议:对于这类问题不必主观去记它,当实际使用时,直接查看源码即可,源码中有详细的注释)

Handler的作用:

(1)在一个线程中发送消息。
(2)在另一个线程中获取、处理消息。

Handler处理的基本原理:

为了让主线程能及时处理子线程发送的消息,显然只能通过回调的方法来实现—-开发者只要重写Handler类中的方法,当新启动的线程发送消息时,消息会发送至与之关联的MessageQueue,而Handler会不断的从MessageQuere中获取并处理消息—–这将导致Handler类中处理消息的方法被回调。

在线程中使用Handler的基本步骤如下:

在被调用线程中完成以下内容:
(1)调用 Looper的prepare()方法为当前线程创建Looper对象,创建Looper对象时,它的构造器会创建与之配套的MessageQueue。
(2)有了Looper之后,创建Handler子类的实例,重写HandlerMessage()方法,该方法负责处理来自其它线程的消息。
(3)调用Looper的loop()方法启动Looper。

注:若被调用线程是主线程类,由于系统自动为主线程创建了Looper的实例,因此第一、三步骤可省略,而只需要做第2步即可。
在调用线程中完成:
(1)创建message,并填充内容。
(2)使用被调用类创建的Handler实例,调用sendMessage(Message msg)方法。

Handler 与线程的关系

Handler 一般运行于主线程内,也可以运行在子线程中,但是一定要创建Looper对象。主线程中Android已经为之创建了Looper对象

使用Handler的两种常见情况:

1、只能在主UI中修改UI。但实际上,有部分UI需要在子线程中控制其修改逻辑,因此子线程需要通过handler通知主线程修改UI。这在游戏开发中尤其常见,比如需要让新启动的线程周期性的改变UI。子线程通知主UI线程修改UI组件的例子,新线程周期性的修改ImageView所显示的图片:
这个例子是Handler在主线程中获取,处理消息,在子线程中发送消息

public class HandlerTest extends Activity  
{  
    // 定义周期性显示的图片的ID  
    int[] imageIds = new int[]  
    {  
        R.drawable.java,  
        R.drawable.ee,  
        R.drawable.ajax,  
        R.drawable.xml,  
        R.drawable.classic  
    };  
    int currentImageId = 0;  

    @Override  
    public void onCreate(Bundle savedInstanceState)  
    {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.main);  
        final ImageView show = (ImageView) findViewById(R.id.show);  
        final Handler myHandler = new Handler()//在主线程中,获取,处理消息,更新UI组件,可以修改UI组件  
        {  
            @Override  
            public void handleMessage(Message msg)  
            {  
                // 如果该消息是本程序所发送的  
                if (msg.what == 0x1233)  
                {  
                    // 动态地修改所显示的图片  
                    show.setImageResource(imageIds[currentImageId++  
                        % imageIds.length]);  
                }  
            }  
        };  
        // 定义一个计时器,让该计时器周期性地执行指定任务。子线程通知主线程修改UI组件,实现进程间通信  
        new Timer().schedule(new TimerTask()  
        {  
            @Override  
            public void run()  
            {  
                // 发送空消息  
                myHandler.sendEmptyMessage(0x1233);在线程中发送消息  
            }  
        }, 0, 1200);  
    }  
}  

2、为避免ANR,应该在子线程中执行耗时较长的操作,而此操作完成后,有可能需要通知主线程修改UI。
在子线程中执行耗时任务后,通知主线程修改UI组件的例子:使用新进程计算质数,并用Toast显示这个例子是在主线程中发送消息,在子线程中获取,处理消息

public class CalPrime extends Activity  
{  
    static final String UPPER_NUM = "upper";  
    EditText etNum;  
    CalThread calThread;  
    // 定义一个线程类  
    class CalThread extends Thread  
    {  
        public Handler mHandler;  

        public void run()  
        {  
            Looper.prepare();//创建Looper对象,每个线程使用Handler时都要有一个Looper对象  
            mHandler = new Handler()//在子线程中用handler获取,处理消息  
            {  
                // 定义处理消息的方法  
                @Override  
                public void handleMessage(Message msg)  
                {  
                    if(msg.what == 0x123)  
                    {  
                        int upper = msg.getData().getInt(UPPER_NUM);  
                        List<Integer> nums = new ArrayList<Integer>();  
                        // 计算从2开始、到upper的所有质数  
                        outer:  
                        for (int i = 2 ; i <= upper ; i++)  
                        {  
                            // 用i处于从2开始、到i的平方根的所有数  
                            for (int j = 2 ; j <= Math.sqrt(i) ; j++)  
                            {  
                                // 如果可以整除,表明这个数不是质数  
                                if(i != 2 && i % j == 0)  
                                {  
                                    continue outer;  
                                }  
                            }  
                            nums.add(i);  
                        }  
                        // 使用Toast显示统计出来的所有质数  
                        Toast.makeText(CalPrime.this , nums.toString()  
                            , Toast.LENGTH_LONG).show();  
                    }  
                }  
            };  
            Looper.loop();//启动Looper  
        }  
    }  
    @Override  
    public void onCreate(Bundle savedInstanceState)  
    {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.main);  
        etNum = (EditText)findViewById(R.id.etNum);  
        calThread = new CalThread();  
        // 启动新线程  
        calThread.start();  
    }  
    // 为按钮的点击事件提供事件处理函数  
    public void cal(View source)  
    {  
        // 创建消息  
        Message msg = new Message();  
        msg.what = 0x123;  
        Bundle bundle = new Bundle();  
        bundle.putInt(UPPER_NUM ,  
            Integer.parseInt(etNum.getText().toString()));  
        msg.setData(bundle);  
        // 在主线程中向新线程中的Handler发送消息  
        calThread.mHandler.sendMessage(msg);//在主线程中发送消息  
    }  
}  

Handler,Loop,Message,MessageQueue的工作原理

Loop,Message,MessageQueue概念理解:

  • Message:Handler发送、接收和处理的消息对象
  • Looper:每个线程只能拥有一个Looper.它的looper()方法负责循环读取MessageQueue中的消息并将读取到的消息交给发送该消息的handler进行处理。
  • MessageQueue:消息队列,它采用先进先出的方式来管理Message。程序在创建Looper对象时,会在它的构造器中创建MessageQueue

Looper提供的源码如下:

private Looper(boolean quitAllowed) {  
      Queue = new MessageQueue(quitAllowed);  
      mThread = Thread.currentThread();  
}  

从源码第2行中可以看出,在创建Looper对象时会创建一个与之关联的MessageQueue对象。构造器是private修饰的,所以程序员是无法创建Looper对象的,就是说创建Looper同时就创建了MessageQueue对象。

Handler: 前面说Handler作用有两个—发送消息和处理消息,Handler发送的消息必须被送到指定的MessageQueue,也就是说,要想Handler正常工作必须在当前线程中有一个MessageQueue,否则消息没法保存。而MessageQueue是由Looper负责管理的,因此要想Handler正常工作,必须在当前线程中有一个Looper对象,这里分为两种情况:

  1. 主线程(UI线程),系统已经初始化了一个Looper对象,因此程序直接创建Handler即可
  2. 程序员自己创建的子线程,这时,程序员必须创建一个Looper对象,并启动它。

创建Looper:

使用Looper.prepare(),查看源码

public static void prepare() {  
    prepare(true);  
}  

private static void prepare(boolean quitAllowed) {  
    if (sThreadLocal.get() != null) {  
         throw new RuntimeException("Only one Looper may be created per thread");  
    }  
    sThreadLocal.set(new Looper(quitAllowed));  
}  

private Looper(boolean quitAllowed) {  
    mQueue = new MessageQueue(quitAllowed);  
    mThread = Thread.currentThread();  
}  

通过方法调用,第9行创建Looper对象,创建Looper对象时同时会创建MessageQueue对象(第13行)。此外,可以看出prepare()允许一个线程最多有一个Looper被创建。

启动Looper:

Looper.loop(),loop()使用一个死循环不断取出MessageQueue中的消息,并将消息发送给对应的Handler进行处理。下面是Looper类中looper()方法的部分源码

for (;;) {  
      Message msg = queue.next(); // might block  
      if (msg == null) {  
         // No message indicates that the message queue is quitting.  
          return;  
      }  

      // This must be in a local variable, in case a UI event sets the logger  
      Printer logging = me.mLogging;  
      if (logging != null) {  
          logging.println(">>>>> Dispatching to " + msg.target + " " +  
              msg.callback + ": " + msg.what);  
      }  

      msg.target.dispatchMessage(msg);  

      if (logging != null) {  
          logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);  
      }  

      // Make sure that during the course of dispatching the  
      // identity of the thread wasn't corrupted.  
      final long newIdent = Binder.clearCallingIdentity();  
      if (ident != newIdent) {  
           Log.wtf(TAG, "Thread identity changed from 0x"  
                 + Long.toHexString(ident) + " to 0x"  
                 + Long.toHexString(newIdent) + " while dispatching to "  
                 + msg.target.getClass().getName() + " "  
                 + msg.callback + " what=" + msg.what);  
      }  

      msg.recycleUnchecked();  
}  

很明显第1行用了一个死循环,第2行从queue中取出Message,第15行通过dispatchMessage(Message msg)方法将消息发送给Handler。

Looper,MessageQueue,Handler的各自作用如下:

Looper:每个线程只有一个Looper,他负责管理MessageQueue,会不断的从MessageQueue中取出消息,将消息交给对应的Handler处理
MessageQueue:由Looper负责管理,是用来存放线程放入的消息。
Handler:它把消息发送给Looper管理的MessageQueue,并负责处理Looper分给它的消息。

在线程中Handler的使用步骤是:

(1)调用 Looper的prepare()方法为当前线程创建Looper对象,创建Looper对象时,它的构造器会创建与之配套的MessageQueue。
(2)有了Looper之后,创建Handler子类的实例,重写HandlerMessage()方法,该方法负责处理来自其它线程的消息。
(3)调用Looper的loop()方法启动Looper。

转载:http://blog.csdn.net/tuke_tuke/article/details/50782893
http://blog.csdn.net/tuke_tuke/article/details/50783153

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值