源码分析Handler机制

2020-01-16 09:04栏目:龙竞技官网
TAG:

看上边例子:

第风华正茂咱们来兑现主线程给子线程发送新闻的几个大约的例证,然后大家再带着主题素材去搞理解它们是怎么关联在联合签字的以至发送音信、收取音信、管理信息整个流程是怎样落实的。

近来在写风流洒脱篇内部存款和储蓄器泄漏的博客,还在圆满中,在那之中写到handler引起的内部存款和储蓄器泄漏,开采对Handler领会太过狭隘,于是百度查寻大神对Handler的解析,希图站在他们的肩膀上通晓一下Handler相关的源码。废话超级少说,赶紧上代码。

图片 1image.png在子线程发送三个音讯,然后在主线程街道这么些新闻处理,这几个音信是何等从子线程切换成主线程的吧?首先跟踪一下handler.sendMessage(new Message如下:图片 2image.png图片 3image.png从下面两图看辗转来到sendMessageAtTime方法,见到A处,此处mQueue是何方圣洁呢???看名字只是了然个大约是个新闻队列, 而B处看方法名都知道是将msg入队列了:mQueue在源码中两处被赋值,都以在布局方法里:图片 4image.png图片 5image.png本文那个事例创制Handler对象的时候调用的是其无参的布局方法,如下:图片 6image.png就是上上海教室C处所在的构造函数,从C、D处能够看出Handler对象的MessageQueue是looper对象的分子mQueue付与的,而looper对象又是哪些创立的吗?图片 7image.png嗯,这里作者归纳的解释一下ThreadLocall,表示每个线程都有和好的意气风发份备份,相互独立。而主线程是如什么时候候设置这么些ThreadLocal的吗??其实在自个儿以前的文章Activity生命周期回调是什么样被回调的?中源码分析的时候已经有关系到这点:图片 8image.png

/**主线程给子线程发送消息的Handler*/
private Handler threadHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_test_handler);
    tvTest = (TextView) findViewById(R.id.tvName);
    new MyThread().start();
}
public void testHandler(View v){
    threadHandler.sendEmptyMessage(0);
    L.D("消息发送者所在线程:" + Thread.currentThread());
}
class MyThread extends Thread{
    @Override
    public void run() {
        Looper.prepare();
        threadHandler= new Handler(){
            @Override
            public void handleMessage(Message msg) {
                L.D("收到主线程发送过来的消息" + "---所在线程:" + Thread.currentThread());
            }
        };
        Looper.loop();
    }
}

参照链接:https://halfstackdeveloper.github.io/2016/08/31/Android-Handler-%E5%8E%9F%E7%90%86%E5%88%86%E6%9E%90/

如上图E1处:

打字与印刷日志:
音讯发送者所在线程:Thread[main,5,main]
收受主线程发送过来的音信---所在线程:Thread[Thread-11699,5,main]

https://www.zhihu.com/question/34652589

图片 9image.png

