3.1异步消息处理机制-handler

异步消息处理机制-handler详解

    1. 什么是handler
    1. handler的使用方法
      postRunnable,sendMessage
    1. handler机制的原理
      handler,looper,messagequeue,ThreadLooper四者的关系
    1. handler引起的内存泄露以及解决办法
      非静态内部类持有外部类的引用造成的

1.什么是handler

现象

1.异常1:android.view.ViewRootImpl$CalledFromWrongThreadException:Only the original thread that created a view hierarchy can touch its views.

    1. 根本原因是,android的View不是线程安全的,程序主线程负责UI的展示,UI事件消息的分发,主线程也称作UI线程
    1. Handler机制,可以通过handler在子线程发送消息给主线程来更新UI,子线程可以做耗时操作,用handler将结果切换的主线程处理
      1. 简单讲:handler是更新UI的机制
      1. 复杂点:在一些场景中,文件的读取,网络数据的获取,这些耗时操作完成后需要在UI线程做出改变,耗时操作在子线程做的,又由于android是不安全的(??怎样的不安全),不能再子线程中更新UI.
    1. 总结:handler通过发送和处理Message和Runnable对象来关联相对应MessageQueue
      1. 可以让对应的Message和Runnable在未来的某个时间点进行相应的处理
      1. 让自己想要处理的耗时操作放在子线程,让更新UI的操作放在主线程。

2.handler的使用方法

    1. 方式1:handler.post(Runnable),2,handler.sendMessage(Message)


      图1

      步骤:

        1. 创建:Handler mHandler = new Handler();创建的handler会绑定到当前所在的线程,在Activity中也就是主线程
      • 2 发送消息 mHandler.post(Runnable)
    1. 方式2 :sendMessage


      图2
        1. 创建Handler,复写handleMessage方法
        1. Message msg = Messager.obtain();
          msg.what = 1;
          msg.arg1 = xx;
          msg.arg2 = xx;
        1. handler.sendMessage(msg);

3.handler机制的原理

    1. handler,looper,messagequeue,ThreadLooper四者的关系

    逻辑图:


    UML图:


    图3

ThreadLocal 图见1

    #sThreadLocal:ThreadLocal<Looper>
    -mMainLooper:static Looper
    #mQueue:MessageQueue
    #mThread:Thread
    ----------------
    +prepare()
    +looper()
    +myLooper()
    +quit()

Looper

图4

图5

图6
    其中+Looper有一个mQueue消息队列
    MessageQueue
    #mMessages:Message
    -------------------
    +isIdle():boolaen
    #next():Message
    +enqueueMessage(msg:Message,when long)
    +removeMessage()

MessageQueue

图5

    其中MessageQueue有一个mMessages消息
    Message
    +what int
    +arg1 int
    +arg2 int
    +obj  Object
    #when long
    #target Handler
    #next Message
    ---------------------
    +Obtain() Message
    +recycle()

Message

    其中+Message有一个Handler对象
    Handler
    #mQueue:MessageQueue
    #mLooper:Looper
    -----------------------
    +dispatchMessage(msg:Message)
    +handleMessage(msg:Message)
    +obtainMessage():Message
    +sendMessage(msg:Message)
    +removeMessage()
    1. Looper是每一个线程所独有,Looper通过looper方法,读取MessageQueue的消息,读到消息后,把消息发送给handler来处理。
  • 2.MessageQueue是一个消息队里,只是一个先进先出的方式来管理message。
    • 1.在创建Looper时会同时创建MessageQueue,二者会关联在一起

    • 2.Message是消息对象

    • 3.Handler:两个作用:1.发送消息 2.处理消息

        1. handler只能将消息发送到自己相关的线程,也就是他相关线程的MessageQueue当中
        1. 而MessageQueue又是跟Looper相关联的,所以说handler要发送消息必须要有一个looper。所以这三者关系到了一起

现象

抛异常:Can‘t craeate handler inside thread that has not called Looper.prepare();

