• 【Android】Handler 使用


    【Android】Handler 使用

    0x1 前言

    Android中的消息机制是通过Handler来实现的。随着EventBus和RxJava等依托观察者模式的消息传递机制的出现,当前在Android开发中Handler的使用已经不如之前那么重要,但是Android系统所提供的Handler中的各种编程思路和设计方案,对我们在编程思想的提升还是有很大益处的。

    0x11 Handler是什么

    Handler是Android给我们提供用于更新UI的一套机制,也是一套消息处理机制。用它可以发送消息,也可以用它处理消息。在Android开发中有着非常重要的地位。

    0x12 为什么要使用Handler

    当一个应用程序运行时,它会创建一个进程。这个进程就是我们的主线程(UI线程&Activity Thread) 。在主线程中,会默认为我们在系统中默认创建一个Looper,这个Looper会与我们的Message Queue主线程有一定联系。 在main线程中,主要是运行一个Message Queue,管理着顶级的应用程序(Activity,Boardcast Receiver…)这些顶级应用程序在默认情况下都会在主线程中创建。这就是为什么我们需要在主线程中更新UI。

    Android在设计的过程中,就封装了一套消息创建、传递、处理的机制。如果不遵循这样的机制,是没有办法更新UI信息的,会抛出异常信息。

    非主线程更新UI的后果

    可以尝试在一个新的线程中更新UI,会发现程序崩溃了。查看Logcat可以看到这样的一句提示。

    Only the original thread that created a view hierarchy can touch its views.

    所以在实际开发中,需要遵循Google为我们设定的这样的机制。

    那么如何在其他线程达到更新UI的目的呢?使用Handler就是其中一种办法。

    0x13 Handler的作用

    根据Android Developer网站上的描述,Handler主要有两个用途。

    • 定时地去发送一个Message或Runnable对象。
    • 可以跳转到另一个线程中去执行一些操作。

    0x14 Handler的使用方式

    0x141 关于Message

    参数

    public int arg1(arg2):如果只需要存储几个整型数据,arg1 和 arg2是setData()的低成本替代品。
    public Object obj: 发送给接收者的任意对象。当使用Message对象在线程间传递消息时,如果它包含一个Parcelable的结构类(不是由应用程序实现的类),此字段必须为非空(non-null)。其他的数据传输则使用setData(Bundle)方法。
    public Messenger replyTo:用户自定义的消息代码,这样接受者可以了解这个消息的信息。每个handler各自包含自己的消息代码,所以不用担心自定义的消息跟其他handlers有冲突。
    
    • 1
    • 2
    • 3

    方法

    另外 ,用Message来传递还可通过Message的**setData(Bundle)方法来传递。获取时调用getData()**方法。与sendData相似的还有peekData。

    public void setData(*Bundle data):设置一个任意数据值的Bundle对象。如果可以,使用arg1和arg2域发送一些整型值以减少消耗。
    public Bundle peekData():与getData()相似,但是并不延迟创建Bundle。如果Bundle对象不存在返回nullpublic Bundle getData():获取附加在此事件上的任意数据的Bundle对象,需要时延迟创建。通过调用setData(Bundle)来设置Bundle的值。需要注意的是,如果通过Messenger对象在进程间传递数据时,需要调用Bundle类的Bundle.setClassLoader()方法来设置ClassLoader,这样当接收到消息时可以实例化Bundle里的对象。
    public static Message obtain(): 从全局池中返回一个新的Message实例。在大多数情况下这样可以避免分配新的对象。
    
    • 1
    • 2
    • 3
    • 4

    0x15 handleMessage方法

    handleMessage方法用于接收Message对象并进行相应的处理,对应Handler的sendMessage方法。

    使用时应在handler中重写此方法。当在其他线程调用sendMessage方法时,handleMessage方法便会被回调,并携带sendMessage方法调用者在Message中存入的信息。当我们想要在其他线程更新UI时,就可以用主线程中创建的Handler调用sendMessage方法,然后在该Handler重写的handleMessage方法中做相应的处理。

    比如此处,我们在handleMessage方法中进行更新TextView的操作,并把Message的arg1作为文本的内容。

    public class MainActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            new Thread() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(2000);
                        Message message = Message.obtain();
                        message.arg1 = 88;
                        mHandlerT.sendMessage(message);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }.start();
        }
    
        private final Handler mHandler = new Handler() {
            public void handleMessage(Message msg) {
                TextView mTextView = findViewById(R.id.textView);
                mTextView.setText("" + msg.arg1 + "-" + msg.arg2);
            }
        };
    
        private final Handler mHandlerT = new Handler(msg -> {
            TextView mTextView = findViewById(R.id.textView);
            mTextView.setText("" + msg.arg1 + 1 + "-" + msg.arg2 + 2);
            return true;
        });
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    0x16 obtainMessage方法

    有的时候,我们不需要创建一个新的Message对象,可以去复用系统的Message对象。这时可以调用obtainMessage()方法,就会为我们返回一个Message对象。然后就可以直接用其来发送消息。

    public class MainActivity extends AppCompatActivity {
    
        private TextView mTextView;
    
        private Handler mHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                Person person = (Person) msg.obj;
                mTextView.setText("name:" + person.getName() + " age:" + person.getAge());
            }
        };
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            mTextView = findViewById(R.id.textView);
            new Thread() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(2000);
                        Message message = mHandler.obtainMessage();
                        Person person = new Person();
                        person.setName("后端码匠");
                        person.setAge(23);
                        message.obj = person;
                        mHandler.sendMessage(message);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }.start();
        }
    
        class Person {
            private String name;
            private int age;
    
            public String getName() {
                return name;
            }
    
            public void setName(String name) {
                this.name = name;
            }
    
            public int getAge() {
                return age;
            }
    
            public void setAge(int age) {
                this.age = age;
            }
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57

    0x17 post方法

    如何才能使用Handler在这个新建的线程更新UI呢?

    可以这样做:在主线程中创建一个Handler。然后在子线程中,我们可以调用Handler的post方法,并向其中传递一个Runnable为参数,在Runnable中更新UI即可。

    public class MainActivity extends AppCompatActivity {
    
        private TextView mTextView;
        private final Handler mHandler = new Handler();
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            mTextView = findViewById(R.id.textView);
            new Thread(() -> {
                try {
                    Thread.sleep(1000);
                    mHandler.post(new Runnable() {
                        @Override
                        public void run() {
                            mTextView.setText("update");
                        }
                    });
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    0x18 postDelayed方法

    postDelayed与post方法非常接近,仅仅是参数多了一个long类型的参数delayMills。它与post的区别就是它会在delayMills这段时间之后再去执行Runnable的方法,也就是延迟执行。

    有时候需要定时的完成一些事情(比如定时更换TextView的文字)时,就可以利用它延迟执行的这一特点来实现。做法是分别在主函数中以及它所执行的Runnable中postDelayed一段时间。这样就可以达到定时地完成某件任务的工作。

    public class MainActivity extends AppCompatActivity {
    
        private TextView mTextView;
        private Handler mHandler = new Handler();
        private String[] mTexts = {"但行好事", "莫問前程"};
        private int index = 0;
        private ChangeRunnable mRunnable = new ChangeRunnable();
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            mTextView = findViewById(R.id.textView);
            mHandler.postDelayed(mRunnable, 1000);
        }
    
        class ChangeRunnable implements Runnable {
            @Override
            public void run() {
                index = index % 2;
                mTextView.setText(mTexts[index++]);
                mHandler.postDelayed(mRunnable, 1000);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    0x19 removeCallbacks方法

    比如这里有个定时更新TextView的文本的代码,如果想要按下按钮,停止定时更换文本,就可以通过removeCallbacks方法,传入该Runnable来中止消息。

    public class MainActivity extends AppCompatActivity {
    
        private TextView mTextView;
        private Handler mHandler = new Handler();
        private String[] mTexts = {"但行好事", "莫問前程"};
        private int index = 0;
        private ChangeRunnable mRunnable = new ChangeRunnable();
    
        private Button mBtnRemove;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            mTextView = findViewById(R.id.textView);
            mHandler.postDelayed(mRunnable, 1000);
    
            mBtnRemove = findViewById(R.id.mBtnRemove);
            mBtnRemove.setOnClickListener(v -> mHandler.removeCallbacks(mRunnable));
        }
    
        class ChangeRunnable implements Runnable {
            @Override
            public void run() {
                index = index % 2;
                mTextView.setText(mTexts[index++]);
                mHandler.postDelayed(mRunnable, 1000);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    0x2 Handler导致的内存泄漏

    Handler使用过程中,需要特别注意一个问题,那就是Handler可能会导致内存泄漏。

    具体原因

    • Handler的生命周期与Activity不同,Handler会关联Looper来管理Message Queue。这个队列在整个Application的生命周期中存在,因此Handler不会因Activity的finish()方法而被销毁。
    • 非静态(匿名)内部类会持有外部对象,当我们这样重写Handler时它就成为了一个匿名内部类,这样如果调用finish方法时Handler有Message未处理的话,就会导致Activity不能被销毁。

    解决方法

    可以在外部新建一个类,这样便可解决这个问题。但这样无疑过于麻烦了,内部类更方便些。

    可以同时使用静态内部类和弱引用,当一个对象只被弱引用依赖时它便可以被GC回收。

    注意,要static和弱引用要同时使用,否则由于非静态内部类隐式持有了外部类Activity的引用,而导致Activity无法被释放。

  • 相关阅读:
    前置放大器和功率放大器有什么区别?
    [附源码]SSM计算机毕业设计教师业绩考核系统JAVA
    【C++】一文带你吃透string的模拟实现 (万字详解)
    【红外双目有监督】CATS系列数据集探索&加载器
    JavaScript 65 JavaScript 类 65.2 JavaScript 类继承
    扫雷小游戏(1.0版本)
    【前端】ECMAScript6从入门到进阶
    linux入门2—文件系统
    Java的String工具类源码
    基于预训练模型的多标签专利分类研究
  • 原文地址:https://blog.csdn.net/weixin_43874301/article/details/127956422