Android开发中对Handler使用的一些总结

写在最前端

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

转发请标明出处 https://blog.csdn.net/Dream_201603/article/details/85246503

Handler运行机制

Handler的消息机制如下图所示,主要包含两个消息队列,一个是消息的回收队列,另一个是Message Q队列。

  • 1 消息的回收队列:消息回收队列是为Handler提供消息对象的,当Handler需要发送消息时,首先从消息回收队列中获取已被清空数据的消息对象,若消息对队列中此时没有消息对象,则创建新的消息对象。当消息对象被使用后,不会直接被当做垃圾回收,而是会进入消息的回收队列,在该队列中会将消息对象上的所有数据清空,之后在队列中等待被使用。

  • 2 获取消息 :直接通过以下两种方式中的任一种获取消息。

        Message message = Message.obtain();
        Message message1 = new Message();
  • 3 创建Handler对象: 直接通过如下方式创建Handler对象即可。该Handler默认使用Android提供的Looper。

              Handler handler= new Handler();
    
  • 4 发送消息:通过如下等方式进行消息的发送。

        handlerUI.sendEmptyMessage(11);//立刻发送空的消息
        handlerUI.sendMessage(message);//立刻发送携带数据的消息
        handlerUI.sendMessageDelayed(message,1000);//延迟1000ms后发送携带数据的消息
  • 5 Message Q队列:消息队列,用来管理消息,会根据消息的执行时间对消息进行排序。并通过Looper.loop对消息进行轮询取出,当队列中有消息时就将消息取出交给Handler的handlerMessage()回调进行消息的处理,当队列中没有消息时就进行阻塞等待,这种机制又被称作等待唤醒机制。Android中的等待唤醒机制是使用的Linux内核的管道流机制。

  • 6 消息的处理:通过Looper.loop()取出消息队列中待处理的消息,通过handler的handlerMessage()回调进行消息的处理。处理完成的消息对象会进入消息的回收队列进行回收。

image

使用Handler向主线程发送消息

通过handler向主线程发送消息是Handler使用过程中最常规的一种方式,也是最普遍的一种使用方式。当创建Handler对象时,若使用空参构造创建,则创建出的Handler默认使用UI线程中的Looper,这个时候通过Looper.loop()取出的消息在handlerMessage()中进行处理时,是在UI线程中进行处理的。这也就是为什么大家常说:Handler是运行在主线程中的。

这又分成两种情况:

  • 1 主线程->主线程 发送消息:在主线程中通过创建好的handler调用sendMessage()方法发送消息即可。

  • 2 子线程->主线程 发送消息:在子线程中通过创建好的handler调用sendMessage()方法发送消息即可。

本次以子线程向主线程发送消息为例:

public class MainActivity extends AppCompatActivity {

    private TextView textView;
    private static final int HANDLER_UI = 1;

    //运行在主线程的Handler:使用Android默认的UI线程中的Looper
    public Handler handlerUI = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case HANDLER_UI:
                    String strData = (String) msg.obj;
                    textView.setText(strData);    
                    break;

                default:
                    break;
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        textView = (TextView) findViewById(R.id.tv);
        findViewById(R.id.btn_ui).setOnClickListener(v->{
            new Thread(()->{
                Message message = Message.obtain();
                message.what = HANDLER_UI;
                message.obj = "发送消息的线程名称:" + Thread.currentThread().getName();
                handlerUI.sendMessage(message);
            }).start();
        });
    }
}

运行结果如下:

image

上述代码有一定的隐患,因为如此使用Handler会导致内存泄露,此处是为了举例简洁才如此使用,正常使用Handler时要避免这种方式,解决这种导致内存泄露可通过——WeakReference(弱引用),后面会详细介绍。

使用Handler向子线程发送消息

上述说的是Handler常规的使用方式,但有时因为业(zi)务(ji)需(xia)求(xiang),需要向子线程发送消息。这时候就需要寻找突破点了,仔细观察其实很容易发现,消息机制最核心的点就是消息的Looper机制,UI线程之所以能够进行消息的实现是因为Android默认提供了一个Looper,含有Looper的线程被称为UI线程,UI线程和子线程之间唯一的区别就是一个有Looper另外的一个没有。既然突破点已经找到,那直接创建一个含有Looper的子线程,即可实现向该子线程发送消息。那如何创建一个含有Looer的子线程能?需要两步:

  • 1 在创建的子线程的run()方法中进行Looper相关的配置。

  • 2 在创建Hander时的构造方法中传入1线程中的Looper。

