Handler(二)

Android知识总结

一、Handler的postDelayed() 方法,View的post方法分析

看源码:

    public boolean post(Runnable action) {
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            // 如果当前View加入到了window中,直接调用UI线程的Handler发送消息
            return attachInfo.mHandler.post(action);
        }
        // View未加入到window,放入ViewRootImpl的RunQueue中
        getRunQueue().post(action);
        return true;
    }

当前View尚未attach到Window时,整个View体系还没有加载完,mAttachInfo就会为null,表现在Activity中,就是onResume()方法还没有执行完。反之,mAttachInfo就不会为null。

  • 1)、View已经attach到window
    当View已经attach到window时。会直接调用Handler中的post()方法:
    //主要作用是线程的切换
    public final boolean post(@NonNull Runnable r) {
       return  sendMessageDelayed(getPostMessage(r), 0);
    } 

然后将指定Runnable(包装成PostMessage)加入到MessageQueue中,然后Looper不断从MessageQueue中读取Message进行处理。

    private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }

然后会把会把Runnable 放入HandlerActionQueue队列中

  • 2)、View还未attach到window
    private HandlerActionQueue getRunQueue() {
        if (mRunQueue == null) {
            mRunQueue = new HandlerActionQueue();
        }
        return mRunQueue;
    }

getRunQueue()是一个单例模式,返回HandlerActionQueue实例mRunQueue。mRunQueue,顾名思义,表示该view的HandlerAction队列,HandlerAction就是对Runnable的封装,所以实际就是一个Runnable的队列。它用于推迟post的调用,直到该view被附着到Window并且拥有了一个handler。

HandlerActionQueue的关键代码如下:

  public class HandlerActionQueue {
      private HandlerAction[] mActions;
      private int mCount;
  
     public void post(Runnable action) {
         postDelayed(action, 0);
     }
     public void postDelayed(Runnable action, long delayMillis) {
          //将Runnable封装成了HandlerAction
         final HandlerAction handlerAction = new HandlerAction(action, delayMillis);
 
         synchronized (this) {
             if (mActions == null) {
                 mActions = new HandlerAction[4];
             }
              //将封装后的Runnable加入到了数组中
             mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction);
             mCount++;
         }
     }
    ......
     public void executeActions(Handler handler) {
         synchronized (this) {
             final HandlerAction[] actions = mActions;
             for (int i = 0, count = mCount; i < count; i++) {
                 final HandlerAction handlerAction = actions[i];
                 handler.postDelayed(handlerAction.action, handlerAction.delay);
             }
 
             mActions = null;
             mCount = 0;
         }
     }
    ......
     private static class HandlerAction {
         final Runnable action;
         final long delay;
 
         public HandlerAction(Runnable action, long delay) {
             this.action = action;
             this.delay = delay;
         }
        ......
     }
}

该类用于在当前view没有handler附属时,将来自View的挂起的作业(就是Runnable)加入到队列中。

  • 3)、View 和 window绑定
    在 ViewRootImpl 的 performTraversals() 方法中调用。
    (ViewGroup的 addViewInner(View child, int index, LayoutParams params, boolean preventRequestLayout);也会调用)
private void performTraversals() {
      ...
      host.dispatchAttachedToWindow(mAttachInfo, 0);
      ...
}

在View的dispatchAttachedToWindow()方法中绑定

   void dispatchAttachedToWindow(AttachInfo info, int visibility) {
        mAttachInfo = info;
        if (mOverlay != null) {
            mOverlay.getOverlayView().dispatchAttachedToWindow(info, visibility);
        }
        mWindowAttachCount++;
        mPrivateFlags |= PFLAG_DRAWABLE_STATE_DIRTY;
        if (mFloatingTreeObserver != null) {
            info.mTreeObserver.merge(mFloatingTreeObserver);
            mFloatingTreeObserver = null;
        }

        registerPendingFrameMetricsObservers();

        if ((mPrivateFlags&PFLAG_SCROLL_CONTAINER) != 0) {
            mAttachInfo.mScrollContainers.add(this);
            mPrivateFlags |= PFLAG_SCROLL_CONTAINER_ADDED;
        }
        // 通过传进来的Handler,来post掉mRunQueue中存储的所有Runnable
        if (mRunQueue != null) {
            mRunQueue.executeActions(info.mHandler);
            mRunQueue = null;
        }
        performCollectViewAttributes(mAttachInfo, visibility);
        onAttachedToWindow();

        ListenerInfo li = mListenerInfo;
        final CopyOnWriteArrayList listeners =
                li != null ? li.mOnAttachStateChangeListeners : null;
        if (listeners != null && listeners.size() > 0) {
            for (OnAttachStateChangeListener listener : listeners) {
                listener.onViewAttachedToWindow(this);
            }
        }
        int vis = info.mWindowVisibility;
        if (vis != GONE) {
            onWindowVisibilityChanged(vis);
            if (isShown()) {
                onVisibilityAggregated(vis == VISIBLE);
            }
        }
        onVisibilityChanged(this, visibility);
        if ((mPrivateFlags&PFLAG_DRAWABLE_STATE_DIRTY) != 0) {
            refreshDrawableState();
        }
        needGlobalAttributesUpdate(false);
        notifyEnterOrExitForAutoFillIfNeeded(true);
        notifyAppearedOrDisappearedForContentCaptureIfNeeded(true);
    }

    protected void onAttachedToWindow() {
        if ((mPrivateFlags & PFLAG_REQUEST_TRANSPARENT_REGIONS) != 0) {
            mParent.requestTransparentRegion(this);
        }
        mPrivateFlags3 &= ~PFLAG3_IS_LAID_OUT;
        jumpDrawablesToCurrentState();
        AccessibilityNodeIdManager.getInstance().registerViewWithId(this, getAccessibilityViewId());
        resetSubtreeAccessibilityStateChanged();
        // rebuild, since Outline not maintained while View is detached
        rebuildOutline();
        if (isFocused()) {
            notifyFocusChangeToImeFocusController(true /* hasFocus */);
        }
    }

