Android Handler 从使用到进阶

Posted 进击的包籽

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android Handler 从使用到进阶相关的知识,希望对你有一定的参考价值。

文章目录

1.Handler

  • 只要是开发android的同学,Handler这个经常都会看到,也会使用到,本文章就做个笔记。
  • Android规定了只能在主线程更新UI,那子线程想更新UI,在操作完成后,就可用通过Handler发消息,然后在主线程更新UI了。其实可以理解为生产者-消费者模式,发送消息,取出消息并处理。
  • Android系统源码中,Android的消息机制中,大量使用Handler,所以了解Handler非常的有必要。
  • 下图是一个消息发送的简易流程,一各个步骤分析。

2.Handler简单使用

2.1 发送消息

  • 最基本的使用就是各种sendMessage,带不带参数,是否延迟等。
  • 发送消息方法非常多,根据自己需求选择,所有发送消息最后都是
  • enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,long uptimeMillis)

2.2 使用

  • 这样直接使用会有内存泄漏风险,后面说。
  • Handler创建有两种,一个是在构造方法传CallBack,一个是重写类的 handleMessage() 方法。
	private static final int MESSAGA_TEST_1 = 1;

    /**
     * 主线程有初始化好Looper,所以在主线程处理消息
     */
    private Handler mHandler = new Handler(new Handler.Callback() 
        @Override
        public boolean handleMessage(@NonNull Message msg) 
            switch (msg.what) 
                case MESSAGA_TEST_1:
                    if (tvTest != null) 
                        //传递消息obj
                        tvTest.setText((String) msg.obj);
                    
                    break;
            
            return false;
        
    );

 
    private Handler mHandler2 = new Handler() 
        @Override
        public void handleMessage(@NonNull Message msg) 
            switch (msg.what) 
                case MESSAGA_TEST_1:
                    if (tvTest != null) 
                        //传递消息obj
                        tvTest.setText((String) msg.obj);
                    
                    break;
            
        
    ;

    private void onClick() 
        //当点击事件执行,就会在子线程发送消息更新textview
        new Thread(new Runnable() 
            @Override
            public void run() 
                clickEndMessage();
            
        ).start();
    
  

    /**
     * 可以在子线程发送
     */
    private void clickEndMessage() 
        //obtain享元模式
        Message message = Message.obtain(mHandler);
        //what相当于标记
        message.what = MESSAGA_TEST_1;
        //obj传数据
        message.obj = new String("baozi");
        //普通发送message
        mHandler.sendMessage(message);
 		.
 		.
 		.
        //发送标记,不带其他内容,内部会封装成只有标记的Message对象
        mHandler.sendEmptyMessage();
        //尾部带有Delayed,发送延迟消息,单位毫秒
        mHandler.sendMessageDelayed(message, 1000);
        //尾部带有AtTime,发送消息的时间跟Delayed差别就是Delayed是执行的当前时间+传进去的时间,AtTime就传进去的绝对时间
        mHandler.sendMessageAtTime();
        //在队列头插入消息
        mHandler.sendMessageAtFrontOfQueue(message);
    


    @Override
    protected void onDestroy() 
        if(mHandler!=null)
        	//关闭activity时,移除消息
            mHandler.removeMessages(MESSAGA_TEST_1);
            mHandler = null;
        
        super.onDestroy();
    

2.3 view.post()

  • 比如view,post()、postDelayed() 方法,可以延迟五秒后更新UI,实际就是使用Handler。
tvTest.postDelayed(new Runnable() 
    @Override
    public void run() 
        tvTest.setText("5s");
    
,5*1000);

/**
 * View 源码
 */
public boolean postDelayed(Runnable action, long delayMillis) 
    final AttachInfo attachInfo = mAttachInfo;
    if (attachInfo != null) 
        return attachInfo.mHandler.postDelayed(action, delayMillis);
    

    // Postpone the runnable until we know on which thread it needs to run.
    // Assume that the runnable will be successfully placed after attach.
    getRunQueue().postDelayed(action, delayMillis);
    return true;

2.4 runOnUiThread

  • 经常用的 runOnUiThread() 方法也是用Handler。
