开发者社区> mogqp3unktbnq> 正文

Android 彻底掌握 Handler 看这里就够了(上)

简介: Handler 允许你发送和处理与线程的 MessageQueue 关联的 Message 和 Runnable 对象。每个 Handler 实例都与一个线程和该线程的消息队列相关联。当你创建一个新的 Handler 时,它会绑定到一个 Looper。它会将消息和可运行对象传递到该 Looper 的消息队列,并在该 Looper 的线程上执行它们。
+关注继续查看

Handler 介绍


       Handler 允许你发送和处理与线程的 MessageQueue 关联的 Message 和 Runnable 对象。每个 Handler 实例都与一个线程和该线程的消息队列相关联。当你创建一个新的 Handler 时,它会绑定到一个 Looper。它会将消息和可运行对象传递到该 Looper 的消息队列,并在该 Looper 的线程上执行它们。


Handler 有两个主要用途:


  • 1、安排消息和可运行对象在将来的某个时间执行;


  • 2、将要在与您自己的线程不同的线程上执行的操作排入 队列


       主要场景是子线程完成耗时操作的过程中,通过 Handler 向主线程发送消息 Message,用来刷新 UI 界面。 本文咱们来了解 Handler 的发送消息和处理消息的源码实现。


       分析源码的时候最好是找到一个合适的切入点,Handler 源码的一个切入点就是它的默认构造器。


分析源码


new Handler()


    public Handler() {
        this(null, false);
    }
    
    public Handler(@Nullable Callback callback, boolean async) {
        //发现泄露,初始值false,不看往下走
        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());
            }
        }
        //注释1
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
        //注释2
        mQueue = mLooper.mQueue;
        //null
        mCallback = callback;
        //false
        mAsynchronous = async;
    }


在无参构造器里调用了重载的构造方法并分别传入 null 和 false。并且在构造方法中给两个全局变量赋值:mLooper 和 mQueue。mLooper是通过 Looper 来获取。mQueue 是通过 mLooper.mQueue 获取。这说明他们都来找于 Looper 。咱们先看myLooper吧。


Looper.myLooper()


    final MessageQueue mQueue;
    
    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }


sThreadLocal.get()


public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }


 可以看出,myLooper 通过一个线程本地变量中的存根,然后 mQueue 是 Looper 中的一个全局变量,类型是 MessageQueue 类型。


MessageQueue:保存要由 Looper 调度的消息列表。 Message不是直接添加到 MessageQueue 的,而是通过与 Looper 关联的 Handler 对象添加的。你可以使用 Looper.myQueue() 检索当前线程的 MessageQueue。


Looper 介绍


       默认情况下,线程没有与之关联的消息循环;要创建一个,在运行循环的线程中调用 prepare ,然后循环让它处理消息,直到循环停止。


       大多数与消息循环的交互是通过 Handler 类进行的。


       这是一个Looper线程实现的典型例子,利用prepare和loop的分离,创建了一个初始Handler与Looper进行通信。


class LooperThread extends Thread {
       public Handler mHandler;
 
       public void run() {
           Looper.prepare();
 
           mHandler = new Handler(Looper.myLooper()) {
               public void handleMessage(Message msg) {
                   // 在这里处理传入的消息
               }
           };
 
           Looper.loop();
       }
   }


   启动一个 java 程序的入口函数是 main 方法,但是当 main 函数执行完毕之后此程序停止运行,也就是进程会自动终止。


       但是当我们打开一个 Activity 之后,只要我们不按下返回键 Activity 会一直显示在屏幕上,也就是 Activity 所在进程会一直处于运行状态。实际上 Looper 内部维护一个无限循环,保证 App 进程持续进行。


Looper初始化


       Activity.attach() 方法中会传入一个ActivityThread,ActivityThread 的 main 方法是一个新的 App 进程的入口。


ActivityThread.main()


    public static void main(String[] args) {
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
        // 安装选择性系统调用拦截
        AndroidOs.install();
        // 禁止CloseGuard
        CloseGuard.setEnabled(false);
        Environment.initForCurrentUser();
        //确保 TrustedCertificateStore 查找 CA 证书的正确位置
        final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
        TrustedCertificateStore.setDefaultUserDirectory(configDir);
        // 调用每个进程的主线模块初始化。
        initializeMainlineModules();
        Process.setArgV0("<pre-initialized>");
        //重点:注释1
        //初始化当前进程的 Looper 对象
        Looper.prepareMainLooper();
        ...
        ActivityThread thread = new ActivityThread();
        thread.attach(false, startSeq);
 
        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }
        ...
        //重点:注释2
        //调用 Looper 的 loop 方法开启无限循环。
        Looper.loop();
 
        throw new RuntimeException("Main thread loop unexpectedly exited");
    }


 注释1:就初始化当前进程的 Looper 对象;

        注释2:调用 Looper 的 loop 方法开启无限循环(具体下面讲到)。