二、ThreadLocal讲解

1)、早期的设计原理

让ThreadLocal维护一个ThreadLocalMap的队列,存储是以Thread作为Key。

2)、JDK8 的设计原理

让Thread维护一个ThreadLocalMap的队列,存储是以ThreadLocal作为Key。ThreadLocalMap是以数组和链表的方式存储的。

这样的好处是:

  • 每个Map存储的Entry数量减少;
  • 当Thread销毁时,ThreadLocalMap也会随之销毁,减少内存的使用。
3)、ThreadLocal出现内存泄漏的原因
  • 内存泄漏有两个原因
    1)、没有手动删除这个Entry
    只有当使用完ThreadLocal时,调用器remove方法删除这个Entry,就能避免内存泄漏。
    2)、CurrentThread依然运行。
    由于ThreadLocalMap是Thread的一个属性,被当前线程所引用,他的生命周期跟Thread一样长。那么使用完ThreadLocal的使用,如果当前Thread也随之执行结束,那么ThreadLocalMap自然也会被GC回收,从根源上避免内存泄漏。

综上,ThreadLocal的内存泄漏根源是:因为ThreadLocal被WeakReference引用,ThreadLocal遇到GC会自动释放时。Thread中持有的ThreadLocalMap的生命周期和Thread一样长,ThreadLocalMap中Entry中的value没有及时的释放,所以需要手动remove。如果没有手动删除Entry可能会导致内存泄漏。

Handler的内存泄漏,就是由于当前消息没有发生完成,ThreadLocalMap中的Entry依然存在导致引用依然持有,造成Handler的内存泄漏。

4)、ThreadLocal的线程安全问题
public class ThreadLocalUnsafe implements Runnable {

    public static Number number = new Number(0);//不安全,静态变量在内存中只能有一份。
//   public  Number number = new Number(0); //安全

    public void run() {
        //每个线程计数加一
        number.setNum(number.getNum()+1);
      //将其存储到ThreadLocal中
        value.set(number);
        SleepTools.ms(2);
        //输出num值
        System.out.println(Thread.currentThread().getName()+"="+value.get().getNum());
    }

    public static ThreadLocal value = new ThreadLocal() {
    };

    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            new Thread(new ThreadLocalUnsafe()).start();
        }
    }

    private static class Number {
        public Number(int num) {
            this.num = num;
        }

        private int num;

        public int getNum() {
            return num;
        }

        public void setNum(int num) {
            this.num = num;
        }

        @Override
        public String toString() {
            return "Number [num=" + num + "]";
        }
    }
}

5 个线程中保存的是同一 Number 对象的引用,在线程睡眠的时候, 其他线程将 num 变量进行了修改,而修改的对象 Number 的实例是同一份,因 此它们最终输出的结果是相同的。
而上面的程序要正常的工作,应该的用法是让每个线程中的 ThreadLocal 都 应该持有一个新的 Number 对象。

5)、ThreadLocal中的方法

get()方法
该方法返回当前线程所对应的线程局部变量。

public T get() {
    Thread t = Thread.currentThread();
    //获取当前Thread持有的ThreadLocalMap
    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();
}

private T setInitialValue() {
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        //ThreadLocal作为key
        map.set(this, value);
    else
        createMap(t, value);
    return value;
}

set()方法
设置当前线程的线程局部变量的值。

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        //ThreadLocalMap 不存在重新创建
        createMap(t, value);
}

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

remove() 方法
将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。

 public void remove() {
     ThreadLocalMap m = getMap(Thread.currentThread());
     if (m != null)
         m.remove(this);
 }

你可能感兴趣的:(Handler(二))