浅谈Handler

作者: olaH | 来源:发表于2017-12-24 14:08 被阅读0次

    前言

    积土成山,风雨兴焉;积水成渊,蛟龙生焉;积善成德,而神明自得;

    Handler消息传递机制

    出于性能优化考虑,Android的UI操作并不是线程安全的,这意义着如果有多个线程并发操作UI组件,则可能导致线程安全问题。为了解决这个问题,Android制定了一条简单的规则:只允许UI线程修改Activity里的UI组件。在实际开发中,需要让新启动的线程周期性地改变界面组件的属性值,这就需要借助于Handler的消息传递机制来实现了。

    Handler类简介

    Handler类的主要作用有两个。
    • 在新启动的线程中发送消息。
    • 在主线程中获取、处理消息。
    Handler类的功能看似简单,似乎只要在新启动的线程中发送消息,在主线程中获取、处理消息。但这个过程涉及两个问题:新启动的线程何时发送消息呢?主线程何时去获取并处理消息呢?
    为了让主线程能在适当的时机处理新启动的线程所发送的消息,显然只能通过回调的方式来实现——需要重写Handler类中处理消息的方法。
    Handler类包含如下方法用于发送、处理消息。
    • void handleMessage(Message msg):处理消息的方法。通常被重写。
    • final boolean hasMessages(int what):检查消息队列是否包含what属性为指定值的消息。
    • final boolean hasMessages(int what, Object object):检查消息队列中是否包含what属性为指定值且object属性为指定对象的消息。
    • sendEmptyMessage(int what):发送空消息。
    • final boolean sendEmptyMessageDelayed(int what,long delayMillis):指定多少秒后发送空消息。
    • final boolean sendMessage(Message msg):立即发送消息。
    • final boolean sendMessageDelayed(Message msg,long delayMillis)
    本实例将通过一个新线程来周期性地修改ImageView所显示的图片,自动播放图片还可以使用ViewFlipper和AdapterViewFlipper组件来实现。

    代码示例

    MainActivity.java
    public class MainActivity extends Activity {
    
        // 定义周期性显示的图片ID
        int[] imageIds = new int[]
        {
            R.drawable.baxianhua,
            R.drawable.dengta,
            R.drawable.juhua,
            R.drawable.kaola,
            R.drawable.qie,
            R.drawable.shamo,
            R.drawable.shuimo,
            R.drawable.yujinx
        };
    
        int currentImageId = 0;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.handler);
            final ImageView show = (ImageView) findViewById(R.id.image);
            final Handler myHandler = new Handler()
            {
    
                @Override
                public void handleMessage(Message msg) {
                    if(msg.what == 0x123)
                    {
                        //动态地修改所显示的图片
                        show.setImageResource(imageIds[currentImageId++ % imageIds.length]);
                    }
                }
    
            };
    
            //定义一个计时器,让该计时器周期性地执行指定任务
            new Timer().schedule(new TimerTask() {
                @Override
                public void run() {
    
                    //发送空消息
                    myHandler.sendEmptyMessage(0x123);
                }
            }, 0, 1200);//1.2秒更改一次
        }
    }
    

    效果

    Screenshot_20171026-112831.png

    Handler、Loop、MessageQueue的工作原理

    为了更好地理解Handler的工作原理,下面介绍一下与Handler一起工作的几个组件。
    • Message:Handler接收和处理的消息对象。

    • Looper:每个线程只能拥有一个Looper。它的loop()方法负责读取MessageQueue中的消息,读到信息之后就把消息交给发送该消息的Handler进行处理。

    • MessageQueue:消息队列,它采用先进先出的方式来管理Message。程序创建Looper对象时,会在它的构造器中创建MessageQueue对象。

    Handler的作用有两个——发送消息和处理消息,程序使用Handler发送消息,由Handler发送的消息必须被送到指定的MessageQueue。也就是说当前线程必须要有一个MessageQueue,不过MessageQueue是由Looper负责管理的。如果希望Handler正常工作,必须在当前线程中有一个Looper对象。为了保证当前线程中有Looper对象,可以分如下两种情况处理。
    • 主UI线程中,系统已经初始化了一个Looper对象,因此程序直接创建Handler即可,然后就可通过Handler来发送消息、处理消息了。

    • 程序员自己启动的子线程,必须自己创建一个Looper对象,并启动它。创建Looper对象调用它的prepare()方法,调用loop()方法来启动它。

    在线程中使用Handler的步骤如下。
    1. 调用Looper的prepare()方法为当前线程创建Looper对象,创建Looper对象时,它的构造器会创建与之配套的MessageQueue。

    2. 有了Looper之后,创建Handler子类的实例,重写handleMessage()方法,该方法负责处理来自于其他线程的消息。

    3. 调用Looper的loop()方法启动Looper。

    接下来看一段程序,该程序的功能是允许用户输入一个数值上限,当用户单击“计算”按钮时,该应用会将该上限数值发送到新启动的线程中,让该线程来计算该范围内的所有质数。

    代码示例

    public class MainActivity extends Activity {
    
        static final String UPPER_NUM = "upper";
        EditText et;
        CalThread calThread;
    
        // 定义一个线程类
        class CalThread extends Thread {
            public Handler handler;
    
            public void run() {
                Looper.prepare();
                handler = new 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++) {
                                for(int j = 2;j <= Math.sqrt(i); j++) {
                                    if(i % j == 0)
                                    {
                                        continue outer;
                                    }
                                }
                                nums.add(i);
                            }
                            //使用Toast显示统计出来的所有质数
                            Toast.makeText(MainActivity.this, nums.toString(), Toast.LENGTH_LONG).show();
                        }
                    }
                };
                Looper.loop();
            }
        }
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.handlerdemo1);
            et = (EditText) findViewById(R.id.et);
            calThread = new CalThread();
            calThread.start();
        }
    
        public void computer(View v)
        {
            //创建消息
            Message msg = new Message();
            msg.what = 0x123;
            Bundle bundle = new Bundle();
            bundle.putInt(UPPER_NUM, Integer.parseInt(et.getText().toString()));
            msg.setData(bundle);
            //向新线程中的Handler发送消息
            calThread.handler.sendMessage(msg);
        }
    }
    

    效果

    Screenshot_20171026-134515.png

    提示

    尽量避免在UI线程中执行耗时操作。

    相关文章

      网友评论

        本文标题:浅谈Handler

        本文链接:https://www.haomeiwen.com/subject/opchgxtx.html