runOnUiThread(new Runnable() 
    @Override
    public void run() 
        //更新UI
    
);  

/**
 * Activity 源码
 */
public final void runOnUiThread(Runnable action) 
    if (Thread.currentThread() != mUiThread) 
        mHandler.post(action);
     else 
        action.run();
    

3.子线程中使用

3.1 子线程直接创建Handler错误

  • 子线程不能直接创建Handler,会报异常,因为Looper还没创建,而主线程默认就初始化好Looper
  • 应该先Looper.
    private Handler handler2;

    /**
     * 子线程
     */
    private void thread() 
        new Thread(new Runnable() 
            @Override
            public void run() 
                handler2 = new Handler(new Handler.Callback() 
                    @Override
                    public boolean handleMessage(@NonNull Message msg) 
                        return false;
                    
                );
            
        ).start();
    
  • 提示的错误。

3.2 主线程默认初始化Looper

  • ActivityThread 类就能主线程找到Looper初始化,Looper.prepareMainLooper();
    public static void main(String[] args) 
        .
        .
        Looper.prepareMainLooper();
		.
		.
	      
        ActivityThread thread = new ActivityThread();
        .
        .
    

3.3 Handler构造方法查看

  • 构造方法中可以看到 mLooper = Looper.myLooper(); 获取的 mLooper 为null,就报上面的那个异常了。
    public Handler(@Nullable Callback callback, boolean async) 
        .
        .
        mLooper = Looper.myLooper();
        if (mLooper == null) 
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        
        .
        .
    

3.4 子线程正确的创建

  • 先执行,Looper.prepare() 初始化Looper,然后调用loop()方法,再创建Handler。
  • 每个线程 Looper.prepare() 只能调用一次,否则会报错。
    private Handler handler2;

    /**
     * 子线程
     */
    private void thread() 
        new Thread(new Runnable() 
            @Override
            public void run() 
                Looper.prepare();
                Looper looper = Looper.myLooper();
                looper.loop();

                handler2 = new Handler(looper, new Handler.Callback() 
                    @Override
                    public boolean handleMessage(@NonNull Message msg) 
                        return false;
                    
                );
            
        ).start();
    

4.Message

  • Message就是一个存放消息的类,是一个链表结构。

4.1 基本参数

public final class Message implements Parcelable 
   	//用于handler标记处理的
    public int what;
    //可以传递的int参数1
    public int arg1;
    //可以传递的int参数2
    public int arg2;
	//可以传递的obj参数
    public Object obj;

    //执行时间
    public long when;
	//传递的bundle
    Bundle data;
	//Message绑定的Handler
    Handler target;
	//Handler.post()时传的callback
    Runnable callback;
	//链表结构
    Message next;

4.2 享元模式obtain()

    /**
     * Return a new Message instance from the global pool. Allows us to
     * avoid allocating new objects in many cases.
     */
    public static Message obtain() 
        synchronized (sPoolSync) 
            if (sPool != null) 
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                return m;
            
        
        return new Message();
    

4.3 回收recycle()

  • 如果发送的延迟消息,或者消息在执行,就会报错,一般我们不用调用recycle方法。
    /**
     * Return a Message instance to the global pool.
     * <p>
     * You MUST NOT touch the Message after calling this function because it has
     * effectively been freed.  It is an error to recycle a message that is currently
     * enqueued or that is in the process of being delivered to a Handler.
     * </p>
     */
    public void recycle() 
        if (isInUse()) 
            if (gCheckRecycle) 
                throw new IllegalStateException("This message cannot be recycled because it "
                        + "is still in use.");
            
            return;
        
        recycleUnchecked();
    
  • Message用完并不是内存回收,只是把里面的内容清空,等下次复用。
  • 这个链表也不是无限的,最多就50个节点
    private static final int MAX_POOL_SIZE = 50;

    @UnsupportedAppUsage
    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 = UID_NONE;
        workSourceUid = UID_NONE;
        when = 0;
        target = null;
        callback = null;
        data = null;

        synchronized (sPoolSync) 
        	//最多50个
            if (sPoolSize < MAX_POOL_SIZE) 
                next = sPool;
                sPool = this;
                sPoolSize++;
            
        
    