Looper.prepareMainLooper()


1.    public static void prepareMainLooper() {
        //注释1:创建一个Looper
        //下面把方法贴出来
        prepare(false);
        //加个同步方法对象锁
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            //这个要等prepare执行完再看
            //注释1
            sMainLooper = myLooper();
        }
    }


Looper.prepare()


    private static void prepare(boolean quitAllowed) {
        //判断是否绑定过 Looper 对象
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }


Looper.prepare 方法其实就是new 一个 Looper。核心之处在于将 new 出的 Looper 设置到了线程本地变量 sThreadLocal.set(looper) 中。也就是说创建的 Looper 与当前线程发生了绑定。


注意:在创建 Looper 对象之前,会判断 sThreaLocal 中是否已经绑定过 Looper 对象,如果是则抛出异常。这行代码的目的是确保在一个线程中 Looper.prepare() 方法只能被调用 1 次。


new Looper()


 

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


       Looper在构造方法中初始化了消息队列 MessageQueue 对象。


       prepare 方法执行完之后,会在 Looper.prepareMainLooper 中处调用 myLooper() 方法,从 sThreadLocal 中取出 Looper 对象并赋值给 sMainLooper 变量。


Looper.prepare() 只能被调用1次


       从上面代码可以看出Activity在被创建时已经在 Looper.prepareMainLooper()中调用了一次Looper.prepare(),我决定在onCreate()里面再调用一次Looper.prepare(),祝福我吧。 我现在我准备在MainActivity中加一行代码


@Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Looper.prepare();
    }


结果惨兮兮:


微信图片_20220522205237.png


注意:


  • prepare 方法在一个线程中只能被调用 1 次
  • Looper 的构造方法在一个线程中只能被调用 1 次
  • MessageQueue 在一个线程中只会被初始化 1 次


结论:也就是说 UI 线程中只会存在 1 个 MessageQueue 对象,后续我们通过 Handler 发送的消息都会被发送到这个 MessageQueue 中。


Looper 干啥的?


  总结 Looper 做的事情就是:不断从 MessageQueue 中取出 Message,然后处理 Message 中指定的任务。

       回到原点,在 ActivityThread 的 main 方法中,除了调用 Looper.prepareMainLooper 初始化 Looper 对象之外,还调用了 Looper.loop 方法开启无限循环,Looper 的主要功能就是在这个循环中完成的。


Looper.loop()


    public static void loop() {
        //取出 Looper 对象并赋值给 me 
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        if (me.mInLoop) {
            Slog.w(TAG, "Loop again would have the queued messages be executed"
                    + " before this one completed.");
        }
 
        me.mInLoop = true;
        final MessageQueue queue = me.mQueue;
        ...
        //下面这个死循环,进去就甭想出来了。
        for (;;) {
            //注释1
            //调用 MessageQueue 的 next 方法取出 Message
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
            ...
            try {
                //注释2
                //msg不为null,进行处理
                msg.target.dispatchMessage(msg);
                if (observer != null) {
                    observer.messageDispatched(token, msg);
                }
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } catch (Exception exception) {
                if (observer != null) {
                    observer.dispatchingThrewException(token, msg, exception);
                }
                throw exception;
            } finally {
                ThreadLocalWorkSource.restore(origWorkSource);
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            ...
            msg.recycleUnchecked();
        }
    }
 


上面代码表示 loop 方法中执行了一个死循环,这也是一个 Android App 进程能够持续运行的原因。


       注释1:不断地调用 MessageQueue 的 next 方法取出 Message。


       注释2:如果 message 不为 null,则处进行后续处理。具体就是从 Message 中取出 target 对象,然后调用其 dispatchMessage 方法处理 Message 自身。target是谁?


Message.target


public final class Message implements Parcelable {
    ...
    @UnsupportedAppUsage
    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
    public long when;
    /*package*/ Bundle data;
    @UnsupportedAppUsage
    /*package*/ Handler target;
    @UnsupportedAppUsage
    /*package*/ Runnable callback;
    // sometimes we store linked lists of these things
    @UnsupportedAppUsage
    /*package*/ Message next;
    /** @hide */
    public static final Object sPoolSync = new Object();
    private static Message sPool;
    private static int sPoolSize = 0;
    ...
}


   查看后其实就是个Handler。那咱们再看看Handler 的 dispatchMessage 方法


Handler.dispatchMessage()


    /**
     * Handle system messages here.
     * 在这里处理系统消息。
     */
    public void dispatchMessage(@NonNull Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }
    /**
     * Subclasses must implement this to receive messages.
     * 子类必须实现它才能接收消息。
     */
    public void handleMessage(@NonNull Message msg) {
    }


可以看出,在 dispatchMessage 方法中会调用一个空方法 handleMessage,而这个方法也正是我们创建 Handler 时需要覆盖的方法。那么 Handler 是何时将其设置为一个 Message 的 target 的呢?


Handler.sendMessage()


       Handler 有几个重载的 sendMessage 方法,但是基本都大同小异。咱使用最普通的 sendMessage 方法来分析,代码具体如下:


    public final boolean sendMessage(@NonNull Message msg) {
        return sendMessageDelayed(msg, 0);
    }


Handler.sendMessageDelayed()


    public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }


