android 详解Handler内部源码机制

handler是我们android开发一定会用到的,如果说你没用到,那你还说是做android开发的,谁都不信,上帝也不会相信的,但是如果只停留在使用上,而不去分析内部实现,如果你去面试问你这个都不会,估计不太好吧,至少在面试官的影响中肯定是减分的.


我们知道Handler里面会涉及到几个类,都说轮询也就是Looper,还有什么消息队列,就是MessageQueue,那么他们之间是什么关系呢? 那就去Handler源码中神游下.



首先从Handler构造函数入手,


/**
 * Default constructor associates this handler with the {@link Looper} for the
 * current thread.
 *
 * If this thread does not have a looper, this handler won't be able to receive messages
 * so an exception is thrown.
 */
public Handler() {
    this(null, false);
}

我们调用一个无参的构造函数,但是它是去调用二个参数的构造函数,这不是主要的,主要的是它上面的注释,意思是通过一个默认的构造函数创建一个Handler对象,而且是在当前线程,也就是说你在哪个线程中new Handler,还有就是如果这个线程中没有与之关联的Looper对象,那这个handler无法接受到消息,


那么在UI线程中创建了一个Handler,但是并没有创建Looper啊,有的,只是系统帮我们做了,在activity的启动的时候通过ActivityThread中的入口main()方法中,


public static void main(String[] args) {
    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
    SamplingProfilerIntegration.start();

    // CloseGuard defaults to true and can be quite spammy.  We
    // disable it here, but selectively enable it later (via
    // StrictMode) on debug builds, but using DropBox, not logs.
    CloseGuard.setEnabled(false);

    Environment.initForCurrentUser();

    // Set the reporter for event logging in libcore
    EventLogger.setReporter(new EventLoggingReporter());

    // Make sure TrustedCertificateStore looks in the right place for CA certificates
    final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
    TrustedCertificateStore.setDefaultUserDirectory(configDir);

    Process.setArgV0("<pre-initialized>");

    Looper.prepareMainLooper();

    ActivityThread thread = new ActivityThread();
    thread.attach(false);

    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }

    if (false) {
        Looper.myLooper().setMessageLogging(new
                LogPrinter(Log.DEBUG, "ActivityThread"));
    }

    // End of event ActivityThreadMain.
    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
    Looper.loop();

    throw new RuntimeException("Main thread loop unexpectedly exited");
}

Looper.prepareMainLooper();通过这个代码,就会去创建Looper对象了,点击进去,看到底干了什么事,


/**
 * Initialize the current thread as a looper, marking it as an
 * application's main looper. The main looper for your application
 * is created by the Android environment, so you should never need
 * to call this function yourself.  See also: {@link #prepare()}
 */
public static void prepareMainLooper() {
    prepare(false);
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        sMainLooper = myLooper();
    }
}

看这个注释就知道了,在当前线程中创建一个Looper,它是应用启动的时候就创建了,所以你永远不需要调用这个方法.

它的第一行代码就是prepare(false),查下这个方法:


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));
}

你会发现这个方法是一个private修饰的,内部使用的,它是创建了一个Looper对象然后保存在ThreadLocal中去,ThreadLocal它的作用是可以在每个线程中存储数据的安全,比如你在A线程中发送了数据,B线程中发送了数据,怎么保证线程的数据不乱,就是通过ThreadLocal实现的,


现在回到Handler的构造函数中,如果是参数的构造函数,会调用带二个参数的构造函数,


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;
}

首先是Looper.myLooper()方法获取Looper对象,


/**
 * Return the Looper object associated with the current thread.  Returns
 * null if the calling thread is not associated with a Looper.
 */
public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}

看注释是返回与当前线程关联的Looper对象,如果调用线程与一个Looper没有关联,则为null.

mQueue = mLooper.mQueue;再看下这个是返回一个MessageQueue对象,就是消息队列,mQueue是在哪赋值的,

final MessageQueue mQueue;这是在Looper类中定义的成员变量,赋值是在构造函数中的,


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

从这可以看的出来,一个Looper对象对应一个MessageQueue,MessageQueue是一个链表的结构,那么我们在子线程中发送消息(Message),消息就入队列了,


/**
 * Pushes a message onto the end of the message queue after all pending messages
 * before the current time. It will be received in {@link #handleMessage},
 * in the thread attached to this handler.
 *  
 * @return Returns true if the message was successfully placed in to the 
 *         message queue.  Returns false on failure, usually because the
 *         looper processing the message queue is exiting.
 */
public final boolean sendMessage(Message msg)
{
    return sendMessageDelayed(msg, 0);
}

看这个注释,最终会调到这个方法:


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()方法就是把Message放入到队列中,


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