构造方法中:

    public Handler(Callback callback,boolean async){
        //xx判断
        mLooper = Looper.myLooper();
        //判断mLooper是否为空,为空 抛异常:Can‘t craeate handler inside thread that has not called         Looper.prepare();
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }
    
    其他线程使用
        Looper.prepare();
        Hander handler = new Handler()
        Looper.loop();
        
    其中Looper类中mLooper.myLooper(){
        return sThreadLocal.get();
    }
    

sThreadLocal 是什么呢

  • 就是不同的线程访问同一个ThreadLocal,不管是set还是get方法,他们对ThreadLocal的所有的读写操作仅限于各自的线程内部,这就是为什么handler里面要通过ThreadLocal来保存looper对象,这样他就可以使每一个线程有一个唯一的looper

疑问:这里用的是ThreadLocal的get方法,那set方法什么时候调用呢

其中Looper类中
public static void prepareMainLooper(){
    prepare(false);
    synchronized(Looper.class){
        if(mMainLooper !=null){
            throw new IllegalStateException("The main looper has already bean prepared.")
        }
        mMainLooper = myLooper();
    }
}
其中Looper类中
public static void prepare(boolean quiallowed){
    if(sThreadLocal.get()!=null){
        throw new RuntimeException("Only one Looper may be created per thread")
    }
    sThreadLocal.set(new Looper(quitAllowed));
}
    1. 这个set方法,将Looper设置到ThreadLocal中,保证了每个线程looper对象的唯一性
    1. 这时候整个MessageQueue通过Looper和线程关联上了,这样我们可以在不同的线程访问不同的消息队列
    1. 回到Handler的构造方法:由于在handler构造方法中创建了looper,接着又根据mQueue = mLooper.mQueue.这样hanlder和MessageQueue关联到了一起,而MessageQueue通过Looper来管理,这是三者最重要的机制。
    1. 前面说,只能在主线程(UI线程)更新UI,就是因为每一个Handler要与主线程的消息队列关联上,所以一定要在主线程创建Handler(构造方法会将所在线程的mQueue进行绑定),而不能在内部类创建Handler的原因,这样就能将处理Message在主线程执行,就不会抛出最开始的异常

Looper 相关
post(Runnable),sendMessage方法都需要先开启Looper.looper()开启轮询才能走起来,

Looper.loop(){
    final Looper me = myLooper();
    if(me ==null) //throw new RuntimeException("No Looper;Looper.prepare() wasn't called on the thread")
        final MessageQueue queue = me.mQueue;
        for(;;){
            Message msg = queue.next();
            if(msg == null){
            return;
            }
            final Printer logging = me.mLogging;
            if()xxx
            try{
                msg.target.dispatchMessage(msg);
            }finally{}
        }
}

创建了一个死循环,从消息队列中逐个的获取消息

总结Looper

    1. Looper通过prepare创建Looper并保存在ThreadLocal中,然后通过loop来进行消息的分发msg.target.dispatchMessage(msg);
    1. 这个target就是Handler,handler发送消息给消息队列,消息队列执行时交给handler处理,一个是发送消息,一个是处理消息

其中Handler类中

public void dispatchMessage(Message msg){
        if(msg.callback !=null){
            handleCallback(msg);
        }else{
            if(mCallback !=null){
                if(mCallback.handleMessage(msg)){
                    return;
                }
            }
            handleMessage(msg);
        }
}
  • 这个方法是一个中转器(分发器),先判断msg.callback是否为空,
    不为空则:
private static void handleCallback(Message message){
        message.callback.run();就是调用的线程的Runn方法
}

4.handler引起的内存泄露以及解决办法

    1. 非静态内部类持有外部类的引用造成的
    • 在Activity中private Handler mHandler = new Handler();
    • 由于不是静态内部类,所以会引用外部即、Activity的引用,
    1. 当Activity要回收时,Handler可能在做一些耗时操作,没有释放引用,导致Activity不能及时销毁
      1. 解决方式,改用private static Handler = new Handler()
      1. 处理完使用removeCallBack
    1. 若在内部类需要引用外部对象,采用弱引用
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,634评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,951评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,427评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,770评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,835评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,799评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,768评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,544评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,979评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,271评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,427评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,121评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,756评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,375评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,579评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,410评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,315评论 2 352

推荐阅读更多精彩内容