View.post分析
我们在使用View的时候可以直接使用View对象进行post(runnable),难道View里面有主线程Handler对象?是每个View都有一个Handler,还是公用的?为何View 没有 AttachedToWindow的时候View.post无效呢,后面还会执行么?
本文所有的源码都是基于API19,也就是4.4KitKat版本,不同版本源码不同,思路雷同
View.post背后究竟是谁?
所有的View的post方法都是直接继承于View类的post(Runnable action)方法:
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
// 1. AttachInfo.mHandler 这里是Handler
return attachInfo.mHandler.post(action);
}
// Assume that post will succeed later
// 2.ViewRootImpl.getRunQueue() 返回的RunQueue
ViewRootImpl.getRunQueue().post(action);
return true;
}
I. AttachInfo.mHandler支线
- AttachInfo中的mHandler 是创建的时候从外部传进的
AttachInfo(IWindowSession session, IWindow window, Display display,
ViewRootImpl viewRootImpl, Handler handler, Callbacks effectPlayer) {
mSession = session;
mWindow = window;
mWindowToken = window.asBinder();
mDisplay = display;
mViewRootImpl = viewRootImpl;
// handler是从外部传进来的,那么是公用的,还是每个View对应一个呢?
mHandler = handler;
mRootCallbacks = effectPlayer;
}
- ViewRootImpl的构造方法,一起创建了AttachInfo
public ViewRootImpl(Context context, Display display) {
mContext = context;
mWindowSession = WindowManagerGlobal.getWindowSession();
// 记住这一行,mThread为当前线程
mThread = Thread.currentThread();
// .....
// 创建了AttachInfo,并将自己的mHandler (ViewRootHandler extends Handler)赋给了AttachInfo的mHandler
mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this);
// .....
}
Question:
1.已知Handler的创建是与线程相关的,那么此Handler一定是在UI线程上创建么?
2.AttachInfo是所有View共用的么,好像不是,如何证明?
问题一解答:此Handler一定是在UI线程上创建的:
我们知道Android控件需要在UI线程显示更新,如果不然会报以下错误:
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks{
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
// .....
}
上面ViewRootImpl 的初始化中有mThread 的创建,就是当前线程对象,根据这里的异常判断,ViewRootImpl 的初始化一定是在UI线程上的,那么成员变量Handler也一定是在主线程上创建的。
问题二解答:AttachInfo每个View对应一个不同对象,且与View的Attach状态相关,创建于addView时:
public final class WindowManagerGlobal {
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
if (parentWindow != null) {
parentWindow.adjustLayoutParamsForSubWindow(wparams);
}
ViewRootImpl root;
View panelParentView = null;
// 每次addView的时候都会创建ViewRootImpl对象
root = new ViewRootImpl(view.getContext(), display);
// .....
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
// do this last because it fires off messages to start doing things
try {
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// .....
}
}
// ......
}
很显然,每个View都是通过addView添加到父View中去的。所以每个View都会有其对应的ViewRootImpl,有其对应的mHandler。
II.ViewRootImpl.getRunQueue()支线
先看看getRunQueue:
static RunQueue getRunQueue() {
RunQueue rq = sRunQueues.get();
if (rq != null) {
return rq;
}
rq = new RunQueue();
sRunQueues.set(rq);
return rq;
}
再看看RunQueue :
// 注意这里是静态的
static final class RunQueue {
private final ArrayList<HandlerAction> mActions = new ArrayList<HandlerAction>();
void postDelayed(Runnable action, long delayMillis) {
// runnable 存到 HandlerAction对象中
HandlerAction handlerAction = new HandlerAction();
handlerAction.action = action;
handlerAction.delay = delayMillis;
synchronized (mActions) {
mActions.add(handlerAction);
}
}
void executeActions(Handler handler) {
synchronized (mActions) {
final ArrayList<HandlerAction> actions = mActions;
final int count = actions.size();
for (int i = 0; i < count; i++) {
final HandlerAction handlerAction = actions.get(i);
// 最终通过handler来执行储存的HandlerAction
handler.postDelayed(handlerAction.action, handlerAction.delay);
}
actions.clear();
}
}
}
最后来看看谁执行了executeActions( 竟然是传说中的performTraversals() ):
private void performTraversals() {
// cache mView since it is used so much below...
final View host = mView;
WindowManager.LayoutParams lp = mWindowAttributes;
final View.AttachInfo attachInfo = mAttachInfo;
// ......
// 这里,没错,就是这里
// Execute enqueued actions on every traversal in case a detached view enqueued an action
getRunQueue().executeActions(attachInfo.mHandler);
// ......
}
我们知道performTraversals是View绘制流程的总入口,当有新的View测量、位置变化、绘制的时候会触发此方法,这是会调用新View的mHandler将之前储存的runnable执行掉。
View.post不是任何时候都能用
之所以存在第二种逻辑,是因为某些情况下AttachInfo为null
第一种情况:View对象被创建,却没有被addView进父View:
handler.postDelayed(new Runnable() {
@Override
public void run() {
LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View inflateView = inflater.inflate(R.layout.layout_inflate_view, null);
inflateView.post(new Runnable() {
@Override
public void run() {
// 若布局不发生变化,此处不会执行
Toast.makeText(mainActivity.getApplicationContext(),"layout_inflate_view 没有Attach 竟然显示了",Toast.LENGTH_LONG).show();
}
});
}
},2000);
第二种情况:onDetachedFromWindow 之后post
// 跳转的同时remove掉自己,再延时post
text_view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(MainActivity.this,SecondActivity.class);
startActivity(intent);
removeView();
}
});
private void removeView(){
text_view.setOnDetachedFromWindowListener(new MineTextView.OnDetachedFromWindowListener() {
@Override
public void onDetached() {
// 会被调用
Toast.makeText(mainActivity.getApplicationContext(),"text_view 被 Detached 了",Toast.LENGTH_LONG).show();
}
});
((ViewGroup) text_view.getParent()).removeView(text_view);
getWindow().getDecorView().postInvalidate();
handler.postDelayed(new Runnable() {
@Override
public void run() {
text_view.post(new Runnable() {
@Override
public void run() {
// 不会执行
Toast.makeText(mainActivity.getApplicationContext(),"text_view 被 Detached 了 竟然还能显示",Toast.LENGTH_LONG).show();
}
});
}
},2000);
}
这里可以看到onDetachedFromWindow 之后的View中的AttachInfo被制空了,我们来看看:
void dispatchDetachedFromWindow() {
AttachInfo info = mAttachInfo;
if (info != null) {
int vis = info.mWindowVisibility;
if (vis != GONE) {
onWindowVisibilityChanged(GONE);
}
}
onDetachedFromWindow();
// ......
// 果然被制空了
mAttachInfo = null;
if (mOverlay != null) {
mOverlay.getOverlayView().dispatchDetachedFromWindow();
}
}
代码验证View中的AttachInfo被制空了