跳出 PopupWindow 使用过程中的坑
遇到异常 Exception
在做页面首次使用引导需求时使用 PopupWindow 来实现,没想到测试过程中一直遇到这两种异常而崩溃:
android.view.WindowManager$BadTokenException
Unable to add window – token android.os.BinderProxy@74dec62 is not valid; is your activity running?java.lang.IllegalArgumentException
View=android.widget.PopupWindow$PopupDecorView{78549e8 V.E…… ……I. 0,0-0,0} not attached to window manager
这种崩溃让人百思不得其解,因为 PopupWindow 使用还是比较简单的,出错代码如下:
public class TestActivity extends Activity {
private TextView mTextView;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = (TextView) findViewById(R.id.text_view);
loadApiFromNet(new Callback() { // 服务器接口调用
@Override
public void onSuccess(String str) {
// 其他处理忽略
showPopup();
}
});
}
private void showPopup() {
View popupView = LayoutInflater.from(this).inflate(R.layout.popup, null);
((TextView) popupView.findViewById(R.id.tv_text)).setText("测试测试");
PopupWindow popup = new PopupWindow(popupView, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, true);
popup.setTouchable(false);
if (null != mTextView) {
popup.showAsDropDown(mTextView);
}
}
}
解决方案
原因是 showAsDropDown() 方法中以来的 View 类型实例还没有依附到绘制 Window 中,因此将 PopupWindow 的显示逻辑延时即可解决问题:
if (null != view.getWindowToken() ) {
popupWindow.showAsDropDown(view, 0, 0);
} else {
view.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(View v) {
if (null != v.getWindowToken()) {
popupWindow.showAsDropDown(v, 0, 0);
v.removeOnAttachStateChangeListener(this);
}
}
@Override
public void onViewDetachedFromWindow(View v) {
}
});
}
寻找真相
问题解决了,但是这就结束了?
当然不行
知其然,更要知其所以然。怎么能放过这么个了解 Android 的机会呢?
那就让我们从源码的角度出发,探究造成异常的根源吧!
PopupWindow 的调用栈顺序为:
/**
* Displays the content view in a popup window anchored to the corner of
* another view. The window is positioned according to the specified
* gravity and offset by the specified x and y coordinates.
* <p>
* If there is not enough room on screen to show the popup in its entirety,
* this method tries to find a parent scroll view to scroll. If no parent
* view can be scrolled, the specified vertical gravity will be ignored and
* the popup will anchor itself such that it is visible.
* <p>
* If the view later scrolls to move <code>anchor</code> to a different
* location, the popup will be moved correspondingly.
*
* @param anchor the view on which to pin the popup window
* @param xoff A horizontal offset from