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被制空了