美文网首页
Handler的使用

Handler的使用

作者: BlackNeko | 来源:发表于2017-01-02 21:37 被阅读67次

    新建handler并为其指定运行线程。

    在主线程中创建Handler

    Handler handler = new Handler();
    

    Handler构造函数:

    /** Handler.java */
    public Handler() {
        this(null, false);
    }
    
    /** Handler.java */
    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());
            }
        }
    
        //取Looper
        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需要Lopper对象,而Looper对象通过Looper.myLooper()获取 :

    /** Looper.java */
    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }
    

    ThreadLocal用于保存线程中的数据,在Looper类中是以静态变量的形式存在的,sThreadLocal对象是在Looper类加载阶段创建的:

    /** Looper.java */
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    

    sThreadLocal不属于任何Looper的实例,在一个app中,每个进程Process有且仅有一个sThreadLocal。

    ThreadLocal内部维护一个ThreadLocalMap,用于保存Looper对象。

    ThreadLocal#get(),获取当前线程中保存的Looper对象:

    /** ThreadLocal.java */
    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null)
                return (T)e.value;
        }
        return setInitialValue();
    }
    

    getMap:

    /** ThreadLocal.java */
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
    

    threadLocals是Thread类中包访问级别的非静态变量:ThreadLocal.ThreadLocalMap threadLocals = null;,ThreadLocal和Thread是在同一个包目录下面的,所以ThreadLocal中可以取得Thread类中的threadLocals变量。

    通过getMap(Thread)方法取得对应线程中的ThreadLocalMap后,再用map.getEntry(this)获取Entry节点,然后返回value。这里用ThreadLocal类实例的哈希值作为取资源的键值,说明一个ThreadLocal对象在Thread中只能对应一个value值,要想要存储多个对象就要创建多个对应的ThreadLocal对象。

    ThreadLocal#get() 函数有用到Thread t = Thread.currentThread(),因此,只能在对应的线程中才能获取到存入的值。

    以上就是Looper.myLooper()获取当前线程Looper对象的过程。

    创建Handler需要Looper,没有的话会报Can't create handler inside thread that has not called Looper.prepare()异常,在主线程中创建Handler时是没有问题的,因为app启动时已经为当前线程创建了Looper对象。

    在新线程中创建Handler

    Thread thread = new Thread(new Runnable() {
        @Override
        public void run() {
            Log.i(TAG, "mHandler thread start");
            Looper.prepare();
            mHandler = new Handler();
            Looper.loop();
            Log.i(TAG, "mHandler thread end");
        }
    }, "mHandlerThread");
    thread.start();
    

    首先调用 Looper.prepare() 创建Looper对象:

    /* Looper.java */
    public static void prepare() {
        prepare(true);
    }
    
    /* Looper.java */
    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对象并添加到静态成员变量sThreadLocal中,如果当前线程已创建过Looper就抛异常。

    ThreadLocal#set(T value) 把当前线程的Looper对象存储到当前线程的ThreadLocalMap中

    /* ThreadLocal.java */
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
    

    ThreadLocal#getMap(Thread t) :

    /* ThreadLocal.java */
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
    

    第一次取的时候map为null,创建并添加,ThreadLocal#createMap(Thread t, T firstValue) :

    /* ThreadLocal.java */
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
    

    这样Handler就创建完了,下面就是使用了。

    Handler,Looper,MessageQueue,Message

    简单地说就是MessageQueue存储Message,Looper跑循环,不断从MessageQueue里面取任务然后执行,如果MessageQueue为空,就卡在那里等,然后Handler发信息,Message携带信息。

    创建完Handler后,要在线程里面调用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
            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.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();
        }
    }
    

    停止Handler

    Looper只有在MessageQueue#next()为null时才会停下来

    调用链:

    Looper.myLooper().quitSafely() -> MessageQueue#quit(true) 清空MessageQueue -> Looper.loop()终止

    Looper.myLooper().quitSafely()要在对应的线程中调用,就是说要用Handler实例发事件才行。

    内存泄露:创建Handler的线程一直在执行,所以创建的Handler对象是不会被系统回收的,Handler中保存的对象引用也是不会被回收的,非静态内部类保存有外部类的引用。

    解决办法:

    • 及时清理Handler,当一个Handler不用时及时在此Handler线程中调用Looper.myLooper().quitSafely()
    • 使用静态类也是一个办法
    • 不重写Handler#handleMessage(Message msg)方法,而是用Handler#post(Runnable r)发事件,因为post发送过去的Runnable是被包装在Message的Handler target里面的,在Message的target执行完成后,会清理掉Message,Message#recycleUnchecked()

    Handler发送事件

    mHandler.post(new Runnable() {
        @Override
        public void run() {
            Log.i(TAG, "current thread name=" + Thread.currentThread().getName();
        }
    });
    
    //or
    mHandler.sendMessage(new Message());
    

    post 也是调用的sendMessage:

    public final boolean post(Runnable r)
    {
        return  sendMessageDelayed(getPostMessage(r), 0);
    }
    

    最终调用 Handler#enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis)把Message放入MessageQueue中

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

    相关文章

      网友评论

          本文标题:Handler的使用

          本文链接:https://www.haomeiwen.com/subject/imqgvttx.html