msg.targt = this,这是把Handler赋值给了Message.target,用来判断这个消息是从哪个Handler发送过来的,

queue.enqueueMessage(msg, uptimeMillis)这个就是真正把消息放入到哦队列中的,既然有消息入队列,我们都知道Looper是消息轮询的,轮询是Looper中的,


/**
 * Run the message queue in this thread. Be sure to call
 * {@link #quit()} to end the 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
        final Printer logging = me.mLogging;
        if (logging != null) {
            logging.println(">>>>> Dispatching to " + msg.target + " " +
                    msg.callback + ": " + msg.what);
        }

        final long traceTag = me.mTraceTag;
        if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
            Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
        }
        try {
            msg.target.dispatchMessage(msg);
        } finally {
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }

        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();
    }
}

但是我们好像从来没有调用过这个方法啊,对吧,它是怎么去轮询这个MessageQueue队列中的消息呢?这是在ActivityThread中的main()方法倒数第二行代码被调用了,


if (false) {
    Looper.myLooper().setMessageLogging(new
            LogPrinter(Log.DEBUG, "ActivityThread"));
}

// End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
Looper.loop();

这就是为啥我们自己不手动调用loop()方法,如果有消息会怎么办,loop()方法中有一个重要的代码


try {
    msg.target.dispatchMessage(msg);
} finally {
    if (traceTag != 0) {
        Trace.traceEnd(traceTag);
    }
}

之前说了targete就是handler,调用dispatchMessage分发Message,


/**
 * Handle system messages here.
 */
public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}

如果msg.callback==null,就调用handleMessage()处理这消息,那么这个消息处理是在子线程中还是主线程中呢,你这么想handler是异步消息处理机制,是更新UI的,如果是子线程中,能更新UI么,所以说这个消息处理一定是在主线程中,


现在花个图理解下:




其实这就是典型的多线程的生产者消费者模式,在子线程中生产(Message),在主线程中消费(Message).


现在几个常见的问题,也是面试常见的问题举例分析下:

1:我们都知道Handler的创建是在主线程,那么在子线程中能创建么,写个,


new Thread(){
    @Override
    public void run() {
        super.run();
        Handler handler = new Handler();
    }
}.start();

运行起来后发现报错了,




它说不能创建handler对象在子线程中,因为没有调用perpare()方法,之前我们也分析了这个方法,再把这个方法看下:


public static void prepare() {
    prepare(true);
}

调用带参数的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对象的,所以说prepare()方法是创建Looper对象的,如果你创建Handler对象,只顾着发送消息,但是没有处理这个消息,那么队列中的消息是不是堆满了,对内存消息挺大,所以它就报错了,那解决的方法就是调用prepare()方法


new Thread(){
    @Override
    public void run() {
        super.run();
        Looper.prepare();
        Handler handler = new Handler();
    }
}.start();

2:handler可以在主线程中发送消息么.那是可以的,只是如果在主线程中发送消息,处理消息也在主线程中,那干嘛还用Handler.


3:能在子线程中更新UI么? 可以的


new Thread(){
    @Override
    public void run() {
        super.run();
        Looper.prepare();
        Handler handler = new Handler(){
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                Toast.makeText(getApplicationContext(), "在子线程中更新UI", Toast.LENGTH_LONG).show();
            }
        };
        handler.sendEmptyMessage(1);
        Looper.loop();
    }
}.start();

有二个必须条件:

1:必须调用Looper.prepare()方法创建Looper对象

2:必须调用Looper.loop()去消息队列中轮询消息,否则你发送消息,没有Looper处理消息.


4:消息回收

在Looper.loop()轮询的时候,方法最后一行代码:


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();

最后一行代码可以看到msg调用了recycleUnchecked(),进入到这个方法


/**
 * Recycles a Message that may be in-use.
 * Used internally by the MessageQueue and Looper when disposing of queued Messages.
 */
void recycleUnchecked() {
    // Mark the message as in use while it remains in the recycled object pool.
    // Clear out all other details.
    flags = FLAG_IN_USE;
    what = 0;
    arg1 = 0;
    arg2 = 0;
    obj = null;
    replyTo = null;
    sendingUid = -1;
    when = 0;
    target = null;
    callback = null;
    data = null;

    synchronized (sPoolSync) {
        if (sPoolSize < MAX_POOL_SIZE) {
            next = sPool;
            sPool = this;
            sPoolSize++;
        }
    }
}

经历一系列的赋null操作,消息池中最大的消息数是50,


private static final int MAX_POOL_SIZE = 50;

5:很多框架的回调接口怎么让他在主线程中执行;


Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() {
    @Override
    public void run() {
        //回调接口
    }
});














  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值