Handler.sendMessageAtTime()


1.    public boolean sendMessageAtTime(@NonNull 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);
    }



    经过几层调用之后,在这里我们拿到了在 ActivityThread 的 main 方法中通过 Looper 创建的 MessageQueue。


       并且最后调用 enqueueMessage 方法将 Message 插入到消息队列 MessageQueue 中。


Handler.emqueueMessage()


    private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) {
        //注释
        msg.target = this;
        msg.workSourceUid = ThreadLocalWorkSource.getUid();
 
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }


注释:将 Handler 自身设置为 Message的target(Handler) 对象。下来咱们看看 MessageQueue 的 enqueueMessage 方法。因此后续 Message 会调用此 Handler 的 dispatchMessage  方法来处理。


MessageQueue.enqueueMessage()


    boolean enqueueMessage(Message msg, long when) {
        //注释1,非空判断
        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();
            //注释2,从这朝下都蛮重要的
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                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;
    }


注释1:会判断msg.target == null 没有设置,则直接抛出异常;


       注释2:会按照 Message 的时间 when 来有序得插入 MessageQueue 中,可以看出 MessageQueue 实际上是一个有序队列,只不过是按照 Message 的执行时间来排序。


       后续就是通过 ActivityThread 的 main 方法中 Looper 创建的 MessageQueue。Looper 从 MessageQueue 中取出 Message 之后,会调用 dispatchMessage 方法进行处理。


       至此 Handler 的发送消息和消息处理流程已经介绍完毕。



版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
阿里云服务器怎么设置密码?怎么停机?怎么重启服务器?
如果在创建实例时没有设置密码,或者密码丢失,您可以在控制台上重新设置实例的登录密码。本文仅描述如何在 ECS 管理控制台上修改实例登录密码。
23581 0
阿里云服务器ECS远程登录用户名密码查询方法
阿里云服务器ECS远程连接登录输入用户名和密码,阿里云没有默认密码,如果购买时没设置需要先重置实例密码,Windows用户名是administrator,Linux账号是root,阿小云来详细说下阿里云服务器远程登录连接用户名和密码查询方法
22359 0
如何设置阿里云服务器安全组?阿里云安全组规则详细解说
阿里云安全组设置详细图文教程(收藏起来) 阿里云服务器安全组设置规则分享,阿里云服务器安全组如何放行端口设置教程。阿里云会要求客户设置安全组,如果不设置,阿里云会指定默认的安全组。那么,这个安全组是什么呢?顾名思义,就是为了服务器安全设置的。安全组其实就是一个虚拟的防火墙,可以让用户从端口、IP的维度来筛选对应服务器的访问者,从而形成一个云上的安全域。
19808 0
使用SSH远程登录阿里云ECS服务器
远程连接服务器以及配置环境
14761 0
阿里云服务器安全组设置内网互通的方法
虽然0.0.0.0/0使用非常方便,但是发现很多同学使用它来做内网互通,这是有安全风险的,实例有可能会在经典网络被内网IP访问到。下面介绍一下四种安全的内网互联设置方法。 购买前请先:领取阿里云幸运券,有很多优惠,可到下文中领取。
22538 0
阿里云服务器如何登录?阿里云服务器的三种登录方法
购买阿里云ECS云服务器后如何登录?场景不同,云吞铺子总结大概有三种登录方式: 登录到ECS云服务器控制台 在ECS云服务器控制台用户可以更改密码、更换系统盘、创建快照、配置安全组等操作如何登录ECS云服务器控制台? 1、先登录到阿里云ECS服务器控制台 2、点击顶部的“控制台” 3、通过左侧栏,切换到“云服务器ECS”即可,如下图所示 通过ECS控制台的远程连接来登录到云服务器 阿里云ECS云服务器自带远程连接功能,使用该功能可以登录到云服务器,简单且方便,如下图:点击“远程连接”,第一次连接会自动生成6位数字密码,输入密码即可登录到云服务器上。
36450 0
阿里云服务器ECS登录用户名是什么?系统不同默认账号也不同
阿里云服务器Windows系统默认用户名administrator,Linux镜像服务器用户名root
16454 0
201
文章
0
问答
文章排行榜
最热
最新
相关电子书
更多
JS零基础入门教程(上册)
立即下载
性能优化方法论
立即下载
手把手学习日志服务SLS,云启实验室实战指南
立即下载