5.MessageQueue

  • 这是一个阻塞队列。
  • 队列是在不停的for循环的,是一个死循环,那不就一直占用着cpu?所以就有了native的方法,处理休眠唤醒。

5.1 MessageQueue每个线程只有一个

  • 消息队列每个线程只有一个,跟Looper绑定在一起,在分析Looper时会一起分析。

5.2 消息入队

  • 前面我们知道Handler所有消息入队最后都是调用 enqueueMessage(Message msg, long when)
  • 判断插入消息的位置,还判断是否唤醒操作。
    boolean enqueueMessage(Message msg, long when) 
        //handler为空就报异常
        if (msg.target == null) 
            throw new IllegalArgumentException("Message must have a target.");
        
        //加锁
        synchronized (this) 
        	//消息正在使用会报错
            if (msg.isInUse()) 
                throw new IllegalStateException(msg + " This message is already in use.");
            
			//判断线程是否存活
            if (mQuitting) 
                IllegalStateException e = new IllegalStateException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w(TAG, e.getMessage(), e);
                msg.recycle();
                return false;
            

			//标记正在使用
            msg.markInUse();
            msg.when = when;
            //链表头
            Message p = mMessages;
            boolean needWake;
            if (p == null || when == 0 || when < p.when) 
            	//如果队列为空,或者消息延迟时间为0,或者延迟时间小于mMessage的,就插入在头部
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                //唤醒队列
                needWake = mBlocked;
             else             
                // Inserted within the middle of the queue.  Usually we don't have to wake
                // up the event queue unless there is a barrier at the head of the queue
                // and the message is the earliest asynchronous message in the queue.
                
            	//插入队列中间。通常,除非队列的开头有障碍并且消息是队列中最早的异步消息,否则我们不必唤醒事件队列。
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                //在中间插入,根据时间位置插入
                for (;;) 
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) 
                        break;
                    
                    if (needWake && p.isAsynchronous()) 
                        needWake = false;
                    
                
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            

			//是否唤醒
            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) 
                nativeWake(mPtr);
            
        
        return true;
    

5.3 消息出队

  • 消息出队其实应该放在 Looper.loop() 里面分析更合适,这里先写,后面结合 loop() 一起看会更好。
  • 前面 return null; 看注释的意思是,如果looper已经退出和释放,就返回null。
  • 这里无限循环,就是一定要取到消息,有消息,阻塞时间为消息的执行时间减去当前时间,如果没消息就阻塞, nativePollOnce(ptr, nextPollTimeoutMillis)
  • 这 next() 方法只有一个地方返回msg,关注这里就行了。
    @UnsupportedAppUsage
    Message next() 
        // Return here if the message loop has already quit and been disposed.
        // This can happen if the application tries to restart a looper after quit
        // which is not supported.
        final long ptr = mPtr;
        if (ptr == 0) 
            return null;
        

        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        for (;;) 
            if (nextPollTimeoutMillis != 0) 
                Binder.flushPendingCommands();
            
			//没消息取就阻塞
            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) 
                // Try to retrieve the next message.  Return if found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                if (msg != null && msg.target == null) 
                    // Stalled by a barrier.  Find the next asynchronous message in the queue.
                    do 
                        prevMsg = msg;
                        msg = msg.next;
                     while (msg != null && !msg.isAsynchronous());
                
                if (msg != null) 
                    if (now < msg.when) 
                        // Next message is not ready.  Set a timeout to wake up when it is ready.
                        //阻塞时间为消息的执行时间减去当前时间
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                     else 
                        // Got a message.
                        mBlocked = false;
                        if (prevMsg != null) 
                            prevMsg.next = msg.next;
                         else 
                            mMessages = msg.next;
                        
                        msg.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();
                        Android进阶——强大的异步不可或缺的组件Handler详解

[Android进阶Handler机制原理解析

Android进阶知识——Android的消息机制

Android进阶知识——Android的消息机制

Android进阶知识——Android的消息机制

Android进阶知识——Android的消息机制