Dialog显示和消失流程分析
来源:互联网 发布:手机游戏双开软件 编辑:程序博客网 时间:2024/06/05 23:02
本文所引用的代码为Android 5.0(API 22)版本
Dialog类实现了DialogInterface, Window.Callback, KeyEvent.Callback, OnCreateContextMenuListener, Window.OnWindowDismissedCallback这个五个接口。比较常用到的三个接口:Window.Callback是接收屏幕touch事件;KeyEvent.Callback为接收键盘按键事件和实体键消息;Window.OnWindowDismissedCallback为接收窗口消失回调。
本文从Dialog的创建,隐藏,显示和移除步骤来分析相关的代码流程。最后补充了几个常见的异常分析。
请尊重博主劳动成果,转载请标明出处。
创建
看看构造方法代码:
//提供给外部调用人口 public Dialog(Context context) { this(context, 0, true); } //提供给外部调用人口 public Dialog(Context context, int theme) { this(context, theme, true); } //真正执行初始化的构造方法 Dialog(Context context, int theme, boolean createContextThemeWrapper) { if (createContextThemeWrapper) { if (theme == 0) { TypedValue outValue = new TypedValue(); context.getTheme().resolveAttribute(com.android.internal.R.attr.dialogTheme, outValue, true); theme = outValue.resourceId; } mContext = new ContextThemeWrapper(context, theme); } else { mContext = context; } //获取WindowManagerImpl对象,这个对象一般为Activity的windowManger mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE); //创建PhoneWindow对象 Window w = PolicyManager.makeNewWindow(mContext); //赋值,mWindow即PhoneWindow对象,不是WindowManger mWindow = w; 设置Window.Callback回调 w.setCallback(this); //window dismiss回调 w.setOnWindowDismissedCallback(this); //调用Window的setWindowManager创建PhoneWindow内部的WindowMangerImpl w.setWindowManager(mWindowManager, null, null); //默认设置为居中 w.setGravity(Gravity.CENTER); //接收异步消息的Handler mListenersHandler = new ListenersHandler(this); }
PolicyManager#makeNewWindow相关代码:
路径:/sources/android-22/com/android/internal/policy/PolicyManager.java
public static Window makeNewWindow(Context context) { return sPolicy.makeNewWindow(context); }
sPolicy为Policy类的对象:
路径:/sources/android-22/com/android/internal/policy/impl/Policy.java
public Window makeNewWindow(Context context) { return new PhoneWindow(context); }
Window的setWindowManager方法相关代码:
public void setWindowManager(WindowManager wm, IBinder appToken, String appName) { setWindowManager(wm, appToken, appName, false); } public void setWindowManager(WindowManager wm, IBinder appToken, String appName, boolean hardwareAccelerated) { mAppToken = appToken; mAppName = appName; mHardwareAccelerated = hardwareAccelerated || SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false); if (wm == null) { wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE); } mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this); }
显示
show方法代码:
public void show() { if (mShowing) {//dialog已调用过show方法,调用dismiss会设置为false if (mDecor != null) { if (mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) { mWindow.invalidatePanelMenu(Window.FEATURE_ACTION_BAR); } //显示Dialog的根View,即Dialog便可见 mDecor.setVisibility(View.VISIBLE); } return; } mCanceled = false; if (!mCreated) { dispatchOnCreate(null); } onStart(); //创建DecorView mDecor = mWindow.getDecorView(); 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); } //设置布局参数 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 { //添加到当前显示的窗口,便可以看见dialog mWindowManager.addView(mDecor, l); mShowing = true; //发送dialog显示消息 sendShowMessage(); } finally { } }
mDecor为DecorView对象,DecorView继承了FrameLayout。在Android 6.0以下版本,DecorView为PhoneWindow的内部类;6.0及以上版本,则被抽出,为一个独立的类。
我们在使用Dialog调用findViewById和setContentView方法,其实都是PhoneWindow中的方法在执行相应的操作。
Dialog中相关方法的代码:
public View findViewById(int id) { return mWindow.findViewById(id); } public void setContentView(int layoutResID) { mWindow.setContentView(layoutResID); } public void setContentView(View view) { mWindow.setContentView(view); } public void setContentView(View view, ViewGroup.LayoutParams params) { mWindow.setContentView(view, params); } public void addContentView(View view, ViewGroup.LayoutParams params) { mWindow.addContentView(view, params); }
再看看PhoneWindow中的相关代码:
//获取DecorView @Override public final View getDecorView() { if (mDecor == null || mForceDecorInstall) { //如果没有,则创建 installDecor(); } return mDecor; } //创建和初始化DecorView private void installDecor() { if (mDecor == null) {//如果为null,则创建 mDecor = generateDecor(); mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); mDecor.setIsRootNamespace(true); if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) { mDecor.postOnAnimation(mInvalidatePanelMenuRunnable); } } ...//省略部分代码 } //创建DecorView对象 protected DecorView generateDecor() { //创建DecorView,-1为布局资源id,即不解析xml布局文件 return new DecorView(getContext(), -1); } @Override public void setContentView(int layoutResID) { // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window // decor, when theme attributes and the like are crystalized. Do not check the feature // before this happens. if (mContentParent == null) { installDecor(); } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) { mContentParent.removeAllViews(); } if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) { final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID, getContext()); transitionTo(newScene); } else { mLayoutInflater.inflate(layoutResID, mContentParent); } //在Dialog的mWindow中,cb为Dialog本身 final Callback cb = getCallback(); if (cb != null && !isDestroyed()) { cb.onContentChanged(); } } @Override public void setContentView(View view) { setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); } @Override public void setContentView(View view, ViewGroup.LayoutParams params) { // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window // decor, when theme attributes and the like are crystalized. Do not check the feature // before this happens. if (mContentParent == null) { installDecor(); } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) { mContentParent.removeAllViews(); } if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) { view.setLayoutParams(params); final Scene newScene = new Scene(mContentParent, view); transitionTo(newScene); } else { mContentParent.addView(view, params); } final Callback cb = getCallback(); if (cb != null && !isDestroyed()) { cb.onContentChanged(); } } @Override public void addContentView(View view, ViewGroup.LayoutParams params) { if (mContentParent == null) { installDecor(); } if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) { // TODO Augment the scenes/transitions API to support this. Log.v(TAG, "addContentView does not support content transitions"); } mContentParent.addView(view, params); final Callback cb = getCallback(); if (cb != null && !isDestroyed()) { cb.onContentChanged(); } }
这里就不细讲DecorView是如何被创建的,有兴趣的可以去看看源码,或看这篇博客:Android应用setContentView与LayoutInflater加载解析机制源码分析。
再看看mWindowManager.addView(mDecor, l)这步操作。从创建这一节的分析可知mWindowManager为WindowMangerImpl的对象。
先介绍下WindowMangerImpl代码:
public final class WindowManagerImpl implements WindowManager { private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance(); // private final Display mDisplay; private final Window mParentWindow; private IBinder mDefaultToken; ...//省略部分代码 //添加View public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) { applyDefaultToken(params); mGlobal.addView(view, params, mDisplay, mParentWindow); } private void applyDefaultToken(@NonNull ViewGroup.LayoutParams params) { // Only use the default token if we don't have a parent window. if (mDefaultToken != null && mParentWindow == null) { if (!(params instanceof WindowManager.LayoutParams)) { throw new IllegalArgumentException("Params must be WindowManager.LayoutParams"); } // Only use the default token if we don't already have a token. final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params; if (wparams.token == null) { wparams.token = mDefaultToken; } } } ...//省略部分代码 //移除View,后面的dimiss会调用到这个方法 public void removeViewImmediate(View view) { //true为立即移除 mGlobal.removeView(view, true); } ...//省略部分代码}
继续看WindowManagerGobal的addView方法代码:
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) { ...//省略 //ViewRootImpl接收接收底层服务发送来的屏幕触摸事件,然后将这些事件传递给DecorView, //并返回结果(true或false)给底层服务。 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); } } } //创建对象 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 { //添加DecorView到ViewRootView,执行完这一步,dialog便能接收屏幕事件 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; } }
关于ViewRootImpl如何接收和传递屏幕事件,可以阅读Activity touch事件传递流程分析。
经历了setContent和show,漂亮的Dialog便显示在手机上。
隐藏和消失
隐藏调用的是hide方法。如果dialog在当前页面被频繁调用,则可以用这个方法。它不会将Dialog的根视图mDecor从当前移除,仅仅是将其设置为不可见。
hide方法代码
public void hide() { if (mDecor != null) { mDecor.setVisibility(View.GONE); } }
当我们调用代码dismiss,点击back键或点击dialog外部区域,便可以将dialog隐藏。
//提供给外部调用的方法 public void dismiss() { if (Looper.myLooper() == mHandler.getLooper()) { //在主线程,则直接调用 dismissDialog(); } else { //非主线程,则发送消息,让mListenersHandler处理 mHandler.post(mDismissAction); } } //真正执行dismiss的方法 void dismissDialog() { //如果mDecor为null或dismiss的,则返回 if (mDecor == null || !mShowing) { return; } //如果PhoneWindow被销毁,则返回 if (mWindow.isDestroyed()) { Log.e(TAG, "Tried to dismissDialog() but the Dialog's window was already destroyed!"); return; } try { //移除mDecor,其实是mWindowManager的内部WindowManagerGlobal对象执行移除操作 mWindowManager.removeViewImmediate(mDecor); } finally { if (mActionMode != null) { mActionMode.finish(); } mDecor = null;//置为null,释放 mWindow.closeAllPanels(); onStop(); mShowing = false;//修改标志为false //发送dismiss事件 sendDismissMessage(); } }
这个方法里最重要的操作为mWindowManager.removeViewImmediate(mDecor),这步操作将Dialog直接从当前视图移除,并销毁释放。所以在页面中被频繁显示的的dialog采用hide比dimiss更高效。
继续看WindowManagerGlobal代码:
public void removeView(View view, boolean immediate) { if (view == null) { throw new IllegalArgumentException("view must not be null"); } synchronized (mLock) { //查找当前View在记录集合中的位置 int index = findViewLocked(view, true); View curView = mRoots.get(index).getView(); //移除ViewRootImpl removeViewLocked(index, immediate); if (curView == view) { return; } //如果要移除的View与记录中的不同,则抛出异常 throw new IllegalStateException("Calling with view " + view + " but the ViewAncestor is attached to " + curView); } } //查找位置 private int findViewLocked(View view, boolean required) { final int index = mViews.indexOf(view); if (required && index < 0) { //如果该View已被移除,则抛出异常 throw new IllegalArgumentException("View=" + view + " not attached to window manager"); } return index; } private void removeViewLocked(int index, boolean immediate) { ViewRootImpl root = mRoots.get(index); View view = root.getView(); if (view != null) {//断开键盘与当前View的关联 InputMethodManager imm = InputMethodManager.getInstance(); if (imm != null) { imm.windowDismissed(mViews.get(index).getWindowToken()); } } //ViewRootImpl执行die操作 boolean deferred = root.die(immediate); if (view != null) { view.assignParent(null); if (deferred) { mDyingViews.add(view); } } }
经过以上三个步骤的分析,我们清楚知道了Dialog是如何创建,显示和消失移除。
补充:常见的异常分析
工作中可能会出现的一些异常,现在就分析下它们出现的原因。
WindowLeaked
异常信息:
android.view.WindowLeaked: Activity xx.xxActivity has leaked window com.android.internal.policy.impl.PhoneWindow$DecorView{3e7fbc08 V.E..... R.....I. 0,0-960,231} that was originally added hereat android.view.ViewRootImpl.<init>(ViewRootImpl.java:462)at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:278)at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:85)at android.app.Dialog.show(Dialog.java:311)
这个是在Activity执行destroy时,dialog还没有被执行dismiss操作而抛出的。需要在onPause()或onStop()中调用dismiss释放dialog窗口。
如果跟着日志显示的类去查找,估计会转晕。其实,它在ActivityThread执行handleDestroyActivity操作时抛出的。如果跟着代码显示的日志,该异常出现在onDestroy的日志后面。
相关代码:
//ActivityThread的handleDestroyActivity方法代码 private void handleDestroyActivity(IBinder token, boolean finishing, int configChanges, boolean getNonConfigInstance) { ActivityClientRecord r = performDestroyActivity(token, finishing, configChanges, getNonConfigInstance); if (r != null) { cleanUpPendingRemoveWindows(r); WindowManager wm = r.activity.getWindowManager(); View v = r.activity.mDecor; if (v != null) { if (r.activity.mVisibleFromServer) { mNumVisibleActivities--; } //当前Activity的window token, IBinder wtoken = v.getWindowToken(); if (r.activity.mWindowAdded) { if (r.onlyLocalRequest) { // Hold off on removing this until the new activity's // window is being added. r.mPendingRemoveWindow = v; r.mPendingRemoveWindowManager = wm; } else { wm.removeViewImmediate(v); } } if (wtoken != null && r.mPendingRemoveWindow == null) { //关闭所有与wtoken相关的所有DecorView WindowManagerGlobal.getInstance().closeAll(wtoken, r.activity.getClass().getName(), "Activity"); } r.activity.mDecor = null; } if (r.mPendingRemoveWindow == null) { // If we are delaying the removal of the activity window, then // we can't clean up all windows here. Note that we can't do // so later either, which means any windows that aren't closed // by the app will leak. Well we try to warning them a lot // about leaking windows, because that is a bug, so if they are // using this recreate facility then they get to live with leaks. //关闭所有与token相关的所有DecorView WindowManagerGlobal.getInstance().closeAll(token, r.activity.getClass().getName(), "Activity"); } …//省略代码 } …//省略代码 } //WindowManagerGlobal的closeAll方法 public void closeAll(IBinder token, String who, String what) { synchronized (mLock) { int count = mViews.size(); //Log.i("foo", "Closing all windows of " + token); //遍历DecorView,对比token for (int i = 0; i < count; i++) { //Log.i("foo", "@ " + i + " token " + mParams[i].token // + " view " + mRoots[i].getView()); //如果当前传入的token为null,或DecorView的token与之相同 if (token == null || mParams.get(i).token == token) { ViewRootImpl root = mRoots.get(i); //Log.i("foo", "Force closing " + root); //当前传入的who为‘Activity’ if (who != null) { //即这个窗口没有被移除,出现泄漏了 WindowLeaked leak = new WindowLeaked( what + " " + who + " has leaked window " + root.getView() + " that was originally added here"); leak.setStackTrace(root.getLocation().getStackTrace()); Log.e(TAG, "", leak); } //将其移除 removeViewLocked(i, false); } } } }
IllegalArgumentException
这个异常调用dismiss时出现的,常出现在WindowLeaked之后。在长时间的异步操作后,Activity可能被回收或调用finish释放,再在主线程调用dismiss时可能会出现这个异常。主要出现在网络请求结束关闭加载框时。解决方法:在dismiss前使用isFinishing()判断当前Activity是否被finish,或者在onStop()或onPaus()方法中调用dismiss,再在onStart方法中判断是否需要显示。
异常信息:
java.lang.IllegalArgumentException: View=com.android.internal.policy.impl.PhoneWindow$DecorView{3e7fbc08 V.E..... R.....I. 0,0-960,231} not attached to window managerat android.view.WindowManagerGlobal.findViewLocked(WindowManagerGlobal.java:416)at android.view.WindowManagerGlobal.removeView(WindowManagerGlobal.java:342)at android.view.WindowManagerImpl.removeViewImmediate(WindowManagerImpl.java:116)at android.app.Dialog.dismissDialog(Dialog.java:354)at android.app.Dialog.dismiss(Dialog.java:337)
抛出异常的代码在隐藏和消失这节的WindowManagerGlobal的findViewLocked方法中。因为mDecor已经被移除了,而dialog又没有收到关闭消息,因此dialog认为mDecor还在当前视图中,所以再次remove便报错了。
BadTokenException
这个异常是使用new创建Dialog是没有传入Activity对象,而是其它的Context。导致token验证没有通过,而抛出异常。如果具有系统权限,则可以以其它的Context创建。
android.view.WindowManager$BadTokenException: Unable to add window -- token null is not valid; is your activity running?at android.view.ViewRootImpl.setView(ViewRootImpl.java:685)at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:289)at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:85)at android.app.Dialog.show(Dialog.java:311)
ViewRootImpl.setView的代码:
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) { synchronized (this) { if (mView == null) { mView = view; …//省略 int res; /* = WindowManagerImpl.ADD_OKAY; */ …//省略 try { mOrigWindowType = mWindowAttributes.type; mAttachInfo.mRecomputeGlobalAttributes = true; collectViewAttributes(); //验证token res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes, getHostVisibility(), mDisplay.getDisplayId(), mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mInputChannel); } catch (RemoteException e) { …//省略 } finally { …//省略 } …//省略 //判断token是否可用 if (res < WindowManagerGlobal.ADD_OKAY) { mAttachInfo.mRootView = null; mAdded = false; mFallbackEventHandler.setView(null); unscheduleTraversals(); setAccessibilityFocus(null, null); switch (res) { case WindowManagerGlobal.ADD_BAD_APP_TOKEN: case WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN: throw new WindowManager.BadTokenException( "Unable to add window -- token " + attrs.token + " is not valid; is your activity running?"); …//省略 } throw new RuntimeException( "Unable to add window -- unknown error code " + res); } …//省略 } } }
- Dialog显示和消失流程分析
- 让dialog消失和显示的方法
- 设置dialog显示,自定义时间到后dialog消失
- Android自定义Dialog带Dialog的显示消失动画(一)
- Dialog显示流程 源码笔记
- android 自定义dialog弹出和消失动画
- android 自定义dialog弹出和消失动画
- android 自定义dialog弹出和消失动画
- android 自定义dialog弹出和消失动画
- dialog第一个消失后再显示第二个
- div的显示和消失
- android dialog和activity 改变出现消失的 动画样式
- android 自定义dialog弹出和消失缩放动画
- AlertDialog.Builder和Dialog分析
- popupwindow 显示和消失的动画
- jQuery图片显示消失和切换
- uboot阶段显示流程分析
- Dialog根据网络状态显示,网络正常自动消失时,dismiss无效
- zookeeper leader和follower启动期交互过程
- Python3 学习之basedao.py的优化详解
- Python标准库之xml.etree.ElementTree
- 蓝桥杯 ALGO-58 算法训练 字串逆序
- 简单点,Html
- Dialog显示和消失流程分析
- equals和==的区别
- 设计模式--工厂模式--Java实现
- 蓝桥杯 ALGO-106 算法训练 6-3判定字符位置
- oracle
- HDFS上传下载
- 嵌入式学习笔记(第四天)c语言续
- C语言:min和max头文件
- HBase scan RPC次数计算