Bootstrap

[未完待续] 跳出 PopupWindow 使用过程中的坑

跳出 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 
;