Android开发(4):多线程之Handle

一、多线程

1. 主线程(UI线程)和子线程(非UI线程)

我们在启用程序的时候,一般会创建一个主线程(也称UI线程),对UI更新只能在主线程中进行,比如你无法新建一个子线程(也称非UI线程)来实现对TextView的setText函数的调用,你只能在主线程中更新UI。

为什么不能在子线程中更新UI呢,因为UI控件不是线程安全的,多线程并发可能带来不安全问题,比如在主线程和子线程同时修改TextView的内容,那么会出现更新不同步的问题,所以我们只能在主线程更新UI。

而为了提供良好的用户体验,我们必须保证程序有高响应性,所以不能在UI线程中进行耗时的计算或I/O操作,比如你想读取(或下载)很多张图片来设置UI界面的imageview,因为会耗时一定时间,你不能在主线程上执行这个操作,原因有:一是当读取图片时,其他操作都阻塞在这里,如果你在读取图片的同时,触发某按钮的点击事件,那事件会在读取图片之后才发生;二是如果主线程在5秒内没有响应,Android操作系统会关闭程序,如果读取图片耗时过长,那程序就会被动关闭。

所以我们会选择重新创建一个子线程来进行这些耗时的操作,但是前面也讲过,读取完图片之后是无法直接在子线程中进行imageview的操作的,我们需要回到主线程进行UI更新。

解决以上问题主要有以下三个方法,本文主要介绍第一种

  1. Handler
  2. AsyncTask
  3. RxJava

二、handle使用

1. handle机制

  • Looper通过一个死循环,当有消息Message加入队列时, 通过 FIFO的顺序处理消息。

  • 一个Message中包括了处理Message的Handler对象还有消息内容。

  • Handler与主线程是同一个线程,所以我们在子线程中完成计算之后,可以通过向消息队列(MessageQueue)加入一个消息,通知特定的 Handler去更改UI。

  • 使用handle有两种方式,一种是Post,一种是sendMessage。

  • 机制如下
    在这里插入图片描述

2. Post(Runnable)方法

btn1.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) { //点击事件
        new Thread(new Runnable() { //创建一个子线程
            @Override
            public void run() { //子线程的操作
                SystemClock.sleep(3000); //延迟3秒,当作是耗时
                final String text = "change";
                new Handler().post(new Runnable() { //post,回到主线程
                    @Override
                    public void run() { //回到主线程的操作,更新UI
                        tv1.setText(text);
                    }
                });
            }
        }).start;//Thread.start()
    }
});

如上所示,这个点击事件如果不使用子线程和handle,将会执行3s时间,而其他控件此时无法响应事件。具体解释已在注释中说清。

3. sendMessage(Message)方法

继承Handler类,重写handleMessage函数

public class Myhandle extends Handler{
    @Override
    public void handleMessage(Message msg) { //重写,在里面实现回到主线程的操作
        super.handleMessage(msg);
        if(msg.what == 1){ //这个什么意思后面讲
        	tv1.setText(msg.obj.toString()); //更新UI操作
        }
    }
}

//实例化一个
myhandle = new Myhandle();

以上也可以这样写,两者是一样的

private Handler Myhandle = new Handler(){
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        if(msg.what == 1){ //这个什么意思后面讲
        	tv1.setText(msg.obj.toString());
        }
    }
};

在点击事件里面创建一个子线程,重写run函数,注意观察与post的区别

btn1.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) { //点击事件
        new Thread(new Runnable() { //子线程
            @Override
            public void run() { //重写run函数,子线程的操作
                SystemClock.sleep(3000); //延迟3秒,当作是耗时
                
                //返回一个message实例,最好不要重新new,避免多次创建message
                //Message msg = myhandle.obtainMessage();和下面是一样的。
                Message msg = Message.obtain(); 
                msg.obj = "change";
                msg.what = 1;
       //将msg发送出去,这时会回到主线程,就是myhandle类的handleMessage去进行操作
                myhandle.sendMessage(msg); 
            }
        }).start();  
    }
});

Message解释

message是Handler接收与处理的消息对象,message可以设置what,obj,arg1以及arg2,what是标志位,用于区分不同子线程,进而在主线程进行不同的操作,obj是任一对象,用来传递数据,arg是int类型。

三、handle导致的内存泄露

1. 原因

当我们的handle是非静态内部类时,如果我们子线程还未完成工作时,我们就将Activity强行关闭,因为在Java中,非静态内部类和匿名类会引用了它们的外部类。此时handle会隐含的持有外部类的引用,即Activity,所以Activity就一直无法被回收,相当于它虽然被关闭了,但还被handle占用着,而handle被子线程占用着(因为子线程尚未执行完,而该线程持有Handler的引用),所以就会导致内存泄露。

2. 解决方法

  • 将handle声明为static,这样handle就不会持有外部类的引用(或变为弱引用)
  • 在关闭Activity前将所有子线程移除,这样就不会占用handle了
  • handler被delay的Message引用了,那么使用相应的Handler的removeCallbacks()方法,把消息对象从消息队列移除。

猜你喜欢

转载自blog.csdn.net/chenxz_/article/details/84351657
今日推荐