前言
Android在DialogFragment推出後,就已經不推薦繼續使用Dialog,可替換為DialogFragment,其實DialogFragment只不過是對增加一層看不到的Fragment,用於監聽生命週期,在Activity退出的時候會自動回收Dialog彈窗
基礎概念
- Activity:活動。控制生命週期和處理事件,統籌檢視的新增與顯示,控制Window和View的互動
- Window:視窗。在Android中是個虛擬的概念,不是View,是承載View的載體,具體實現是PhoneWindow,承載著DecorView
- WindowManager:視窗管理者。管理Window檢視的新增或移除等,具體實現是WindowManagerService(wms)
- DecorView:視窗根檢視。本身是FrameLayout,是Window上真正的根佈局,其包含兩部分,標題和內容
- TitleView:標題。作為DecorView的子View,其Id為@android:id/content
- ContentViews:內容。作為DecorView的子View,其Id為@android:id/content
- ViewRoot:連線wms和DecorView的紐帶,View的measure、layout、draw均通過ViewRoot來完成,具體實現是ViewRootImpl
Dialog
在平時中,簡單的彈出Dialog只需要這句話
new Dialog(MainActivity.this).show();
複製程式碼
一、Dialog的顯示
1、Dialog
Dialog的構造方法有多個,但最後都會呼叫這個構造方法
Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
if (createContextThemeWrapper) {
if (themeResId == 0) {
//如果沒有主題,則使用預設主題
final TypedValue outValue = new TypedValue();
context.getTheme().resolveAttribute(R.attr.dialogTheme, outValue, true);
themeResId = outValue.resourceId;
}
//包裹主題的Context
mContext = new ContextThemeWrapper(context, themeResId);
} else {
mContext = context;
}
//獲取windowManager服務
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
//建立新的Window
final Window w = new PhoneWindow(mContext);
mWindow = w;
//設定callback
w.setCallback(this);
w.setOnWindowDismissedCallback(this);
w.setWindowManager(mWindowManager, null, null);
w.setGravity(Gravity.CENTER);
mListenersHandler = new ListenersHandler(this);
}
複製程式碼
從Dialog的構造方法中可以看出,Dialog實質上是個Window,其顯示和隱藏也是藉助WindowManager去控制的
2、Dialog.show
public void show() {
//如果之前已經show過後,就讓檢視顯示即可
if (mShowing) {
if (mDecor != null) {
if (mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {
mWindow.invalidatePanelMenu(Window.FEATURE_ACTION_BAR);
}
mDecor.setVisibility(View.VISIBLE);
}
return;
}
mCanceled = false;
//如果沒有create則會呼叫dispatchOncreate,該方法最終會呼叫dialog的onCreate方法
if (!mCreated) {
dispatchOnCreate(null);
}
//dialog的onstart回撥
onStart();
//獲取decorView
mDecor = mWindow.getDecorView();
//如果需要ActionBar,則建立出來
if (mActionBar == null && mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {
final ApplicationInfo info = mContext.getApplicationInfo();
mWindow.setDefaultIcon(info.icon);
mWindow.setDefaultLogo(info.logo);
mActionBar = new WindowDecorActionBar(this);
}
//window引數的設定
WindowManager.LayoutParams l = mWindow.getAttributes();
if ((l.softInputMode
& WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) == 0) {
WindowManager.LayoutParams nl = new WindowManager.LayoutParams();
nl.copyFrom(l);
nl.softInputMode |=
WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
l = nl;
}
try {
//windowManager將decorView加入檢視
mWindowManager.addView(mDecor, l);
mShowing = true;
sendShowMessage();
} finally {
}
}
複製程式碼
show()其實就是走Dialog的生命週期,然後做初始化工作,獲取Window上的DecorView後,將DecorView新增到檢視上,這裡需要注意的是在show()之後才執行onCreate()
3、Dialog.dispatchOnCreate
void dispatchOnCreate(Bundle savedInstanceState) {
if (!mCreated) {
onCreate(savedInstanceState); //回撥onCreate()
mCreated = true;
}
}
protected void onCreate(Bundle savedInstanceState) {
//由開發者實現
}
複製程式碼
Dialog的初始化其實就是讓使用者去初始化自己的檢視,平時我們是這麼寫的
public class RxDialog extends Dialog {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//設定檢視
setContentView(mView);
}
}
複製程式碼
具體的邏輯還是回到**setContentView()**設定Dialog的檢視
4、Dialog.setContentView
public void setContentView(View view) {
//呼叫mWindow進行檢視設定,mWindow實際上就是構造方法中的PhoneWindow
mWindow.setContentView(view);
}
複製程式碼
mWindow則是在構造方法建立的PhoneWindow
5、PhoneWindow.setContentView
@Override
public void setContentView(View view) {
//預設MATCH_PARENT
setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
@Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
if (mContentParent == null) {
installDecor(); //建立應用程式視窗檢視物件
} else {
mContentParent.removeAllViews(); //重新設定應用程式視窗的檢視
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
view.setLayoutParams(params);
final Scene newScene = new Scene(mContentParent, view);
transitionTo(newScene);
} else {
mContentParent.addView(view, params); //將我們傳遞進來的view新增布局上
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
......
}
複製程式碼
PhoneWindow.setContentView()不僅在Dialog中存在,在Activity的setContentView也是走到這裡。mContentParent指的是依附於DecorView上的R.id.content中的view。到這裡只是將Dialog設定的View載入到PhoneWindow的ContentView上,其實更主要的還是PhoneWindow新增到我們的手機螢幕上,程式碼回溯到show()的mWindowManager.addView(mDecor, l)
6、WindowManagerImpl.addView
WindowManager本質上是對View進行管理,但是WindowManager顯然依然是個介面,其具體實現是WindowManagerImpl,最後還是委託給WindowManagerGlobal例項mGlobal處理
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
//委託給mGlobal來進行實現
mGlobal.addView(view, params, mDisplay, mParentWindow);
}
複製程式碼
7、WindowManagerGlobal.addView
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);
} else {
// If there's no parent, then hardware acceleration for this view is
// set from the application's hardware acceleration setting.
final Context context = view.getContext();
if (context != null
&& (context.getApplicationInfo().flags
& ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
}
}
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
// Start watching for system property changes.
if (mSystemPropertyUpdater == null) {
mSystemPropertyUpdater = new Runnable() {
@Override public void run() {
synchronized (mLock) {
for (int i = mRoots.size() - 1; i >= 0; --i) {
mRoots.get(i).loadSystemProperties();
}
}
}
};
SystemProperties.addChangeCallback(mSystemPropertyUpdater);
}
int index = findViewLocked(view, false);
if (index >= 0) {
if (mDyingViews.contains(view)) {
// Don't wait for MSG_DIE to make it's way through root's queue.
mRoots.get(index).doDie();
} else {
throw new IllegalStateException("View " + view
+ " has already been added to the window manager.");
}
// The previous removeView() had not completed executing. Now it has.
}
// If this is a panel window, then find the window it is being
// attached to for future reference.
if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
final int count = mViews.size();
for (int i = 0; i < count; i++) {
if (mRoots.get(i).mWindow.asBinder() == wparams.token) {
panelParentView = mViews.get(i);
}
}
}
//建立ViewRootImpl,ViewRootImpl是view和window中的連線紐帶
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
//儲存相關View的資訊,會在Remove的時候移除,相當於快取
mViews.add(view);//mViews:儲存的是所有Window對應的View,本質是個List
mRoots.add(root);//mRoots:儲存的是所有Window所對應的ViewRootImpl,本質是個List
mParams.add(wparams);//mParams:儲存所有window對應的佈局引數,本質是個List
}
// do this last because it fires off messages to start doing things
try {
//最終由root去實現最終的檢視顯示
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
synchronized (mLock) {
final int index = findViewLocked(view, false);
if (index >= 0) {
removeViewLocked(index, true);
}
}
throw e;
}
}
複製程式碼
將檢視新增到視窗上的工作交給root.setView(),root就是ViewRootImpl
8、ViewRootImpl.setView
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
......
requestLayout(); //真正完成檢視的非同步重新整理請求
......
//這裡呼叫了mWindowSession的addToDisplay方法,在WindowManagerService層通過IPC機制完成真正的window新增
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mInputChannel);
}
}
}
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals(); //真正的去走Measure、Layout、Draw
}
}
複製程式碼
在新增view之前,會走requestLayout(),真正實現View繪製的三部曲Measure、Layout、Draw。mWindowSession型別是IWindowSession,它是個Binder物件,真正的實現類是Session,window的新增過程實際上是一次ipc的呼叫,最後在WindowManagerService層通過IPC機制去實現的
總結
在這讀完這裡原始碼後,我們知道Window是個相對虛擬的物件,真正的操作是對Window中的DecorView進行addView()操作,而且在addView()之前,會先走onCreate()、onStart()、setContentView()操作,而在setContentView()過程中,會經過ViewRootImpl物件進行setView,並且在ViewRootImpl物件中會實現View繪製的三步曲,Measure、Layout、Draw操作,最後再將繪製好的view通過IWindowSession的ipc呼叫新增到介面上
- Dialog本質上是個Window,具體是通過Window的DecorView進行顯示的
- Dialog是在show()之後走的onCreate()、onStart()、setContentView()等回撥