美文网首页
源码学习_12Dialog

源码学习_12Dialog

作者: 冉桓彬 | 来源:发表于2018-05-02 21:52 被阅读21次

    一、参考文章:

    为什么Dialog不能用Application的Context
    使用Dialog时需要注意的问题
    创建Dialog所需的上下文为什么必须是Activity?

    二、问题:

    • 1、为什么Dialog不能传ApplicationContext, 以及如何解决?
    image.png

    三、demo:

    public class Activity {
        public void method() {
            DialogTest dialogTest = new DialogTest(getApplicationContext());
            dialogTest.show();
        }
    }
    
    public class DialogTest extends Dialog {
    
        DialogTest(@NonNull Context context) {
            super(context);
        }
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.dialog_test);
        }
    }
    
    • 上面代码会GG, 错误提示如下:
    构造Dialog传入ApplicationContext会报错

    四、源码分析:

    4.1 Dialog构造函数:
    public class Dialog {
        public Dialog(@NonNull Context context) {
            this(context, 0, true);
        }
    
        Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
            if (createContextThemeWrapper) {
                mContext = new ContextThemeWrapper(context, themeResId);
            } else {...}
            /**
             * 1. WindowManager指向WindowManagerImpl;
             * 2. Context有两种情况: Context instanceof Application; Context instanceof ActivityContext,
             *    结合这两种模式构建WindowManagerImpl的方式可能不同<4.2>;
             * 3. 结合模块<4.2>可知, 如果Context instanceof Application, 构建一个Application_WML, 如果
             *    Context instanceof ActivityContext, 则返回Activity_WML;
             */
            mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
    
            final Window w = new PhoneWindow(mContext);
            mWindow = w;
            /**
             * 1. ContextThemeWrapper持有我们外部传进来的Context, 可能指向ApplicationContext/ActivityContext;
             * 2. PhoneWindow只有ContextThemeWrapper的引用;
             */
            w.setWindowManager(mWindowManager, null, null);
        }
    }
    
    public class PhoneWindow {
    
        public Window(Context context) {
            mContext = context;
            ...
        }
    
        public void setWindowManager(WindowManager wm, IBinder appToken, String appName) {
            setWindowManager(wm, appToken, appName, false);
        }
        /**
         * 1. 结合上面传来的参数---> Dialog构造函数会触发PhoneWindow被初始化, 然后通过setWindowManager
         *    将PhoneWindow与WindowManager进行关联;
         * 2. 此时PhoneWindow内部的mAppToken默认为null;
         */
        public void setWindowManager(WindowManager wm, IBinder appToken, String appName, boolean hardwareAccelerated) {
            mAppToken = appToken;
            if (wm == null) {
                wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
            }
            mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
        }
    }
    
    public final class WindowManagerImpl implements WindowManager {
        public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
            return new WindowManagerImpl(mContext, parentWindow);
        }
    
        private WindowManagerImpl(Context context, Window parentWindow) {
            mContext = context;
            mParentWindow = parentWindow;
        }
    }
    
    4.2 WindowManagerImpl构建:
    public class Application {
        @Override
        public Object getSystemService(String name) {
            return mBase.getSystemService(name);
        }
    }
    
    public class ContextImpl {
        @Override
        public Object getSystemService(String name) {
            return SystemServiceRegistry.getSystemService(this, name);
        }
    }
    
    public class Activity {
        @Override
        public Object getSystemService(@ServiceName @NonNull String name) {
            if (WINDOW_SERVICE.equals(name)) {
                return mWindowManager;
            } else if (SEARCH_SERVICE.equals(name)) {
                ...
            }
            return super.getSystemService(name);
        }
    }
    
    4.3 Dialog.show:
    public void show() {
        /**
         * mShowing默认为false, 调用show()方法之后, mShowing = true, dismiss被触发时, mShowing = false;
         */
        if (mShowing) {
            return;
        }
        mCanceled = false;
        /**
         * 1. 默认为false, 触发dispatchOnCreate之后, mCreated = true;
         * 2. dispatchOnCreate内部会触发onCreate的执行;
         */
        if (!mCreated) {
            dispatchOnCreate(null);
        } else {
            final Configuration config = mContext.getResources().getConfiguration();
            mWindow.getDecorView().dispatchConfigurationChanged(config);
        }
        /**
         * 采用模板模式, 控制Dialog的生命周期;
         */
        onStart();
        /**
         * 这里采用的方式其实有些类似Activity, 我们在onCreate中执行setContentView方法, 然后
         * 该方法会构造一个DecorView出来, 这里的mWindow指向PhoneWindow, mDecor指向DecorView;
         */
        mDecor = mWindow.getDecorView();
        /**
         * 构建LayoutParams; 模块<4.4>
         * 结合模块<4.4>可知, Dialog窗口类型默认为应用级别;
         */
        WindowManager.LayoutParams l = mWindow.getAttributes();
        /**
         * 1. mWindowManager实际指向WindowManagerImpl;模块<4.5>
         * 2. onCreate内部通过setContentView构建DecorView树, 然后通过这里将DecorView与WindowManager
         *    进行绑定, 并绘制在界面上;
         */
        mWindowManager.addView(mDecor, l);
        mShowing = true;
        ...
    } 
    
    4.4 PhoneWindow.getAttributes:
    public class PhoneWindow {
        private final WindowManager.LayoutParams mWindowAttributes = new WindowManager.LayoutParams();
    
        public final WindowManager.LayoutParams getAttributes() {
            return mWindowAttributes;
        }
    }
    
    public class WindowManager.LayoutParams {
        public LayoutParams() {
            /**
             * 创建Dialog会走这个方法, 也就是说Dialog的窗口类型默认为应用级别;
             */
            type = TYPE_APPLICATION;
        }
    }
    
    4.5 WindowManagerImpl.addView:
    @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        /**
         * 对token进行赋值操作; 模块<4.6>
         */
        applyDefaultToken(params);
        mGlobal.addView(view, params, mDisplay, mParentWindow);
    }
    
    public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) {
        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
        ...
        ViewRootImpl root;
        View panelParentView = null;
        synchronized (mLock) {
            ...          
            root = new ViewRootImpl(view.getContext(), display);
            ...
        }
        /**
         * 1. 将DecorView与ViewRootImpl进行绑定, 然后显示在WindowManager中; 
         * 2. 如果构建Dialog时传入Application会抛出上文所述异常, 而异常的原因就出在setView里面;模块<4.7>
         */
        root.setView(view, wparams, panelParentView);
    }
    
    4.6 WindowManagerImpl.applyDefaultToken:
    private void applyDefaultToken(@NonNull ViewGroup.LayoutParams params) {
        /**
         * 结合模块<4.1> ~ <4.2>可知, 针对传入的Context不同, WMI有两种情况:
         * 1. 如果传入的Context属于Application, 则直接跳过<//1--->>处的if语句;  
         * 2. 如果传入的Context属于Activity, 则根据Activity启动流程可知, if内语句被赋值, 所以
         *    wparams.token不为空;
         */
        if (mDefaultToken != null && mParentWindow == null) {
            final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
    //1--->
            if (wparams.token == null) {
                wparams.token = mDefaultToken;
            }
        }
    }
    
    4.7 ViewRootImpl.setView:
    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            mView = view;
            attrs = mWindowAttributes;
            /**
             * 1. 通过查看ViewRootImpl构造函数可知, mWindowSession实际指向Session;模块<4.8>
             * 2. 结合模块<4.8> 可知mWindowSession.addToDisplay最终调用WMS的addWindow方法;模块<4.9>
             */
            res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                                    getHostVisibility(), mDisplay.getDisplayId(),
                                    mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                                    mAttachInfo.mOutsets, mInputChannel);
                ...
            if (res < WindowManagerGlobal.ADD_OKAY) {
                ...
                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?");
                    case WindowManagerGlobal.ADD_NOT_APP_TOKEN:
                        throw new WindowManager.BadTokenException(
                                        "Unable to add window -- token " + attrs.token
                                        + " is not for an application");
                }
            }
        }
    }
    
    4.8 Session.addToDisplay:
    @Override
    public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
                int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets,
                Rect outOutsets, InputChannel outInputChannel) {
        /**
         * mService指向的是WMS; 模块<4.9>
         */
        return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
                    outContentInsets, outStableInsets, outOutsets, outInputChannel);
    }
    
    4.9 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];
        /**
         * 1. 校验机制太长了, checkAddPermission触发窗口类型和权限机制的校验; 模块<4.10>;
         * 2. 结合模块<4.10>可知, res有以下几种类型:
         *    (1) WindowManagerGlobal.ADD_OKAY: 如果窗口类型为应用级别, 或者系统级别且对应权限已经
         *        进行了申请(包括动态申请/在清单文件中申请);
         *    (2) WindowManagerGlobal.ADD_PERMISSION_DENIED: 窗口级别为需要申请权限的系统级别, 但
         *        是没有进行权限申请时;
         * 3. 如果type为WindowManagerGlobal.ADD_PERMISSION_DENIED, 则直接返回该值, 反之在<//1>处
         *    继续进行具体的判断;
         */
        int res = mPolicy.checkAddPermission(attrs, appOp);
        if (res != WindowManagerGlobal.ADD_OKAY) {
            return res;
        }
    //1--->
        /**
         * 结合模块<7>可知, type类型从大方面来说有三种: 系统级别、应用级别, 子窗口级别;
         */
        final int type = attrs.type;
    
        synchronized(mWindowMap) {
            ...
            boolean addToken = false;
            /**
             * token默认来自何处? 这里在回到Dialog的构造函数中;从Dialog构造函数<4.10>中了解到构造Dialog时, 
             * token默认被置为null, 然后如果Context instanceof ActivityContext时, taken会被赋值是因为
             * Dialog使用的是Activity的WML, 持有的token实际为Activity的token;
             */
            WindowToken token = mTokenMap.get(attrs.token);
            /**
             * 1. 能执行到这里, 说明res == WindowManagerGlobal.ADD_OKAY成立, 而该等式成立分三种情况:
             *   (1) 窗口类型为应用级别;
             *   (2) 窗口类型为系统级别, 且该系统级别不需要申请权限;
             *   (3) 窗口类型为系统级别, 且该系统级别需要申请权限, 而且确实已经申请了权限;
             * 2. 如果context属于Activity, 则token!=null, 如果context属于Application,
             *    则taken == null;
             */
            if (token == null) {
                /**
                 * 1. 窗口类型为应用级别, 且token == null, 此时抛出该异常, 响应模块<4.7>
                 */
                if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) {
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
                ...
                /**
                 * 1. 执行到这里说明: token == null, 且窗口级别为系统级别;
                 * 2. 然后初始化token;
                 */
                token = new WindowToken(this, attrs.token, -1, false);
                addToken = true;
            } 
            else if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) {...} 
            else if (type == TYPE_INPUT_METHOD) {...} 
            else if (type == TYPE_VOICE_INTERACTION) {...} 
            else if (type == TYPE_WALLPAPER) {...} 
            else if (type == TYPE_DREAM) {...} 
            else if (type == TYPE_ACCESSIBILITY_OVERLAY) {...} 
            else if (token.appWindowToken != null) {...}
            ...
            /**
             * 再次对type进行校验; 模块<4.11>
             */
            res = mPolicy.prepareAddWindowLw(win, attrs);
            if (res != WindowManagerGlobal.ADD_OKAY) {
                return res;
            }
            res = WindowManagerGlobal.ADD_OKAY;
        }
        return res;
    }
    
    4.10 PhoneWindowManager.checkAddPermission:
    @Override
    public int checkAddPermission(WindowManager.LayoutParams attrs, int[] outAppOp) {
        /**
         * 1. 结合上文---> attrs = PhoneWindow.getAttributes();模块<4.7>
         * 2. 结合模块<4.7>可知, Dialog窗口类型默认为应用级别, 即TYPE_APPLICATION, 直接在<//1--->>
         *    返回WindowManagerGlobal.ADD_OKAY;
         */
        int type = attrs.type;
    //1--->
        /**
         * 如果这里if()内部执行语句为false, 也就是说当前窗口类型为系统级别或者是子窗口级别, 跳转至<//2--->>
         */
        if (type < FIRST_SYSTEM_WINDOW || type > LAST_SYSTEM_WINDOW) {
            return WindowManagerGlobal.ADD_OKAY;
        }
    //2--->
        String permission = null;
        /**
         * 1. 如果窗口为系统级别, 在switch内部判断是否对具体系统级别申请了对应的权限; 
         * 2. 如果权限申请成功, 则返回type为WindowManagerGlobal.ADD_OKAY;
         * 3. 如果权限申请失败, 则返回type为WindowManagerGlobal.ADD_PERMISSION_DENIED;
         */
        switch (type) {
            case TYPE_TOAST:
                outAppOp[0] = AppOpsManager.OP_TOAST_WINDOW;
                break;
            case TYPE_DREAM:
            case TYPE_INPUT_METHOD:
            case TYPE_WALLPAPER:
            case TYPE_PRIVATE_PRESENTATION:
            case TYPE_VOICE_INTERACTION:
            case TYPE_ACCESSIBILITY_OVERLAY:
                break;
            case TYPE_PHONE:
            case TYPE_PRIORITY_PHONE:
            case TYPE_SYSTEM_ALERT:
            case TYPE_SYSTEM_ERROR:
            case TYPE_SYSTEM_OVERLAY:
                permission = android.Manifest.permission.SYSTEM_ALERT_WINDOW;
                outAppOp[0] = AppOpsManager.OP_SYSTEM_ALERT_WINDOW;
                break;
            default:
                permission = android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
        }
        if (permission != null) {
            if (permission == android.Manifest.permission.SYSTEM_ALERT_WINDOW) {
                final int callingUid = Binder.getCallingUid();
                if (callingUid == Process.SYSTEM_UID) {
                    return WindowManagerGlobal.ADD_OKAY;
                }
                final int mode = mAppOpsManager.checkOp(outAppOp[0], callingUid, attrs.packageName);
                switch (mode) {
                    case AppOpsManager.MODE_ALLOWED:
                    case AppOpsManager.MODE_IGNORED:
                        return WindowManagerGlobal.ADD_OKAY;
                    case AppOpsManager.MODE_ERRORED:
                        return WindowManagerGlobal.ADD_PERMISSION_DENIED;
                    default:
                        if (mContext.checkCallingPermission(permission) != PackageManager.PERMISSION_GRANTED) {
                            return WindowManagerGlobal.ADD_PERMISSION_DENIED;
                        } else {
                            return WindowManagerGlobal.ADD_OKAY;
                        }
                }
            }
    
            if (mContext.checkCallingOrSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {
                return WindowManagerGlobal.ADD_PERMISSION_DENIED;
            }
        }
        return WindowManagerGlobal.ADD_OKAY;
    }
    
    4.11 PhoneWindowManager.prepareAddWindowLw:
    @Override
    public int prepareAddWindowLw(WindowState win, WindowManager.LayoutParams attrs) {
        switch (attrs.type) {
            case TYPE_STATUS_BAR:...break;
            case TYPE_NAVIGATION_BAR:...break;
            case TYPE_NAVIGATION_BAR_PANEL:
            case TYPE_STATUS_BAR_PANEL:
            case TYPE_STATUS_BAR_SUB_PANEL:
            case TYPE_VOICE_INTERACTION_STARTING:...break;
            case TYPE_KEYGUARD_SCRIM:...break;
        }
        return WindowManagerGlobal.ADD_OKAY;
    }
    

    五、如何避免在Dialog中使用ApplicationContext抛异常的问题:

    5.1 改变Dialog窗口的级别类型:
    在show方法执行之前;
    public class Window {
        public void setType(int type) {
            final WindowManager.LayoutParams attrs = getAttributes();
            attrs.type = type;
            dispatchWindowAttributesChanged(attrs);
        }
    }
    
    DialogTest(@NonNull Context context) {
        super(context);
        getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
    }
    
    5.2 申请权限:
    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
    

    或者直接将type类型改为Toast类型;

    ublic class DialogTest extends Dialog {
        DialogTest(@NonNull Context context) {
            super(context);
            getWindow().setType(WindowManager.LayoutParams.TYPE_TOAST);
        }
    }
    

    相关文章

      网友评论

          本文标题:源码学习_12Dialog

          本文链接:https://www.haomeiwen.com/subject/hfrhrftx.html