Android PopupWindow Dialog 关于 is your activity running 崩溃详解
Android PopupWindow Dialog 关于 is your activity running 崩溃详解
起因
对于 PopupWindow Dialog
需要 Activity 作为容器,并于其生命周期联系在一起.在Activity 还没有初始化完成时,此时我们调用 PopupWindow Dialog
的show()
方法就会抛出异常:
throw new WindowManager.BadTokenException("Unable to add window -- token " + attrs.token+ " is not valid; is your activity running?");
常见的崩溃日志如下:
android.view.WindowManager$BadTokenException: Unable to add window -- token android.os.BinderProxy@406a074 is not valid; is your activity running?
at android.view.ViewRoot.setView(ViewRoot.java:530)
at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:199)
at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:113)
at android.view.Window$LocalWindowManager.addView(Window.java:424)
at android.app.Dialog.show(Dialog.java:241)
at com.eleybourn.bookcatalogue.dialogs.StandardDialogs.goodreadsAuthAlert(StandardDialogs.java:261)
at com.eleybourn.bookcatalogue.goodreads.GoodreadsUtils$4$1.run(GoodreadsUtils.java:101)
at android.os.Handler.handleCallback(Handler.java:587)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:130)
at android.app.ActivityThread.main(ActivityThread.java:3687)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:507)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:878)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:636)
at dalvik.system.NativeStart.main(Native Method)
解决办法
if(!Activity.isFinishing()){
mPopupWindow.show(anchor);
}
如果页面结束时 PopupWindow Dialog
没有dismiss()
,那么会出现内存泄漏,日志如下:
SpecialTopicActivity has leaked window android.widget.PopupWindow$PopupDecorView{dfa91cc V.E...... ........ 0,0-185,86} that was originally added here
at android.view.ViewRootImpl.<init>(ViewRootImpl.java:573)
at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:326)
at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:109)
at android.widget.PopupWindow.invokePopup(PopupWindow.java:1333)
at android.widget.PopupWindow.showAsDropDown(PopupWindow.java:1156)
at android.widget.PopupWindow.showAsDropDown(PopupWindow.java:1115)
at com.fanwe.customview.PopTipShare.show(PopTipShare.java:56)
at com.fanwe.seller.views.SpecialTopicActivity$4.run(SpecialTopicActivity.java:222)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:148)
at android.app.ActivityThread.main(ActivityThread.java:7224)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1230)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1120)
...
在Activity执行onDestroy()
时 dismiss 掉 PopupWindow Dialog
即可;
if (mPopupWindow!=null && mPopupWindow.isShowing()){
mPopupWindow.dismiss();
}
源码
需要涉及的类:
ViewManager
:WindowManager
的父类.WindowManager
及其实现类WindowManagerImpl(@hide)
: 接口类与实现类,对用户开放.ViewRootImpl(@hide)
: WindowManager 的 View 的操作实现类.WindowManagerGlobal(@hide)
:WindowManagerImpl
类功能的执行者.
1. 在调用 Show() 方法时
注:以下情况都是对PopupWindow Dialog
适用的,现在不再指明PopupWindow Dialog
,下面以PopupWindow
为例一步步说明.
直接展示源码可能更容易说明问题,注释是关键点,以下源码展示以方法执行顺序进行.
public void showAsDropDown(View anchor, int xoff, int yoff, int gravity) {
if (isShowing() || mContentView == null) {
return;
}
TransitionManager.endTransitions(mDecorView);
attachToAnchor(anchor, xoff, yoff, gravity);
mIsShowing = true;
mIsDropdown = true;
final WindowManager.LayoutParams p = createPopupLayoutParams(anchor.getWindowToken());
preparePopup(p);
final boolean aboveAnchor = findDropDownPosition(anchor, p, xoff, yoff,
p.width, p.height, gravity);
updateAboveAnchor(aboveAnchor);
p.accessibilityIdOfAnchor = (anchor != null) ? anchor.getAccessibilityViewId() : -1;
//以上都是准备 WindowManager.LayoutParams p;
invokePopup(p);
}
private void invokePopup(WindowManager.LayoutParams p) {
if (mContext != null) {
p.packageName = mContext.getPackageName();
}
final PopupDecorView decorView = mDecorView;
decorView.setFitsSystemWindows(mLayoutInsetDecor);
setLayoutDirectionFromAnchor();
//调用WindowManager.addView()方法
mWindowManager.addView(decorView, p);
if (mEnterTransition != null) {
decorView.requestEnterTransition(mEnterTransition);
}
}
以上都是在PopupWindow
,在调用WindowManager.addView()
后进入WindowManager
类,实际是其实现类WindowManagerImpl
.
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
//调用了 WindowManagerGlobal.addView()方法.
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
重点在这里 WindowManagerGlobal.addView()
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
// ...
// 检查一些状态
// ViewRootImpl root : 添加 View 最后一步由此 View 操作类的 setView() 完成.
ViewRootImpl root;
View panelParentView = null;
//...
//准备需要的参数
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 {
//ViewRootImpl类的 setView() 方法.
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 的 setView() 方法
/**
* We have one child
*/
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;
mAttachInfo.mDisplayState = mDisplay.getState();
mDisplayManager.registerDisplayListener(mDisplayListener, mHandler);
mViewLayoutDirectionInitial = mView.getRawLayoutDirection();
//最终把 View 添加上去,如果异常了就传null.
mFallbackEventHandler.setView(view);
mWindowAttributes.copyFrom(attrs);
//...省略代码
if (DEBUG_LAYOUT) Log.v(mTag, "Added window " + mWindow);
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:
//这里集中处理异常情况
//异常实际抛出的ADD_BAD_SUBWINDOW_TOKEN
throw new WindowManager.BadTokenException(
"Unable to add window -- token " + attrs.token
+ " is not valid; is your activity running?");
case WindowManagerGlobal.ADD_NOT_APP_TOKEN:
throw new WindowManager.BadTokenException(
"Unable to add window -- token " + attrs.token
+ " is not for an application");
case WindowManagerGlobal.ADD_APP_EXITING:
throw new WindowManager.BadTokenException(
"Unable to add window -- app for token " + attrs.token
+ " is exiting");
case WindowManagerGlobal.ADD_DUPLICATE_ADD:
throw new WindowManager.BadTokenException(
"Unable to add window -- window " + mWindow
+ " has already been added");
case WindowManagerGlobal.ADD_STARTING_NOT_NEEDED:
// Silently ignore -- we would have just removed it
// right away, anyway.
return;
case WindowManagerGlobal.ADD_MULTIPLE_SINGLETON:
throw new WindowManager.BadTokenException("Unable to add window "
+ mWindow + " -- another window of type "
+ mWindowAttributes.type + " already exists");
case WindowManagerGlobal.ADD_PERMISSION_DENIED:
throw new WindowManager.BadTokenException("Unable to add window "
+ mWindow + " -- permission denied for window type "
+ mWindowAttributes.type);
case WindowManagerGlobal.ADD_INVALID_DISPLAY:
throw new WindowManager.InvalidDisplayException("Unable to add window "
+ mWindow + " -- the specified display can not be found");
case WindowManagerGlobal.ADD_INVALID_TYPE:
throw new WindowManager.InvalidDisplayException("Unable to add window "
+ mWindow + " -- the specified window type "
+ mWindowAttributes.type + " is not valid");
}
throw new RuntimeException(
"Unable to add window -- unknown error code " + res);
}
//....省略代码
}
}
}
从上面的代码中可以看出,判断异常类型的是一个int
值res,现在看看res.
try {
mOrigWindowType = mWindowAttributes.type;
mAttachInfo.mRecomputeGlobalAttributes = true;
collectViewAttributes();
// mWindowSession 的类型是 IWindowSession , mWindow 的类型是 IWindow.Stub .这行代码就是利用AIDL进行IPC, 实际被调用的是Session.addToDisplay()方法.
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mInputChannel);
} catch {//...}
进一步调用Session.java中的addToDisplay方法:
@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);
}
mService是WindowManagerService,继续看 WmS 的 addWindow() 方法.
这是最核心的类,关于所有的Android addView 最后都是通过 WmS 的 addWindow()
方法完成添加操作.
public int addWindow(Session session, IWindow client, int seq,
WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
InputChannel outInputChannel) {
int[] appOp = new int[1];
// 关于权限的检查,如果有使用 WindowManager 实现悬浮窗效果的对悬浮窗,关于 SYSTEM_ALERT_WINDOW 的申请问题肯定纠结过.
// 我在一篇文章说对于SDK 19以上,使用 WindowManager.LayoutParams.TYPE_TOAST,SDK 19 以下使用 WindowManager.LayoutParams.TYPE_PHONE
// 原因是:1.type为"TYPE_TOAST"在sdk19之前不接收事件,之后可以.
// 2.type为"TYPE_PHONE"需要"SYSTEM_ALERT_WINDOW"权限.在sdk19之前不可以直接申明使用,之后不能直接申明使用.
// 想知道为什么会这样,可以看看 mPolicy.checkAddPermission(attrs, appOp); 答案在这里面.(这里就不看了=.=)
int res = mPolicy.checkAddPermission(attrs, appOp);
if (res != WindowManagerGlobal.ADD_OKAY) {
return res;//如果权限不满足就不用继续了.
}
boolean reportNewConfig = false;
WindowState attachedWindow = null;
long origId;
final int type = attrs.type;
//...
//check something...
boolean addToken = false;
// mTokenMap 存储 WindowToken;
// 这里是取,后面在会执行 mTokenMap.put()方法,这样 token 就不为null了.
// Activity 的addWindow时会传入不为 null 的 token,然而 PopupWindow 和 Dialog 传入的是为 null 的token.
//final HashMap<IBinder, WindowToken> mTokenMap = new HashMap<>();
WindowToken token = mTokenMap.get(attrs.token);
AppWindowToken atoken = null;
//...
//check something...
if (addToken) {
//如果activity调用 WindowManager.addView(),token就会被 put 到 map 中.
mTokenMap.put(attrs.token, token);
}
win.attach();
mWindowMap.put(client.asBinder(), win);
//...省略
return res;
}
现在应该知道了在哪里抛出异常了,最后还有一点就是关于疑问的: 那 Activity 什么时候 才算 is running ?
Activity 什么时候 才算 is running ?
现在看看 ActivityThread类,这是 Activity 的管理类,分发Activity的生命周期等.
在 ActivityThread 的 handleResumeActivity() 方法中,有如下代码:
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
//...
// performResumeActivity() 方法最后会调用 Activity 的 OnResume() 方法.
r = performResumeActivity(token, clearHide, reason);
//....
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
if (r.mPreserveWindow) {
a.mWindowAdded = true;
r.mPreserveWindow = false;
// Normally the ViewRoot sets up callbacks with the Activity
// in addView->ViewRootImpl#setView. If we are instead reusing
// the decor view we have to notify the view root that the
// callbacks may have changed.
ViewRootImpl impl = decor.getViewRootImpl();
if (impl != null) {
impl.notifyChildRebuilt();
}
}
if (a.mVisibleFromClient && !a.mWindowAdded) {
a.mWindowAdded = true;
//这里调用了 WindowManager 的 addView() 方法,最终会调用 WindowManagerService 的 addWindow() 方法.然后就是之前看到的源码内容了.其他的情况也类似这个流程,但是还是有很多细微区别,比如 WindowManager.LayoutParams 的 Type 与 Flag 等.
wm.addView(decor, l);
}
//...
}
在 Activity 的生命周期 onResume 执行后不久, Activity 的 token 随着 wm.addView(decor, l);
后就被 put 到 map 中,其后调用 PopupWindow 与 Dialog 的 Show() 方法后就不会出现 "is your activity running ?"这种异常了.由于token在 performResumeActivity() 后(从代码中可以看出,是在同一方法体中执行完两个操作),所以有人在 Activity 的 onResume() 方法中这样写道:
@Override
protected void onResume() {
super.onResume();
mListview.postDelayed(new Runnable() {
@Override
public void run() {
mPopupWindow.show(anchor);
}
},100);
}
这种写法是不可取的.
在实际工作过程中,可能需要在界面展示后2s展示一个Dialog 或者 PopupWindow ,此时如果用户在2s内退出 Activity ,那么Runnable执行时无法使用一个Finished 的Activity 这里推荐的写法.
//第一种
mSomeView.postDelayed(new Runnable() {
@Override
public void run() {
if (!Activity.this.isFinishing()){
mPopupWindow.show();
}
}
},1000);
//第二种
mSomeView.post(new Runnable() {
@Override
public void run() {
if (!Activity.this.isFinishing()){
mPopupWindow.show();
}
}
});
Bugly社区整理一篇关于 Window 的文章总结的很不错,对于 Window 不是很了解的可以点此了解下. 我之前整理过PopupWindow 的实现过程,大概有一年了吧,现在竟然忘的差不多了,额....
剩余就是 Activity.isFinishing() 方法的具体调用与实现过程了. Google API 的说明是:
Check to see whether this activity is in the process of finishing, either because you called finish on it or someone else has requested that it finished. This is often used in onPause to determine whether the activity is simply pausing or completely finishing.
检测 Activity 是否在 Process 或者 Finishing过程中.有空看看具体过程.
Android PopupWindow Dialog 关于 is your activity running 崩溃详解的更多相关文章
- Android中Intent传值与Bundle传值的区别详解
Android中Intent传值与Bundle传值的区别详解 举个例子我现在要从A界面跳转到B界面或者C界面 这样的话 我就需要写2个Intent如果你还要涉及的传值的话 你的Intent就要写两 ...
- Android 高级UI设计笔记07:RecyclerView 的详解
1. 使用RecyclerView 在 Android 应用程序中列表是一个非常重要的控件,适用场合非常多,如新闻列表.应用列表.消息列表等等,但是从Android 一出生到现在并没有非常 ...
- 转: Android 软件开发之如何使用Eclipse Debug调试程序详解(七)
转自: http://www.uml.org.cn/mobiledev/201110092.asp Android 软件开发之如何使用Eclipse Debug调试程序详解(七) 发布于2011- ...
- Android为TV端助力 转载:Android绘图Canvas十八般武器之Shader详解及实战篇(上)
前言 Android中绘图离不开的就是Canvas了,Canvas是一个庞大的知识体系,有Java层的,也有jni层深入到Framework.Canvas有许多的知识内容,构建了一个武器库一般,所谓十 ...
- Android为TV端助力 转载:Android绘图Canvas十八般武器之Shader详解及实战篇(下)
LinearGradient 线性渐变渲染器 LinearGradient中文翻译过来就是线性渐变的意思.线性渐变通俗来讲就是给起点设置一个颜色值如#faf84d,终点设置一个颜色值如#CC423C, ...
- Android学习笔记:Activity生命周期详解
进行android的开发,必须深入了解Activity的生命周期.而对这个讲述最权威.最好的莫过于google的开发文档了. 本文的讲述主要是对 http://developer.android.co ...
- Android之Activity启动流程详解(基于api28)
前言 Activity作为Android四大组件之一,他的启动绝对没有那么简单.这里涉及到了系统服务进程,启动过程细节很多,这里我只展示主体流程.activity的启动流程随着版本的更替,代码细节一直 ...
- Activity生命周期详解
http://blog.csdn.net/liuhe688/article/details/6733407 onPause 回到 onResume 的过程“在一般的开发中用不上”,但是作为开发者还是有 ...
- 【Android】时间选择器,选择日期DatePicker 简单详解demo及教程
作者:程序员小冰,GitHub主页:https://github.com/QQ986945193 新浪微博:http://weibo.com/mcxiaobing 首先给大家看一下我们今天这个最终实现 ...
随机推荐
- Ubuntu下使用nvm
写在前面:刚写着写着博客就跨年了,希望新的一年大家万事如意,一切向"前"看! 安装 wget -qO- https://raw.githubusercontent.com/crea ...
- boost强分类器的实现
boost.cpp文件下: bool CvCascadeBoost::train( const CvFeatureEvaluator* _featureEvaluator, int _numSampl ...
- Go结构体实现类似成员函数机制
Go语言结构体成员能否是函数,从而实现类似类的成员函数的机制呢?答案是肯定的. package main import "fmt" type stru struct { testf ...
- 【NLP】Python NLTK获取文本语料和词汇资源
Python NLTK 获取文本语料和词汇资源 作者:白宁超 2016年11月7日13:15:24 摘要:NLTK是由宾夕法尼亚大学计算机和信息科学使用python语言实现的一种自然语言工具包,其收集 ...
- Java中的Socket的用法
Java中的Socket的用法 Java中的Socket分为普通的Socket和NioSocket. 普通Socket的用法 Java中的 ...
- C#异步编程
什么是异步编程 什么是异步编程呢?举个简单的例子: using System.Net.Http; using System.Threading.Tasks; using static System.C ...
- 【干货分享】流程DEMO-加班与调休
流程名: 加班.调休 业务描述: 加班: 工作日加班可以申请调休,也可以申请支付加班费.原则上都应申请调休:周末加班原则上申请调休:法定节假日加班支付加班费. 加班申请以半小时为单位. 当月加班不能 ...
- SNMP简单网络管理协议
声明:以下内容是学习谌玺老师视频整理出来(http://edu.51cto.com/course/course_id-861.html) SNMP(Simple Network Management ...
- 一个简单的网站web项目的详解
有不对的术语,或者不好理解的部分,欢迎大家批评指正,谢谢大家! 近期做的网站web项目,实现登录功能,查询功能.首先把这个项目分为几个模块来处理,当前用户模块,历史用户模块,历史记录模块,数据库模块, ...
- Express 教程 01 - 入门教程之经典的Hello World
目录: 前言 一.Express?纳尼?! 二.开始前的准备工作 三.测试安装之经典的Hello World 四.使用express(1)来生成一个应用程序 五.说明 前言: 本篇文章是建立在Node ...