专注于 JetBrains IDEA 全家桶,永久激活,教程
持续更新 PyCharm,IDEA,WebStorm,PhpStorm,DataGrip,RubyMine,CLion,AppCode 永久激活教程

Android中Handler的基本使用

一、概述

在Android开发中,我们经常会遇到这样一种情况:在UI界面上进行某项操作后要执行一段很耗时的代码,比如我们在界面上点击了一个“下载”按钮,那么我们需要执行网络请求,这是一个耗时操作,因为不知道什么时候才能完成。为了保证不影响UI线程,所以我们会创建一个新的线程去执行我们的耗时的代码。当我们的耗时操作完成时,我们需要更新UI界面以告知用户操作完成了。

二、错误使用场景

可能会写出如下的代码:

package com.example.myapplication;

import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;

public class TestHandlerActivity extends AppCompatActivity {

    private TextView textview;
    private Button button;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test_handler);
        textview = (TextView)findViewById(R.id.textview);
        button = (Button)findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                DownloadThread downloadThread = new DownloadThread();
                downloadThread.start();
            }
        });
    }

    class DownloadThread extends Thread{
        @Override
        public void run() {
            try{
                System.out.println("开始下载文件");
                Thread.sleep(5000);//此处让线程DownloadThread休眠5秒中,模拟文件的耗时过程
                System.out.println("文件下载完成");
                textview.setText("success"); //文件下载完成后更新UI
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }
}

1、描述

上面的代码演示了单击“下载”按钮后会启动一个新的线程去执行实际的下载操作,执行完毕后更新UI界面。但是在实际运行到代码textview.setText(“success”)时,会报错如下: android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

2、原因

错误的意思是只有创建View的原始线程才能更新View。出现这样错误的原因是Android中的View不是线程安全的,在Android应用启动时,会自动创建一个线程,即程序的主线程,主线程负责UI的显示和UI事件消息的分发处理等等,因此主线程也叫做UI线程。textview是在UI线程中创建的,当我们在DownloadThread线程中去更新UI线程中创建的textview时自然会报上面的错误。

3、解决方案

Android的UI控件是非线程安全的,其实很多平台的UI控件都是非线程安全的,比如C#的.Net Framework中的UI控件也是非线程安全的,所以不仅仅在Android平台中存在从一个新线程中去更新UI线程中创建的UI控件的问题。不同的平台提供了不同的解决方案以实现跨线程更新UI控件,Android为了解决这种问题引入了Handler机制。

三、Handler是什么

那么Handler到底是什么呢?Handler是Android中引入的一种让开发者参与处理线程中消息循环的机制。每个Hanlder都关联了一个线程,每个线程内部都维护了一个消息队列MessageQueue,这样Handler实际上也就关联了一个消息队列MessageQueue。可以通过Handler将Message和Runnable对象发送到该Handler所关联线程的消息队列MessageQueue中,然后该消息队列MessageQueue一直在循环拿出一个消息Message,对其进行处理,处理完之后拿出下一个消息Message,继续进行处理,周而复始。当创建一个Handler的时候,该Handler就绑定了当前创建Hanlder的线程。从这时起,该Hanlder就可以发送Message和Runnable对象到该Handler对应的消息队列中,当从MessageQueue取出某个消息Message时,会让Handler对其进行处理。

四、Handler的作用

Handler可以用来在多线程间进行通信,在另一个线程中去更新UI线程中的UI控件只是Handler使用中的一种典型案例。

除此之外,通过Handler可以在一个线程中控制另一个线程。每个Handler都绑定了一个线程,假设存在两个线程ThreadA和ThreadB,并且HandlerA绑定了ThreadA,在ThreadB中的代码执行到某处时,出于某些原因,我们需要让ThreadA执行某些代码,此时我们就可以使用Handler,我们可以在ThreadB中向HandlerA中加入某些信息以告知ThreadA中该做某些处理了。由此可以看出,Handler是Thread的代言人,是多线程之间通信的桥梁,通过Handler,我们可以在一个线程中控制另一个线程去做某事。

五、Handler的使用

Handler提供了两种方式解决上边代码遇到的问题(在一个新线程中更新主线程中的UI控件),一种是通过post方法,一种是调用sendMessage方法。

1、使用post方法,代码如下:

package com.example.myapplication;

import android.os.Bundle;
import android.os.Handler;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;

public class TestHandlerActivity extends AppCompatActivity {

    private TextView textview;
    private Button button;

    //在主线程中创建mHandler,所以自动绑定主线程
    private Handler mHandler = new Handler();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test_handler);
        textview = (TextView)findViewById(R.id.textview);
        button = (Button)findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                System.out.println("Main thread id" + Thread.currentThread().getId());
                DownloadThread downloadThread = new DownloadThread();
                downloadThread.start();
            }
        });

    }
    class DownloadThread extends Thread{
        @Override
        public void run() {
            try{
                System.out.println("Download thread id" + Thread.currentThread().getId());
                System.out.println("开始下载文件");
                Thread.sleep(5000);//此处让线程DownloadThread休眠5秒中,模拟文件的耗时过程
                System.out.println("文件下载完成");
                //文件下载完成后更新UI
                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println("Runnable thread id + Thread.currentThread().getId());
                        textview.setText("success");
                    }
                });

            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }
}