说一下那边怎么可以够从主线程发送到子线程吧:threadHandler 在子线程实行初阶化的时候,布局参数若无传到Looper,那么内部会调用Looper.myLooper(卡塔尔国,给它的mLooper赋值,而Looper.myLooper(卡塔尔(قطر‎正是从当前子线程的ThreadLocal.get(卡塔尔国取三个Looper,因为我们前边调用了Looper.prepare(卡塔尔国,所以当前Looper轮询的是子线程的新闻,管理的也正是子线程的音讯。

http://blog.csdn.net/sdkfjksf/article/details/52777722

F处又跳转调用了prepare方法:

Handler、Looper、MessageQueue、Message那多少个类、及他们的部分属性
Handler:
final MessageQueue mQueue;
final Looper mLooper;

深远源码

图片 10image.png这里注意一下咯:Looper布局函数是私家的,在类外部是不可能直接new设置给一个线程的,不过也不用担忧,调用他的prepare这一个静态方法就能够了,所以这里记 住哦,Looper的prepare方法很注重,能够给一个线程“挂载”多少个Looper对象,好啊回到F处,接下去看下标识G处呗:图片 11image.png

Looper:
final MessageQueue mQueue;

Handler的使用

接下去看看上海体育地方中的E2处调用了Looper.loop方法:

MessageQueue:
Message mMessages;

先来回想一下大家一向都以怎么使用Handler的。

图片 12image.png图片 13image.png

Message:
Handler target;

step1:初步化三个Handler对象,重写其handleMessage(卡塔尔国方法:

从地点两张图能够看到,Looper.loop方法正是步向三个死循环,在循环体内连发地从队列收取消息,然后进行分发管理见标识E3处,嗯,上海教室你能够看来有二个E4,这里总计了音讯分发耗费时间,其实这里有一个行使,能够接收这里点计算UI线程是还是不是卡顿,像那几个标题标篇章已然是烂大街了,随意百度时而皆有,嗯,一时光笔者只怕也会写风度翩翩篇总括一下,回到地点提到的标志E3处:msg.target.dispatchMessage;大家驾驭这些msg是Message对象,而这么些target呢??它实际上是四个Handler对象,难题是其生机勃勃target又是何许时候被赋值的??好吧,你权且先记下那一个难点待会再说咯:

那么难题来了:

Handler mHandler=new Handler() {

图片 14image.png

主题素材意气风发:Looper.prepare(卡塔尔(قطر‎,Looper.loop(卡塔尔(قطر‎这多少个艺术是干嘛的,为啥要调用这七个艺术?

大家经过看Looper.prepare(卡塔尔方法就精通了:

public static void prepare() {    
    prepare(true);
}
private static void prepare(boolean quitAllowed) {  
    if (sThreadLocal.get() != null) {        
          throw new RuntimeException("Only one Looper may be created per thread");   
    }
    sThreadLocal.set(new Looper(quitAllowed));
}

实则就是为了获取八个Looper。这里先从ThreadLocal中取Looper,若无的话就new二个Looper,然后set到ThreadLocal中。
(ThreadLocal,它是用来累积线程中的一些变量,它有set get 方法。这里的我们存款和储蓄的变量正是Looper。关于ThreadLocal可以参照他事他说加以考察大神的博客:http://blog.csdn.net/singwhatiwanna/article/details/48350919)

收下里查看Looper的构造方法

  private Looper(boolean quitAllowed) {    
    mQueue = new MessageQueue(quitAllowed);    
    mThread = Thread.currentThread();
  }

大家发现在Looper的布局方法中会new MessageQueue(卡塔尔,给它的mQueue属性赋值。

接下去再看Handler的布局方法
注意:Handler成立时会选拔当下线程的Looper来营造内部的音信循环种类

public Handler(Callback callback, boolean async) {
    if (FIND_POTENTIAL_LEAKS) {
        final Class<? extends Handler> klass = getClass();
        if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                (klass.getModifiers() & Modifier.STATIC) == 0) {
            Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                klass.getCanonicalName());
        }
    }

    mLooper = Looper.myLooper();
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

能够观望在Handler布局方法中,调用了
mLooper = Looper.myLooper();
mQueue = mLooper.mQueue;
分级给它的mLooper、mQueue属性赋值;至此他们三者就涉嫌起来了。

@Override

好的,到最近停止你曾经知道新闻是如何派发何地被拍卖的了,继续往回翻,回到标识C处:

主题材料二:为啥handler.sendMessage(msg卡塔尔国之后,handler会回调handlerMessage(Message msg卡塔尔方法?(即:从new 贰个Message 到 handler 去处理那几个音讯整个工艺流程是怎么下来的。)

查看sendMessage(msg)源码,不管什么点子sendMessage,都会调用到下边包车型大巴办法:

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
    MessageQueue queue = mQueue;
    if (queue == null) {
        RuntimeException e = new RuntimeException(
                this + " sendMessageAtTime() called with no mQueue");
        Log.w("Looper", e.getMessage(), e);
        return false;
    }
    return enqueueMessage(queue, msg, uptimeMillis);
}

跟进去看enqueueMessage(queue, msg, uptimeMillis卡塔尔(قطر‎方法:

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

这么些主意正是将Message压入到音讯队列MessageQueue中(注意:MessageQueue而不是队列而是单链表,单链表在插入和删除上比较有优势。enqueueMessage方法第一句给msg的target属性赋值为Handler自身);大家精晓Looper是接连不断的从MessageQueue中取音信的,接下去我们看一下是他是怎么裁撤息的,查看一下Looper.loop(卡塔尔源码:

  public static void loop() {
    final Looper me = myLooper();
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    final MessageQueue queue = me.mQueue;
    // Make sure the identity of this thread is that of the local process,
    // and keep track of what that identity token actually is.
    Binder.clearCallingIdentity();
    final long ident = Binder.clearCallingIdentity();
    for (;;) {
        Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }
        // This must be in a local variable, in case a UI event sets the logger
        Printer logging = me.mLogging;
        if (logging != null) {
            logging.println(">>>>> Dispatching to " + msg.target + " " +
                    msg.callback + ": " + msg.what);
        }
        msg.target.dispatchMessage(msg);
        if (logging != null) {
            logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
        }
        // Make sure that during the course of dispatching the
        // identity of the thread wasn't corrupted.
        final long newIdent = Binder.clearCallingIdentity();
        if (ident != newIdent) {
            Log.wtf(TAG, "Thread identity changed from 0x"
                    + Long.toHexString(ident) + " to 0x"
                    + Long.toHexString(newIdent) + " while dispatching to "
                    + msg.target.getClass().getName() + " "
                    + msg.callback + " what=" + msg.what);
        }
        msg.recycleUnchecked();
    }
}

经过查看Looper.loop(卡塔尔国源码大家得以看出,里面是个for(;;卡塔尔{}死循环在一再的从MessageQueue中抽取Message,在for循环中大家得以看来,msg.target.dispatchMessage(msg卡塔尔(قطر‎,通过前面大家精通Message的target属性便是Handler,所以那边的disptachMessage(msg卡塔尔(قطر‎正是调用了Handler的disptachMessage(msg卡塔尔,大家去走访这一个点子:

public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}

能够看看,在Message的callback == null时候,就能够调用handleMessage(msgState of Qatar,那是个空方法,让大家和谐达成去管理新闻,如下:

public void handleMessage(Message msg) {}

到此地我们就知晓了,从new Messags(State of Qatar获得二个新闻Message,然后通过handler.sendMessage(msg卡塔尔把新闻归入MessageQueue中,然后Looper.loop不断从MessageQueue中抽取音讯,再把音信交给handler.handlerMessage(msg卡塔尔(قطر‎举办拍卖任何工艺流程。

public void handleMessage(Message msg){

图片 15image.png

主题素材三:为何我们在UI线程中接受Handler的时候,不用本人去调用Looper.prepare(卡塔尔和Looper.loop(State of Qatar?

因为ActivityThread在成立Main线程的时候会调用Looper.prepareMainLooper(卡塔尔(قطر‎;其实,这么些Looper.prepareMainLooper方法内部正是调用了Looper.prepare(State of Qatar方法,即获取七个Looper,所以就不必要大家和好去完结了。

//todo

为此C处只是收取当前线程“挂载”的Looper对象而已,接下去看D,“mLooper.mQueue”那确定是说mQueue是mLooper的分子呀,进去Looper内部商讨切磋分子布局:

如有错误的地点劳烦提示下,以便及时改进,辛苦。

}

图片 16image.png

};

于是到此地您早已领悟以下那个实际:

step2:发送音讯布告Handler管理:

  • Looper对象能够通过静态方法prepare“挂载”二个线程上
  • Handler对象内部用到的Looper、MessageQueue都是源头当前线程“挂载”的Looper(顺便提示一下,那些实际就表达了多少个线程要接受Handler此前要先让这一个线程“挂载”上三个Looper对象,哈哈,那又怎么让线程“挂载”上三个Looper呢?上文不是说了啊——调用prepare静态函数)
  • Looper的loop方法会运转三个死循环不断的从队列中读取音讯,然后将新闻送到Handler对象的handleMessage方法中拍卖

Message msg=mHandler.obtainMessage();

嗯,今后能够回去随笔起始的标志B处,这里能够看见当调用Handler对象来发送音信时,会调用enqueueMessage方法,将信息入队列:

msg.what=0;

图片 17image.png

mHandler.sendMessage(msg);

见上海教室,标志 I 处是的确的将音讯入队列,不过在入队列早先多做了风姿浪漫项操作见标志H处——将handler对象设置到msg音信对象的target成员中,到此上文这一个叫您一时存档的疑云也帮你撤除了。

咱俩平时会在主线程中张开step1操作,而在子线程中通过setp2来与主现场通讯,如实行UI操作。那么Handler毕竟是哪些成功线程之间通讯的吧?故事要从叁个叫Looper的玩意儿提起。

  • 一个线程在运用Handler早先要先在线程上挂载叁个Looper,android主线程在ActivityThread的main方法已经做了那几个挂载步骤,所以常常大家在主线程之间创设Handler对象式直接尚可的不会出错,不过在子线程创制Handler对象就非得先帮线程挂在二个Looper对象。

  • Looper的loop方法会开启三个死循环不断读取消息队列的消息,然后传给handler对象的handleMessage方法管理,那些loop方法在哪个线程被调用,handleMessage方法就在哪些线程被实行

  • handler对象在发送音讯的时候,会将音信入新闻队列,压入队列早前会将本人引用设置在Message音信对象的target成员方便loop死循环读取音讯之后散发新闻。

Looper是什么

在Android中,对于每二个线程,都足以创立一个Looper对象(最多三个!)和多少个Handler。

private static void prepare(boolean quitAllowed) {

if (sThreadLocal.get() != null) {

throw new RuntimeException("Only one Looper may be created per thread");

}

sThreadLocal.set(new Looper(quitAllowed));

}

Looper有如二个音信泵,源源不断的从音信池中拿到音信,交给Handler管理。

大家先来大致的看一下Looper类,Looper类中有有三个大家必须要要打听的变量:

private static final ThreadLocal sThreadLocal = new ThreadLocal();

private static Looper sMainLooper;  // guarded by Looper.class

final MessageQueue mQueue;

final Thread mThread;

上面定义了七个静态变量:一个ThreadLocal类型的变量sThreadLocal,叁个Looper类型的变量sMainLooper。还应该有多少个final变量:四个行列类型的mQueue,贰个线程类型的mThread。那七个变量至关心注重要。

一个线程想要使用Handler,就必须得创立一个Looper对象。那么创造三个Looper对象供给做怎么样呢?相当的轻巧,黄金时代行代码能够。(main线程中不要求成立Looper对象,因为在AvtivityThread.java类中的mian方法里早已成立了Looper对象,相关代码也是唯有风流倜傥行Looper.prepareMainLooper(卡塔尔;)

Looper.prepare();

//非UI线程起头化Handler必得加此代码不然报那三个: RuntimeException("No Looper; Looper.prepare(卡塔尔 wasn't called on this thread."卡塔尔(قطر‎;

待handler.sendMsg(卡塔尔国;试行后,还会有一句关键代码便是:Looper.loop(卡塔尔;

源码请看:

在Looper.prepare(卡塔尔中,Looper类会创建二个新的Looper对象,并归入全局的sThreadLocal中。

sThreadLocal.set(new Looper(quitAllowed));

咱俩再持续深切看看new Looper(quitAllowed卡塔尔(قطر‎,很粗大略,也就两行代码:

mQueue = new MessageQueue(quitAllowed);mThread = Thread.currentThread();

原来只是初阶化mQueue和mThread那七个变量。

只是偏偏创设了Looper还特别,还非得开启音信循环,要否则要Looper有啥用。开启音信循环相仿很简短:

Looper.Loop();

现行反革命再来看意气风发看Looper.loop(卡塔尔国,部分源码已省略:

public static void loop() {

final Looper me = myLooper();

//下面是Looper.myLooper()源码

public static @Nullable Looper myLooper() {

return sThreadLocal.get(卡塔尔;//通过Looper对象辅导的线程名称拿到Looper对象(线程名称唯生龙活虎)

}if (me == null) {

throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");

}

final MessageQueue queue = me.mQueue;

版权声明:本文由龙竞技官网发布于龙竞技官网,转载请注明出处:源码分析Handler机制