浅谈Handler

简介: 前言积土成山,风雨兴焉;积水成渊,蛟龙生焉;积善成德,而神明自得;Handler消息传递机制出于性能优化考虑,Android的UI操作并不是线程安全的,这意义着如果有多个线程并发操作UI组件,则可能导致线程安全问题。

前言

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

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秒更改一次
    }
}

效果

img_636daa29fe3d2ebe3dedba774b16b9cc.png
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);
    }
}

效果

img_28bd285fa6ddf2d455d0483a6410d3a8.png
Screenshot_20171026-134515.png

提示

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

目录
相关文章
|
6月前
|
XML 自然语言处理 JavaScript
【车载Android】使用自定义插件实现多语言自动化适配
2024年中国跃居全球第一大汽车出口国,车载Android应用全球化需求激增。为解决多语言适配繁琐、易错问题,开发者推出开源插件`MultilingualPlugin`,支持Excel驱动翻译、自动生成功能,覆盖多模块工程与增量更新,大幅提升开发效率。
283 2
【车载Android】使用自定义插件实现多语言自动化适配
|
Java 关系型数据库 MySQL
JVM深入原理(六)(二):双亲委派机制
自定义类加载器打破双亲委派机制的方法:复写ClassLoader中的loadClass方法常见问题:要加载的类名如果是以java.开头,则会抛出安全性异常加载自定义的类都会有一个共同的父类Object,需要在代码中交由父类加载器去加载自定义类加载器不手动指定parent会默认指定应用类加载两个自定义类加载器加载同一个类会被认为是两个对象,只有相同的类加载器+想通的类限定名才会被认为是一个对象。
392 0
|
缓存 Java 数据库
Android的ANR原理
【10月更文挑战第18天】了解 ANR 的原理对于开发高质量的 Android 应用至关重要。通过合理的设计和优化,可以有效避免 ANR 的发生,提升应用的性能和用户体验。
810 56
|
Web App开发 前端开发 JavaScript
前端开发必备神器大公开,用过的人都哭了:效率翻倍不是梦!
前端开发结合了创意与技术,本文介绍了几个提升开发效率的工具:Visual Studio Code、Webpack、Postman、GitHub 和 Chrome DevTools。这些工具分别在代码编辑、模块打包、API 测试、版本控制和网页调试等方面发挥重要作用,帮助开发者提高工作效率,优化项目管理。
351 4
|
前端开发 JavaScript 开发工具
2024年前端开发者的终极工具集
前端开发领域不断演进,新工具层出不穷。为了帮助前端开发者保持领先,本文介绍了2024年最前沿的前端开发工具,包括 VS Code、Webpack、React、Vue.js、Angular、TypeScript、Sass、PostCSS、Figma 和 Netlify。这些工具涵盖了代码编辑、模块打包、UI构建、样式处理、设计与部署等多个方面,能够显著提升开发效率和应用质量。选择合适的工具组合,可助你事半功倍,保持竞争力。
|
前端开发 JavaScript 算法
|
设计模式 测试技术 容器
依赖注入与控制反转:理解与应用
【8月更文挑战第22天】
757 0
|
存储 监控 Java
JVM内存模型详解,看不懂明天你就不用来了!
线程独占 每个线程都会有它独立的空间,随线程生命周期而创建和销毁 线程共享 所有线程能访问这块内存数据,随虚拟机或者GC而创建和销毁
JVM内存模型详解,看不懂明天你就不用来了!
|
API Android开发 开发者
AppCompat 用了这么久,你真的了解吗?
为了能够让低版本的Android系统能够运行新特性,AppCompat框架自Support时代就已推出。但随着AndroidX的一统江湖,AppCompat的相关类则一并迁移到了AndroidX库里。
1050 0
|
缓存 监控 安全
❤️Android Runtime (ART) 和 Dalvik❤️
目录 1. Dalvik 1.1 Dalvik 和 JVM 区别 1.2 Dalvik 如何运行 java 1.3 dex文件 1.4 65535 2. Android Runtime (ART) 2.1 ART 功能 2.1.1 预先 (AOT) 编译 2.1.2 垃圾回收方面的优化 2.1.3 开发和调试方面的优化 2.2 Android 8.0 中的 ART 功能改进 2.2.1 并发压缩式垃圾回收器 2.2.2 循环优化 2.2.3 类层次结构分析 2.2.4 .oat 文件中的内嵌缓存 2.2.5 Dexlayout 2.2.6 Dex 缓存移除
838 0
❤️Android Runtime (ART) 和 Dalvik❤️