代码实现如下:

    private Handler threadHandler;  
    private static final int HANDLER_THREAD = 2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        createLooperHandler();

        textView = (TextView) findViewById(R.id.tv);
        findViewById(R.id.btn_thread).setOnClickListener(v -> {
            new Thread(() -> {
                Message message = Message.obtain();
                message.what = HANDLER_THREAD;
                message.obj = "发送的线程:" + Thread.currentThread().getName();
                threadHandler.sendMessage(message);
            }).start();
        });
    }

    /**
     * 创建一个可以包含looper的子线程,并开启
     */
    private void createLooperHandler() {
        MyThread myThread = new MyThread();
        myThread.start();

        SystemClock.sleep(100);    //注释1
        threadHandler = new Handler(myThread.threadLooper) {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                switch (msg.what) {
                    case HANDLER_THREAD:
                        //添加上当前线程名称
                        String thrMsg = (String) msg.obj + "\n 收到的线程:" + Thread
                                .currentThread().getName();
                        runOnUiThread(() -> textView.setText(thrMsg));
                        break;

                   default:
                        break;
                }
            }
        };
    }

    private class MyThread extends Thread {

        private Looper threadLooper;
        @Override
        public void run() {
            Looper.prepare();
            threadLooper = Looper.myLooper();
            Looper.loop();
        }
    }
}

运行结果如下:

image

通过上面的方法,确实可以实现向子线程发送消息,但是其实还是有隐患的,眼尖的人已经看到上面29行(注释1)处的代码了,为什么要进行睡眠100毫秒,其实是因为MyThread在调用start方法后,代码向下运行,进行了创建Handler的操作,在创建Handler时需要在构造方法中传入MyThread的Looper,而只有当MyThread开启调用run()方法后才会创建出Looper,两者操作是在不同线程中,所以不能确定谁会先被执行。故,如果不进行睡眠的话,就会概率性出现空指针异常。那如何才能保证既可以向子线程发送消息,主线程又不用出现睡眠等待呢?其实android也知道这种情况,所以向我们提供了一个类HandlerThread来解决上述问题。

HandlerThread的使用

HnadlerThread是Thread的子类,是专门向子线程发送消息使用的。使用步骤如下:

 private Handler handlerThreadHandler;
 private static final int HANDLER_THREAD_HANDLER = 3;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        createHandlerThread();

        findViewById(R.id.btn_handler_thread).setOnClickListener(v -> {
            new Thread(() -> {
                Message message = Message.obtain();
                message.what = HANDLER_THREAD_HANDLER;
                message.obj = "发送的线程:" + Thread.currentThread().getName();
                handlerThreadHandler.sendMessage(message);
            }).start();
        });
    }

    private void createHandlerThread() {
        HandlerThread handlerThread = new HandlerThread("handler_name1");
        handlerThread.start();

        handlerThreadHandler = new Handler(handlerThread.getLooper()) {

            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                switch (msg.what) {
                    case HANDLER_THREAD_HANDLER:
                        //添加上当前线程名称
                        String thrMsg = (String) msg.obj + "\n 收到的线程:" + Thread
                                .currentThread().getName();
                        runOnUiThread(() -> textView.setText(thrMsg));
                        break;

                    default:
                        break;
                }
            }
        };
    }

运行结果如下:

image

Handler内存泄露

前面说到,使用Handler时最需要注意的就是内存泄露问题,内存泄露通俗来讲就是:对象使用完成将要回收,但是对象的引用还被其他类所持有,导致对象无法被GC回收。当前通用的解决方案是:Handler使用静态类,内部通过弱引用的方式来持有对象的引用。具体如下:

public static class MyHandler extends Handler {
        WeakReference<Activity> mWeakReference;

        public MyHandler(Activity activity) {
            mWeakReference = new WeakReference<Activity>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            Activity activity = mWeakReference.get();
            if (activity == null) {
                return;
            }

            switch (msg.what) {
                case 101:
                    adapter.notifyDataSetChanged();
                    break;

                default:
                    break;
            }
        }
    }

总结

以上只是对Handler使用的一个整体概述,包含向主线程发送消息和向子线程发送消息,基本囊括了Handler使用的所有场景。指示探索过程中,总有未知和不足,不足之处还望指正。

---------------------分------------割------------线--------------------------

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容