我们在Activity中创建了一个Handler成员变量mHandler,Handler有个特点,在执行new Handler()的时候,默认情况下Handler会绑定当前代码执行的线程,我们在主线程中实例化了mHandler,所以mHandler就自动绑定了主线程,即UI线程。当我们在DownloadThread中执行完耗时代码后,我们将一个Runnable对象通过post方法传入到了Handler中,Handler会在合适的时候让主线程执行Runnable中的代码,这样Runnable就在主线程中执行了,从而正确更新了主线程中的UI。以下是程序运行结果:

58_1.png通过输出结果可以看出,Runnable中的代码所执行的线程id与DownloadThread的线程id不同,而与主线程的线程id相同,由此我们也看出,在执行了Handler.post(Runnable)这句代码之后,运行Runnable代码的线程与Handler所绑定的线程是一致的,而与执行Handler.post(Runnable)这句代码的DownloadThread线程无关。

2、使用sendMessage方法,代码如下:

package com.example.myapplication;

import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;

public class TestHandlerActivity extends AppCompatActivity {

    private TextView textview;
    private Button button;

    //在主线程中创建mHandler,所以自动绑定主线程
    private Handler mHandler = new Handler(){
        @Override
        public void handleMessage(@NonNull Message msg) {
            switch (msg.what){
                case 1:
                    System.out.println("handleMessage thread id " + Thread.currentThread().getId());
                    System.out.println("msg.arg1:" + msg.arg1);
                    System.out.println("msg.arg2:" + msg.arg2);
                    textview.setText("success");
                    break;
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test_handler);
        textview = (TextView)findViewById(R.id.textview);
        button = (Button)findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                System.out.println("Main thread id " + Thread.currentThread().getId());
                DownloadThread downloadThread = new DownloadThread();
                downloadThread.start();
            }
        });

    }
    class DownloadThread extends Thread{
        @Override
        public void run() {
            try{
                System.out.println("Download thread id " + Thread.currentThread().getId());
                System.out.println("开始下载文件");
                Thread.sleep(5000);//此处让线程DownloadThread休眠5秒中,模拟文件的耗时过程
                System.out.println("文件下载完成");
                //文件下载完成后更新UI
                Message msg = new Message();

                //what是自定义的一个Message的识别码,
                //作用:在Handler的handleMessage方法中根据what识别出不同的Message,以便我们做出不同的处理操作
                msg.what = 1;

                //arg1是自定义的一个Message的参数
                //作用:可以通过arg1和arg2给Message传入简单的数据
                msg.arg1 = 123;
                msg.arg2 = 321;

                //将该Message发送给对应的Handler
                mHandler.sendMessage(msg);

            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }
}

通过Message与Handler进行通信的步骤是:

1、 重写Handler的handleMessage方法,根据Message的what值进行不同的处理操作
2、 创建Message对象,虽然Message的构造函数式public的,我们还可以通过Message.obtain()或Handler.obtainMessage()来获得一个Message对象(Handler.obtainMessage()内部其实调用了Message.obtain())。
3、 设置Message的what值,Message.what是我们自定义的一个Message的识别码,以便于在Handler的handleMessage方法中根据what识别出不同的Message,以便我们做出不同的处理操作。
4、 设置Message的所携带的数据,简单数据可以通过两个int类型的field arg1和arg2来赋值,并可以在handleMessage中读取。
5、 如果Message需要携带复杂的数据,那么可以设置Message的obj字段,obj是Object类型,可以赋予任意类型的数据。或者可以通过调用Message的setData方法赋值Bundle类型的数据,可以通过getData方法获取该Bundle数据。
6、 我们通过Handler.sendMessage(Message)方法将Message传入Handler中让其在handleMessage中对其进行处理。

需要说明的是,如果在handleMessage中不需要判断Message类型,那么就无须设置Message的what值;而且让Message携带数据也不是必须的,只有在需要的时候才需要让其携带数据;如果确实需要让Message携带数据,应该尽量使用arg1或arg2或两者,能用arg1和arg2解决的话就不要用obj,因为用arg1和arg2更高效。 程序的运行结果如下:

58_2.png由上我们可以看出,执行handleMessage的线程与创建Handler的线程是同一线程,在本示例中都是主线程,与执行mHandler.sendMessage(msg)的线程没有关系。

六、总结

Android线程的原则:

1、 不能再非UI线程中更新UI;
2、 不能再UI线程中做耗时操作;

1、为什么不能在非UI线程中更新UI

因为Android的UI线程是非线程安全的。应用更新UI是调用invalidate()方法来实现界面的重绘,而invalidate()方法是非线程安全的,也就是说当我们在非UI线程来更新UI时,可能会有其他的线程或UI线程也在更新UI,这就会导致界面更新的不同步。因此我们不能在非UI主线程中做更新UI的操作。

2、为什么不能再主线程中做耗时操作

防止ANR。不能在UI主线程中做耗时的操作,因此我们可以把耗时的操作放在另一个工作线程中去做。操作完成后,再通知UI主线程做出相应的响应。这就需要掌握线程间通信的方式了-Handler机制。

特别感谢:

本文转自 blog.csdn.net/iispring/ar… 并做了加工

文章永久链接:https://tech.souyunku.com/27591

未经允许不得转载:搜云库技术团队 » Android中Handler的基本使用

JetBrains 全家桶,激活、破解、教程

提供 JetBrains 全家桶激活码、注册码、破解补丁下载及详细激活教程,支持 IntelliJ IDEA、PyCharm、WebStorm 等工具的永久激活。无论是破解教程,还是最新激活码,均可免费获得,帮助开发者解决常见激活问题,确保轻松破解并快速使用 JetBrains 软件。获取免费的破解补丁和激活码,快速解决激活难题,全面覆盖 2024/2025 版本!

联系我们联系我们