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.addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE)
window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN)
複製代碼
Window是一個抽象概念,它是以View的形式存在,每一個View和Activity都有對應的Window,每一個Window都對應着一系列的View和ViewRootImpl,Window和View之間經過ViewRootImpl創建聯繫,在WindowManager的工做過程當中會出現幾個重要的類,如:WindowManagerGlobal、PhoneWindow等,這裏先給出系統中WindowManager的關係圖 ![在這裏插入圖片描述](img-blog.csdnimg.cn/20190830151… =400x500)java
Window的整個添加過程可分爲兩部分執行:android
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的運行狀態網絡
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()中的部分代碼,它執行了如下幾個操做:app
在保存了相關數據後,View真正的執行就是setView()這一句開始,下面看看ViewRootImpl中是如何實現View的測量繪製的, ViewRootImpl是View中的最高層級,屬於全部View的根(但ViewRootImpl不是View,只是實現了ViewParent接口),實現了View和WindowManager之間的通訊協議異步
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()方法ide
//調用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的測量、佈局和繪製,因此在執行第一此requestLayout()時內部的View就已經測量和繪製好了,剩下的就是將View添加到Window中顯示了;函數
@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()方法:oop
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
複製代碼
在removeViewLocked()方法中,首先根據傳入的index,獲取緩存集合中保存的ViewRootImpl和View對象,而後將View保存到正在刪除的集合mDyingViews中,執行root.die()刪除View;
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
}
}
複製代碼
更新過程相對間的難一些,首先爲View 設置新的佈局參數,而後更新緩存中的Params,最後調用root.setLayoutParams()執行View 的更新,在 root.setLayoutParams中會觸發ViewRootImpl的scheduleTraversals實現View的再次測量、佈局、繪製;
![在這裏插入圖片描述](img-blog.csdnimg.cn/20190123181… =300x400) 借用網絡上的一幅圖展現Activity的層次關係:
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();
}
複製代碼
在setContentView()傳入佈局文件後,會調用getWindow().setContentView(layoutResID)爲Window設置佈局,此處的getWindow()獲得的就是前面建立的PhoneWindow ,因此setContentView()最終是在PhoneWindow中執行的
public void setContentView(int layoutResID) {
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
}
複製代碼
setContentView()方法中,首先判斷contentParent是否空,若是爲空則執行installDecor(),在剛建立的Activity中contentParent必定爲努力了,因此第一次加載會執行installDecor(),installDecor中有兩處代碼比較明顯,分別是初始化DecorView和mContentParent,下面分別看看這兩個方法
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
mDecor = generateDecor(-1); //1
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor); //2
}
}
複製代碼
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的佈局,而且加載的layoutRes佈局也已加載到ContentView中,因此關於DecorView內部的工做已經完成,但DecorView未被添加到Window中,因此此時界面還是不可見
在Activity的動過程當中,程序會調用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的構造函數中建立了PhoneWindow對象,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;
複製代碼
在makeToast()中首先加載了系統佈局,並將提示的內容設置在TextView中,而後將結果保存在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()中執行如下操做:
本文從Window、WindowManager的屬性和工做過程,結合Android現有系統中的實例,詳細的介紹了WindowManager的原理和做用,相信整個流程的學習必定對Android中界面的展現有更深入的認識;