Android多线程开发

线程的基本使用:

目前大多数安卓应用是基于Java开发的,所以其线程的实现方式跟java一样,一般有以下三种:
1>新建一个继承自Thread的类,重写run()方法,在run()里面处理耗时操作,然后通过 new MyThread().start() 开启线程;

    class MyThread extends Thread {
        @Override
        public void run() {
            //处理耗时操作,比如网络请求
        }
    }

2> 使用继承的耦合度有点高,更多的时候我们会选择创建一个类,使其实现Runnable接口,通过new Thread(MyThread).start()开启线程;

    class MyThread implements Runnable{
        @Override
        public void run() {
            
        }
    }

3>更长见的方式是使用匿名内部类:

new Thread(new Runnable() {
    @Override
    public void run() {

    }
 }).start();

更新UI

我们一般认为,Android的主线程是线程不安全的,不能在子线程中更新UI,我们现在看个案例,当我点击文字的时候,在子线程中改变它的内容:

会报如下异常:

其实稍加琢磨,我们就会明白安卓这种设计方案的缘由,如果任何一个线程都可以对UI进行刷新,那么就会有这样的情况,A线程把当前界面的文字更新成“我是A线程”,B线程又把文字内容更新成“我是B线程”,这不乱套了吗,用户体验非常不好!即便这样,我们很多时候需要在子线程中获取网络数据、遍历用户目录等耗时操作执行完成之后,根据执行结果更新UI,那怎么玩?其实Android为此提供了一套异步消息处理机制,老规矩,先看我修改后的代码:
1>创建Handler实例,并重写handlerMessage方法,在此方法中刷新UI:

    Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case UPDATE_TETX:
                    mTvWelcome.setText(getString(R.string.string_welcome));
                    break;
                default:
                    break;
            }
        }
    };

2>在子线程中通过Handler实例发送一条消息:

            case R.id.tv_welcome://在子线程中改变文字,会报android.view.ViewRootImpl$CalledFromWrongThreadException这个异常
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        //耗时操作执行完后,发送消息给handler处理
                        Message message = Message.obtain();
                        message.what = UPDATE_TETX;
                        handler.sendMessage(message);
                    }
                }).start();
                break;

异步消息处理机制

Android中异步消息处理包含四个成员:Message、Handler、MessageQueue和Looper。
1>Message是线程之间传递的消息,其what、arg1、arg2字段可以携带整型数据,obj字段可以携带一个Object对象;
2>Handler是消息处理者,主要职责是发送和处理消息;
3>MessageQueue顾名思义也就是消息队列的意思,主要用于存放Handler发送的消息,这些消息会一直存放其中,等待被处理。每个线程中只有一个MessageQueue对象;
4>Looper是每个线程中的MessageQueue的管家,调用Looper的loop()方法后,就会进入一个无限循环中,当检测到MessageQueue中存在消息时,就会将它们一一取出,传递到Handler的handlerMessage()中处理,每个线程也只有一个Looper对象。
至此我们已经了解异步消息处理的四个成员,再总结一下整个使用流程:
1>首先,需要在主线程中创建一个Handler对象,并重写handleMessage()方法;
2>当子线程处理完耗时操作后,需要将处理结果反馈到UI中时,此时需要创建一个Message对象,并让其what字段携带一个int值,然后通过Handler对象发送出去;
3>之后该消息会被添加到MessageQueue中等待被处理,而Looper会一直尝试从MessageQueue中取出待处理消息,最后分发回Handler对象中的handleMessage()方法中。
ps:由于Handler对象是在主线程中创建的,所以可以在handleMessage()方法中安心地进行UI操作。
异步消息处理机制流程图:

彩蛋

我之前修改的代码有如下提示:

大概意思是说这么写会导致内存泄漏,怎么就泄露了呢?其实提示内容中也提到了,“由于此Handler被声明为内部类,因此可能会阻止外部类被垃圾回收。如果Handler使用Looper或MessageQueue作为主线程以外的线程,则没有问题。如果Handler正在使用主线程的Looper或MessageQueue,则需要修复Handler声明”。可能大家还是不太懂,我的理解是这样的:当我们通过Handler对象的sendMessage()方法发送一个Message对象时,该Message对象持有该Handler对象的引用(正是依靠这个引用,Looper在消息队列中取出该Message对象后,才能准确地将该Message对象分派回该Handler对象)。而我们在主线程中创建Handler对象时,为了重写其handleMessage()方法,使用了匿名内部类的方式来创建该Handler对象。而匿名内部类和非静态内部类都是隐性地持有一个对外部类的引用。所以,该Handler对象持有外部类MyServiceActivity的引用。
那么问题来了:Message对象持有Handler对象引用,Handler对象持有该Activity的引用,如果Message对象在子线程中被发送至消息队列,然后一直没有被处理,该Activity所在的主线程也会一直挂着,而不会被内存回收。所以,会导致内存泄露。
既然知道了缘由,怎么解决呢?

其实提示信息已经给出了解决方案,那就是通过静态内部类的方式创建Handler,因为静态内部类不会持有外部类对象的引用。但是这样还不够,因为我要在handlerMessage()中刷新UI,没有外部类引用怎么访问外部类的成员?终极方案:对于这种使用了静态内部类来避免内存泄露,同时又需要调用外部类成员的情况:可以使用弱引用,也就是说我们在该内部类中声明一个对外部类对象的弱引用。这样即可以调用外部类的成员,又不会导致内存泄露。
这下大家应该清楚了,且看我修改后的代码:

    //创建Handler对象
    private MyHandler myHandler = new MyHandler(this);

    private static class MyHandler extends Handler {
        //使该内部类持有外部类MyServiceActivity的弱引用
        private WeakReference<MyServiceActivity> weakReference;

        public MyHandler(MyServiceActivity activity) {
            weakReference = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case UPDATE_TETX:
                    MyServiceActivity myServiceActivity = weakReference.get();
                    myServiceActivity.mTvWelcome.setText(myServiceActivity.getString(R.string.string_welcome));
                    break;
                default:
                    break;
            }
        }
    }

此篇博文至此告一段落,如果各位同学有任何疑问 ,请私信或者评论区留言与我交流~

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值