val layoutParams = WindowManager.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE shl WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL shl WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
windowManager.addView(imageView,layoutParams)
複製代碼
Window是一個抽象概念,它是以View的形式存在,每一個Window都對應着View和ViewRootImpl,Window和View之間經過ViewRootImpl創建聯繫android
Window的整個添加過程可分爲兩部分執行:bash
View的添加是從調用windowManager.addView()開始的,其實點開windowManager只是一個繼承ViewManager的接口,在活動中真正執行任務的是它的實現類WindowMangerImpl,所以方法會執行到WindowMangerImpl.addView(),但WindowMangerImpl 是個聰明的類,在addView()中除了驗證設置LayoutParams的合法性以外,它又將全部的工做都橋接給WindowManagerGlobal執行:網絡
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);//驗證params的合法性
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow); // 直接交給WindowManagerGlobal處理
}
複製代碼
在具體執行方法前先介紹下WindowManagerGlobal中的各個集合的做用(見下面註釋),在Window工做的整個過程他們時刻保存着Window和View的運行狀態app
private final ArrayList<View> mViews = new ArrayList<View>(); // 保存全部Window對應的View
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>(); // 保存全部Window對應的ViewRootImpl
private final ArrayList<WindowManager.LayoutParams> mParams = // 保存全部Window對應的WindowManager.LayoutParams
new ArrayList<WindowManager.LayoutParams>();
private final ArraySet<View> mDyingViews = new ArraySet<View>(); // 保存正在被刪除的View
複製代碼
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
root.setView(view, wparams, panelParentView); // View的繪製
複製代碼
上面是addView()中的部分代碼,它執行了如下幾個操做:異步
在保存了相關數據後,View真正的執行就是setView()這一句開始,下面看看ViewRootImpl中是如何實現View的測量繪製的ide
ViewRootImpl是View中的最高層級,屬於全部View的根(但ViewRootImpl不是View,只是實現了ViewParent接口),實現了View和WindowManager之間的通訊協議oop
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
......
requestLayout();//對View進行第一次測量和繪製
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), mWinFrame,
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel); //調用WindowSession的addTodiaplay()添加窗口
}
}
複製代碼
requestLayout()內調用scheduleTraversals(),scheduleTraversals()中 會獲取主線程的Handler而後發送消息執行TraversalRunnable實例,TraversalRunnable是Runnable的實現類,在run()方法中執行oTraversal() ,而後方法會執行到performTraversals()佈局
//調用performMeasure完成Window內視圖的測量
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
int width = host.getMeasuredWidth();
int height = host.getMeasuredHeight();
boolean measureAgain = false;
if (lp.horizontalWeight > 0.0f) {
width += (int) ((mWidth - width) * lp.horizontalWeight);
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width,
MeasureSpec.EXACTLY);
measureAgain = true;
}
if (lp.verticalWeight > 0.0f) {
height += (int) ((mHeight - height) * lp.verticalWeight);
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height,
MeasureSpec.EXACTLY);
measureAgain = true;
}
if (measureAgain) {
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
......
final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
boolean triggerGlobalLayoutListener = didLayout
|| mAttachInfo.mRecomputeGlobalAttributes;
if (didLayout) {
performLayout(lp, mWidth, mHeight); //完成View的佈局Layout
}
......
performDraw();//對View的繪製
複製代碼
performTraversals方法中,依次調用了performMeasure、performLayout、performDraw三個方法,這三個方法中又分別調用View或ViewGroupde的measure、layout和draw方法,完成了View的測量、佈局和繪製;ui
@Override
public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets,
Rect outOutsets, InputChannel outInputChannel) {
return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
outContentInsets, outStableInsets, outOutsets, outInputChannel);
}
複製代碼
刪除過程和添加同樣最後執行任務的都是WindowManagerGlobal,先看下WindowManagerGlobal的removeView()方法:this
int index = findViewLocked(view, true);
View curView = mRoots.get(index).getView();
removeViewLocked(index, immediate);
複製代碼
removeView()中主要執行三個步驟:
ViewRootImpl root = mRoots.get(index);
View view = root.getView(); // 獲取ViewRootImpl保存的View
boolean deferred = root.die(immediate); // 調用die()執行刪除View
mDyingViews.add(view); // 將要刪除的View添加到mDyingViews
複製代碼
boolean die(boolean immediate) {
if (immediate && !mIsInTraversal) {
doDie();
return false;
}
mHandler.sendEmptyMessage(MSG_DIE);
return true;
}
複製代碼
在die()方法中根據傳入的immediate執行同步或異步刪除:
mView.dispatchDetachedFromWindow();
mWindowSession.remove(mWindow);
mView.onDetachedFromWindow();
WindowManagerGlobal.getInstance().doRemoveView(this);
複製代碼
doDie是真正發起刪除的地方,執行操做以下:
public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
view.setLayoutParams(wparams); // 設置新的LyaoutParams
synchronized (mLock) {
int index = findViewLocked(view, true);
ViewRootImpl root = mRoots.get(index); //更新root,mParams集合中的數據
mParams.remove(index);
mParams.add(index, wparams);//替換mParams中保存的wparams
root.setLayoutParams(wparams, false); // 更新View
}
}
複製代碼
執行過程見上面註釋,在 root.setLayoutParams中會觸發ViewRootImpl的scheduleTraversals實現View的測量、佈局、繪製;
mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow.setWindowControllerCallback(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
複製代碼
//Activity中
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID); //調用PhoneWindow的setContentView()
initWindowDecorActionBar();
}
複製代碼
此處的getWindow()獲得的就是前面建立的PhoneWindow ,因此setContentView()最終是在PhoneWindow中執行的
public void setContentView(int layoutResID) {
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
}
複製代碼
setContentView()方法中,首先判斷contentParent是否空,若是爲空則執行installDecor(),installDecor()中有兩處代碼比較明顯,分別是初始化DecorView和mContentParent,下面分別看看這兩個方法
protected DecorView generateDecor(int featureId) {
return new DecorView(context, featureId, this, getAttributes()); //初始化DecorView,此時只是一個FrameLayout
}
複製代碼
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
//根據加載後的佈局查找content
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT); // 加載DecorView佈局中的content容器
public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content; //content的id
//mDecor.onResourcesLoaded()
final View root = inflater.inflate(layoutResource, null); //加載原始佈局文件:包含標題欄和content
addView(root,new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT)); //將加載的佈局添加到DecorView中
複製代碼
在generateLayout中完成了佈局layout文件的加載,具體細節以下:
@Override
public void setContentView(int layoutResID) {
mLayoutInflater.inflate(layoutResID, mContentParent); //加載佈局到mContentParent中
}
複製代碼
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
複製代碼
到此DecorView和contentParent初始化已經完成,DecorView中加載了一個具備TitleView和ContentView的佈局,而且加載的layoutResID也已加載到ContentView中,因此關於DecorView內部的工做已經完成,但DecorView未被添加到Window中,因此此時界面還是不可見
ActivityThread的handleResumeActivity()中調用Activity的makeVisible()方法,makeVisible中調用WindowManager.addView()將DecorView添加到PhoneWindow中,到此佈局資源展現在屏幕上
//handleResumeActivity
if (r.activity.mVisibleFromClient) {
r.activity.makeVisible();
}
void makeVisible() {
if (!mWindowAdded) {
ViewManager wm = getWindowManager();
wm.addView(mDecor, getWindow().getAttributes()); //將DecorView添加到PhoneWindow中
mWindowAdded = true;
}
mDecor.setVisibility(View.VISIBLE); //設置DecorView可見
}
複製代碼
val dialog = Dialog(this,R.style.Base_ThemeOverlay_AppCompat_Dialog)
dialog.setContentView(R.layout.dialog)
dialog.show()
dialog.cancel()
複製代碼
從上面使用能夠看出,dialog設置佈局時和Activity都是使用setContentView,因此其執行初始化的過程和Activity一致,只是在將DecorView添加到Window時有所不一樣
public void show() { //在Dialog顯示時添加到Window中
mWindowManager.addView(mDecor, l); // 添加DecorView
}
複製代碼
mWindowManager.removeViewImmediate(mDecor);
複製代碼
Toast.makeText(this,"Toast",Toast.LENGTH_SHORT).show()
複製代碼
Toast result = new Toast(context, looper); //建立Toast實例,並傳入隊列Loop
LayoutInflater inflate = (LayoutInflater)
context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View v = inflate.inflate(com.android.internal.R.layout.transient_notification, null); //加載Toast佈局並設置View
TextView tv = (TextView)v.findViewById(com.android.internal.R.id.message);
tv.setText(text); //設置Toast的信息
result.mNextView = v; // 複製給mNextView
result.mDuration = duration; //設置Toast的彈出時長
return result;
複製代碼
INotificationManager service = getService();
String pkg = mContext.getOpPackageName();
TN tn = mTN;
tn.mNextView = mNextView;
service.enqueueToast(pkg, tn, mDuration);
複製代碼
針對上面方法中作幾點說明:
Toast最終回調TN中的show方法,show()中發送Message到Handle,而後調用handleShow()
public void handleShow(IBinder windowToken) {
handleHide();
mParams.x = mX;
mParams.y = mY;
mParams.verticalMargin = mVerticalMargin;
mParams.horizontalMargin = mHorizontalMargin;
mParams.packageName = packageName;
mParams.hideTimeoutMilliseconds = mDuration ==
Toast.LENGTH_LONG ? LONG_DURATION_TIMEOUT : SHORT_DURATION_TIMEOUT;
mParams.token = windowToken;
mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
mWM.addView(mView, mParams);
}
複製代碼
handleShow()中執行如下操做:
WindowManagerService是如何執行Window的添加和操做的?