精华内容
下载资源
问答
  • DialogWindow创建过程

    千次阅读 2019-06-08 21:49:56
    DialogWindow的创建过程和Activity类似 首先看一下dialog构造方法里面代码 Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) { if (createContextThemeWrapper...
    • Dialog的Window的创建过程和Activity类似
    • 首先看一下dialog构造方法里面代码
     Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
            if (createContextThemeWrapper) {
                if (themeResId == ResourceId.ID_NULL) {
                    final TypedValue outValue = new TypedValue();
                    context.getTheme().resolveAttribute(R.attr.dialogTheme, outValue, true);
                    themeResId = outValue.resourceId;
                }
                mContext = new ContextThemeWrapper(context, themeResId);
            } else {
                mContext = context;
            }
    
            mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
    		//  创建window
            final Window w = new PhoneWindow(mContext);
            mWindow = w;
            w.setCallback(this);
            w.setOnWindowDismissedCallback(this);
            w.setOnWindowSwipeDismissedCallback(() -> {
                if (mCancelable) {
                    cancel();
                }
            });
            w.setWindowManager(mWindowManager, null, null);
            w.setGravity(Gravity.CENTER);
    
            mListenersHandler = new ListenersHandler(this);
        }
    

    从上面的代码可以看出,通过final Window w = new PhoneWindow(mContext);创建window对象,接下来设置各种回调

    • 接下来要做的就是初始化DecorView并将Dialog的视图添加到DecorView中,接下来看一下Dialog的setContentView方法
    public void setContentView(@LayoutRes int layoutResID) {
            mWindow.setContentView(layoutResID);
        }
    

    直接调用的window的setContentView方法。和Activity的逻辑基本是一样的了,最后在dialog的show()方法中将DecorView添加到Window中代码如下

    public void show() {
            if (mShowing) {
                if (mDecor != null) {
                    if (mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {
                        mWindow.invalidatePanelMenu(Window.FEATURE_ACTION_BAR);
                    }
                    mDecor.setVisibility(View.VISIBLE);
                }
                return;
            }
    
            mCanceled = false;
    
            if (!mCreated) {
                dispatchOnCreate(null);
            } else {
                // Fill the DecorView in on any configuration changes that
                // may have occured while it was removed from the WindowManager.
                final Configuration config = mContext.getResources().getConfiguration();
                mWindow.getDecorView().dispatchConfigurationChanged(config);
            }
    
            onStart();
            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;
            }
    		// 添加 mDecor
            mWindowManager.addView(mDecor, l);
            mShowing = true;
    
            sendShowMessage();
        }
    

    Dialog的Window创建和Activity的Window创建过程很类似,二者几乎没有什么区别。当Dialog被关闭时,它会通过WindowManager来移除DecorView:mWindowManager.removeViewImmediate(mDecor)。

     void dismissDialog() {
            if (mDecor == null || !mShowing) {
                return;
            }
    
            if (mWindow.isDestroyed()) {
                Log.e(TAG, "Tried to dismissDialog() but the Dialog's window was already destroyed!");
                return;
            }
    
            try {
                mWindowManager.removeViewImmediate(mDecor);
            } finally {
                if (mActionMode != null) {
                    mActionMode.finish();
                }
                mDecor = null;
                mWindow.closeAllPanels();
                onStop();
                mShowing = false;
    
                sendDismissMessage();
            }
        }
    

    Dialog有个特殊之处,如果用applicationContext来启动的话会报错报错如下:

    android.view.WindowManager$BadTokenException: Unable to add window -- token null is not valid; is your activity running?
            at android.view.ViewRootImpl.setView(ViewRootImpl.java:809)
            at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:356)
            at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:94)
            at android.app.Dialog.show(Dialog.java:330)
            at com.demo.myapplication.MainActivity.showMyDialog(MainActivity.kt:26)
            at com.demo.myapplication.MainActivity.access$showMyDialog(MainActivity.kt:9)
            at com.demo.myapplication.MainActivity$onCreate$1.onClick(MainActivity.kt:19)
            at android.view.View.performClick(View.java:6311)
            at android.view.View$PerformClick.run(View.java:24833)
            at android.os.Handler.handleCallback(Handler.java:794)
            at android.os.Handler.dispatchMessage(Handler.java:99)
            at android.os.Looper.loop(Looper.java:173)
            at android.app.ActivityThread.main(ActivityThread.java:6653)
            at java.lang.reflect.Method.invoke(Native Method)
            at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:547)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:821)
    

    错误的原因就是找不到token这个标识,而应用token一般只有Activity才有所以只要用Activity作为Context来显示Dialog即可。
    另外系统Window比较特殊,它可以不需要token。WindowManager.LayoutParams中的type表示Window的类型,而系统Window的层级范围是2000~2999,这些层级范围就对应着type参数。系统Window的层级有很多值,对于本例来说,可以选用TYPE_SYSTEM_OVERLAY,TYPE_SYSTEM_ERROR 来指定对话框的Window类型为系统Window,并在AndroidManifest文件中声明权限从而可以使用系统Window

    dialog.window.setType(WindowManager.LayoutParams.TYPE_SYSTEM_ERROR)
    //或者 dialog.window.setType(WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY)
    
    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
    
    展开全文
  • Android中Dialog中的Window添加过程解析

    千次阅读 2019-06-14 19:54:20
    Android中Dialog真的只能依赖Activity? Dialog一直作为一种依附在Activity上下文才能存在的窗口视图,那是否可以逃脱Activity的上下文,采用其他Context存在呢?答案是肯定的,Dialog完全可以不依赖Activity上下文...

    Dialog中的Window添加过程解析

    Dialog一直作为一种依附在Activity上下文才能存在的窗口视图,那是否可以逃脱Activity的上下文,采用其他Context存在呢?答案是肯定的,Dialog完全可以不依赖Activity上下文存在,这里我们说的只是非Activity的Context,不是不依赖Context

    • Dialog的组成:
      Dialog是一种承载Window的容器,而Window的唯一实现便是PhoneWindow,Dialog的setContentView就是将布局文件的id传给PhoneWindow,PhoneWindow通过该布局id解析然后创建一个DecorView,这是一个继承FrameLayout的ViewGroup,每个Window都有一个WindowManagerImpl,这里所说的是每个非子window类型的window,因为子window是依附于父window,父子共用一个WindowManagerImpl,普通的Dialog的WindowManagerImpl与Activity是共用的
    • Dialog的创建代码:
    
        Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
            if (createContextThemeWrapper) {
                if (themeResId == ResourceId.ID_NULL) {
                    final TypedValue outValue = new TypedValue();
                    context.getTheme().resolveAttribute(R.attr.dialogTheme, outValue, true);
                    themeResId = outValue.resourceId;
                }
                mContext = new ContextThemeWrapper(context, themeResId);
            } else {
                mContext = context;
            }
    
            mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); // 获取windowmanager
     
            final Window w = new PhoneWindow(mContext);
            mWindow = w;
            w.setCallback(this);
            w.setOnWindowDismissedCallback(this);
            w.setOnWindowSwipeDismissedCallback(() -> {
                if (mCancelable) {
                    cancel();
                }
            });
            w.setWindowManager(mWindowManager, null, null);  // phonewindow 与 Windowmanager绑定
            w.setGravity(Gravity.CENTER);
    
            mListenersHandler = new ListenersHandler(this);
        }
    

    再来看一下setContentView代码:

       Dialog.java
        public void setContentView(@LayoutRes int layoutResID) {
            mWindow.setContentView(layoutResID);
        }
    PhoneWindow.java
    public void setContentView(int layoutResID) {
            if (mContentParent == null) {
                installDecor();  // 创建DecorView与PhoneWindow绑定
            } 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);
            }
            mContentParent.requestApplyInsets();
            final Callback cb = getCallback();
            if (cb != null && !isDestroyed()) {
                cb.onContentChanged();
            }
            mContentParentExplicitlySet = true;
        }
    

    以上也说明Dialog中视图的构建过程,其实就是Dialog持有一个Context、phonewindow、windowmanager,其中Context如果是Activity的话,因为Activity是集成ContextWrapTheme类,所以由Activity上下文构建的Dialog是和Activity的主题一样的。phonewindow持有视图解析后的View结构树DecorView,而WindowManager,如果是子窗口类型,便使用父窗口的windowmanager,如果是系统窗口,将独立创建一个windowmanager,其实每个WindowManager也是一个傀儡,真正执行View操作的的事WindowManagerGlobal,这个每个Application只有一个该单例对象,管理当前app的所有window以及view的跟新操作,可以看一下为什么WindowManager是个傀儡,这个类代码就几十行:

    public final class WindowManagerImpl implements WindowManager {
        private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
        private final Context mContext;
        private final Window mParentWindow;
    
        private IBinder mDefaultToken;
    
        public WindowManagerImpl(Context context) {
            this(context, null);
        }
    
        private WindowManagerImpl(Context context, Window parentWindow) {
            mContext = context;
            mParentWindow = parentWindow;
        }
    
        public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
            return new WindowManagerImpl(mContext, parentWindow);
        }
    
        public WindowManagerImpl createPresentationWindowManager(Context displayContext) {
            return new WindowManagerImpl(displayContext, mParentWindow);
        }
    
        /**
         * Sets the window token to assign when none is specified by the client or
         * available from the parent window.
         *
         * @param token The default token to assign.
         */
        public void setDefaultToken(IBinder token) {
            mDefaultToken = token;
        }
    
        @Override
        public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
            applyDefaultToken(params);
            mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
        }
    
        @Override
        public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
            applyDefaultToken(params);
            mGlobal.updateViewLayout(view, params);
        }
    
        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;
                }
            }
        }
    
        @Override
        public void removeView(View view) {
            mGlobal.removeView(view, false);
        }
    
        @Override
        public void removeViewImmediate(View view) {
            mGlobal.removeView(view, true);
        }
    
        @Override
        public void requestAppKeyboardShortcuts(
                final KeyboardShortcutsReceiver receiver, int deviceId) {
            IResultReceiver resultReceiver = new IResultReceiver.Stub() {
                @Override
                public void send(int resultCode, Bundle resultData) throws RemoteException {
                    List<KeyboardShortcutGroup> result =
                            resultData.getParcelableArrayList(PARCEL_KEY_SHORTCUTS_ARRAY);
                    receiver.onKeyboardShortcutsReceived(result);
                }
            };
            try {
                WindowManagerGlobal.getWindowManagerService()
                    .requestAppKeyboardShortcuts(resultReceiver, deviceId);
            } catch (RemoteException e) {
            }
        }
    
        @Override
        public Display getDefaultDisplay() {
            return mContext.getDisplay();
        }
    
    

    可以看到WindowMangaerGlobal这个类才是真正执行window视图添加跟新操作的,这个类有四个数据结构:

        private final ArrayList<View> mViews = new ArrayList<View>();  // 存储所有的DecorView
        private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>(); // 每个窗口对应的视图树管理类
        private final ArrayList<WindowManager.LayoutParams> mParams =
                new ArrayList<WindowManager.LayoutParams>();   // 存储所有window的布局参数 包括窗口类型
        private final ArraySet<View> mDyingViews = new ArraySet<View>(); // 存储刚才remove的窗口中的view
    

    为了方便梳理这个window窗口的添加过程,看一下dialog.show():

     public void show() {
            if (mShowing) { // 是否正在展示状态
                if (mDecor != null) {   // 是否设置了view
                    if (mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {  // 判断Context带有的主题是否包含ActionBar 或者用户是否手动设置了
                        mWindow.invalidatePanelMenu(Window.FEATURE_ACTION_BAR);
                    }
                    mDecor.setVisibility(View.VISIBLE); // 设置DecorView可见 当View属于Visible 才会触发绘制流程
                }
                return;
            }
    
            mCanceled = false;
    
            if (!mCreated) {
                dispatchOnCreate(null);
            } else {
                // Fill the DecorView in on any configuration changes that
                // may have occured while it was removed from the WindowManager.
                final Configuration config = mContext.getResources().getConfiguration(); 
                mWindow.getDecorView().dispatchConfigurationChanged(config);   // 当app配置改变时回调OnConfigChange
            }
    
            onStart();
            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;
            }
    
            mWindowManager.addView(mDecor, l);  // 重点   将DecorView添加给windowmanager
            mShowing = true;
    
            sendShowMessage();
        }
    

    在Dialog创建的时候PhoneWindow与WindowManager绑定,在show的时候讲DecorView交给WindowManager,继续看 mWindowManager.addView(mDecor, l); 上面知道真正执行addView的事WindowManagerGlobal:

    public void addView(View view, ViewGroup.LayoutParams params,
                Display display, Window parentWindow) {
                ... 
            final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
            if (parentWindow != null) {  // 判断是否有父窗口 如果context 会执行下面一句 因为父窗就是Activity所持有的phonewindow
                parentWindow.adjustLayoutParamsForSubWindow(wparams);
            } else {
                // If there's no parent, then hardware acceleration for this view is
                // set from the application's hardware acceleration setting.
                final Context context = view.getContext();
                if (context != null
                        && (context.getApplicationInfo().flags
                                & ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
                    wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; //开启硬件加速
                }
            }
    
            ViewRootImpl root;
            View panelParentView = null;
              // 创建View视图管理
                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 {
                    root.setView(view, wparams, panelParentView);  // 将DecorView交由VIewRootImp管理与Wms交互
                } catch (RuntimeException e) {
                    // BadTokenException or InvalidDisplayException, clean up.
                    if (index >= 0) {
                        removeViewLocked(index, true);
                    }
                    throw e;
                }
            }
        }
    

    上面最关键的两行代码:

    void adjustLayoutParamsForSubWindow(WindowManager.LayoutParams wp) {  // 如果窗口类型是子窗口类型 将该DecorView的WindowToken赋值给该窗口管理器的布局参数
            CharSequence curTitle = wp.getTitle();
            if (wp.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
                    wp.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
                if (wp.token == null) {
                    View decor = peekDecorView();
                    if (decor != null) {
                        wp.token = decor.getWindowToken();
                    }
                }
                }
    
    1. root.setView(view, wparams, panelParentView); // 将DecorView交由VIewRootImp管理与Wms交互
      最后其实addView交给了ViewRootImpl,截取添加有本文有关的代码:
                    int res;                         /* = WindowManagerImpl.ADD_OKAY; */
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                                getHostVisibility(), mDisplay.getDisplayId(),
                                mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                                mAttachInfo.mOutsets, mInputChannel);
                    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?");
                            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);
                    }
    
    

    以上代码是ViewRootImpl中setView的代码其中最重要的就是res的赋值,res默认是window可以添加的 WindowManagerImpl.ADD_OKAY:

      res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                                getHostVisibility(), mDisplay.getDisplayId(),
                                mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                                mAttachInfo.mOutsets, mInputChannel);
    
    • mWindowSession是一个用户与Wms交互的接口,源码查看只有十个左右的接口:
    interface IWindowSession {
        int add(IWindow window, int seq, in WindowManager.LayoutParams attrs,
                in int viewVisibility, out Rect outContentInsets,
                out InputChannel outInputChannel);
        int addToDisplay(IWindow window, int seq, in WindowManager.LayoutParams attrs,
                in int viewVisibility, in int layerStackId, out Rect outContentInsets,
                out InputChannel outInputChannel);
        int addWithoutInputChannel(IWindow window, int seq, in WindowManager.LayoutParams attrs,
                in int viewVisibility, out Rect outContentInsets);
        int addToDisplayWithoutInputChannel(IWindow window, int seq, in WindowManager.LayoutParams attrs,
                in int viewVisibility, in int layerStackId, out Rect outContentInsets);
        void remove(IWindow window);
        
        int relayout(IWindow window, int seq, in WindowManager.LayoutParams attrs,
                int requestedWidth, int requestedHeight, int viewVisibility,
                int flags, out Rect outFrame,
                out Rect outContentInsets, out Rect outVisibleInsets,
                out Configuration outConfig, out Surface outSurface);
    
        void performDeferredDestroy(IWindow window);
    
        boolean outOfMemory(IWindow window);
    
        void setTransparentRegion(IWindow window, in Region region);
        
        void setInsets(IWindow window, int touchableInsets, in Rect contentInsets,
                in Rect visibleInsets, in Region touchableRegion);
        
        void getDisplayFrame(IWindow window, out Rect outDisplayFrame);
        
        void finishDrawing(IWindow window);
        
        void setInTouchMode(boolean showFocus);
        boolean getInTouchMode();
        
        boolean performHapticFeedback(IWindow window, int effectId, boolean always);
       
        IBinder prepareDrag(IWindow window, int flags,
                int thumbnailWidth, int thumbnailHeight, out Surface outSurface);
      
        boolean performDrag(IWindow window, IBinder dragToken, float touchX, float touchY,
                float thumbCenterX, float thumbCenterY, in ClipData data);
    
    	void reportDropResult(IWindow window, boolean consumed);
      
        void dragRecipientEntered(IWindow window);
     
        void dragRecipientExited(IWindow window);
     
        void setWallpaperPosition(IBinder windowToken, float x, float y, float xstep, float ystep);
        
        void wallpaperOffsetsComplete(IBinder window);
        
        Bundle sendWallpaperCommand(IBinder window, String action, int x, int y,
                int z, in Bundle extras, boolean sync)
        
        void wallpaperCommandComplete(IBinder window, in Bundle result);
        void setUniverseTransform(IBinder window, float alpha, float offx, float offy,
                float dsdx, float dtdx, float dsdy, float dtdy);
      
        void onRectangleOnScreenRequested(IBinder token, in Rect rectangle, boolean immediate);
    }
    

    而此处mWindowSession真正是实现是

    class Session extends IWindowSession.Stub implements IBinder.DeathRecipient 
    

    每个application对应一个IwindowSession的实现类Session对象 session很明显是Binder的服务端对象,而真正实现窗口添加的还是WindowManagerService,下面是Session添加window的代码:

    Session.java 
    
        @Override
        public int add(IWindow window, int seq, WindowManager.LayoutParams attrs,
                int viewVisibility, Rect outContentInsets, Rect outStableInsets,
                InputChannel outInputChannel) {
            return addToDisplay(window, seq, attrs, viewVisibility, Display.DEFAULT_DISPLAY,
                    new Rect() /* outFrame */, outContentInsets, outStableInsets, null /* outOutsets */,
                    new DisplayCutout.ParcelableWrapper()  /* cutout */, outInputChannel);
        }
    
    

    因此可以得出Session每个application进行窗口操作的唯一对应,想要进行窗口的操作必须通过Session来与Wms交互,因此下面一张图很清楚描述这一关系:
    Session关系图

    重点来了:

    窗口的添加从WindowManager - > WIndowManagerGlobal - > ViewRootImpl -> Session - > WindowMangagerService 这一系列检查包装最终交给Wms,wms的添加窗口操作如下,这个添加方法较长 ,只分析跟本来关联较大代码,拆开分析:

    • 方法名 关注一下这个返回值是一个窗口添加结果返回值
     public int addWindow(Session session, IWindow client, int seq,
                LayoutParams attrs, int viewVisibility, int displayId, Rect outFrame,
                Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
                DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel) 
    
    • 窗口 type 判断
    // 判断该window是否权限允许
    if (!displayContent.hasAccess(session.mUid)
                        && !mDisplayManagerInternal.isUidPresentOnDisplay(session.mUid, displayId)) {
                    Slog.w(TAG_WM, "Attempted to add window to a display for which the application "
                            + "does not have access: " + displayId + ".  Aborting.");
                    return WindowManagerGlobal.ADD_INVALID_DISPLAY;
                }
    // 判断这个client是否已经存在 这个Client 是每个Wms在客户端回调的Binder类
                if (mWindowMap.containsKey(client.asBinder())) {
                    Slog.w(TAG_WM, "Window " + client + " is already added");
                    return WindowManagerGlobal.ADD_DUPLICATE_ADD;
                }
    // 判断是否是子窗口类型的窗口
                if (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW) {
               // 如果是Dialog的Context是activity 且未修改dialog的窗口类型 下面这句代码就会获取到Activity的window
                    parentWindow = windowForClientLocked(null, attrs.token, false);
                    if (parentWindow == null) {   // 如果为null Activity的窗口已经不再显示(可能销毁 可能pause) 
                        Slog.w(TAG_WM, "Attempted to add window with token that is not a window: "
                              + attrs.token + ".  Aborting.");
                        return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
                    }
                    // 判断父window type如果也是子窗口类型window 返回ADD_BAD_SUBWINDOW_TOKEN
                    if (parentWindow.mAttrs.type >= FIRST_SUB_WINDOW
                            && parentWindow.mAttrs.type <= LAST_SUB_WINDOW) {
                        Slog.w(TAG_WM, "Attempted to add window with token that is a sub-window: "
                                + attrs.token + ".  Aborting.");
                        return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
                    }
                }
    
    

    以上就是为啥你在使用Window时进场报错的最终触发点,而这里的错误只是提供给google工程师看的,我们看到的是在ViewRootImpl中addView时根据IwindowSession.addDisplay()返回值抛给我们的:

    int  res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                                getHostVisibility(), mDisplay.getDisplayId(), mWinFrame,
                                mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                                mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel);
     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?");
                            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);
                    }
    
    
    

    以上的错误信息就是我们经常在as的logcat看到的 ,那么既然windowmanager添加之后了解到与wms通信的服务端,那么wms是如何与View通信的呢?其实ViewRootImpl中有一个匿名内部类W类,该类继承IWindow.stub,我们知道binder通信是客户端与服务端是相对的,此时WMS想发消息给客户端,也只能通过BInder了,那WMS持有的客户端的Binder对象便是W类的远程代理,这个bInder代理对象就是在Session.addToDisplay时的参数通过binder通信传过去的。

    展开全文
  • 最后发现type的值是TYPE_APPLICATION ,所以我得出结论:由于Dialog的type值是TYPE_APPLICATION,该值属于应用Window的范围,所以Dialog是属于应用Window。如果觉得我这种理解有问题的,或者有什么别的不同理解的,...

    前言

    《Android开发艺术探索》第八章介绍WindowManger.LayoutParams的type参数的时候,有这样一句话:子Window不能单独存在,它需要附属在特定的父Window中,比如常见的一些Dialog就是一个子Window。看到这里的时候,我是理解成Dialog属于子Window的。但是,我在昨天看到一篇关于为什么Dialog不能用Application的Context的文章的时候,发现里面说Dialog 的类型是TYPE_APPLICATION,属于应用窗口类型。我就感到奇怪了,那么Dialog到底是属于应用Window还是子Window呢?所以我就从Dialog的源码出发,找寻这个问题的答案,并用这篇文章记录下来。

    Dialog

    首先看看Dialog的构造方法

        Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
          	
          	//省略无关代码
          	
            mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
    
            final Window w = new PhoneWindow(mContext);
            mWindow = w;	//Dialog中的Window其实是一个PhoneWindow
           
           //一些Window的设置
        }
    

    从它的构造方法可以看出,Dialog中的Window其实是一个PhoneWindow
    然后再看看Dialog的show方法

        public void show() {
    		
    		//省略无关代码
           
            WindowManager.LayoutParams l = mWindow.getAttributes();		
            //通过PhoneWindow的getAttributes()获取WindowManager.LayoutParams
            
            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;
            }
    
            mWindowManager.addView(mDecor, l);		//通过WindowManager把自己的Window添加到WMS上
            mShowing = true;
    
            sendShowMessage();
        }
    

    从show方法可以看到Dialog是通过PhoneWindow的getAttributes()来获取WindowManager.LayoutParams,里面就包含了type参数。所以我们转到看PhoneWindow的getAttributes方法。

    Window

    由于PhoneWindow并没有重写getAttributes方法,而PhoneWindow是继承于Window的,所以我们直接看Window的getAttributes方法

        public final WindowManager.LayoutParams getAttributes() {
            return mWindowAttributes;
        }
    

    该方法返回mWindowAttributes,我们再看看mWindowAttributes的值

        // The current window attributes.
        private final WindowManager.LayoutParams mWindowAttributes =
            new WindowManager.LayoutParams();
    

    可以看到,这里调用了WindowManager.LayoutParams的无参构造方法

    WindowManager.LayoutParams

    转到WindowManager.LayoutParams,看看其无参构造方法

            public LayoutParams() {
                super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
                type = TYPE_APPLICATION;
                format = PixelFormat.OPAQUE;
            }
    

    注意 type = TYPE_APPLICATION;这句话。到了这里,终于是水落石出了。我们知道了Dialog的type值是TYPE_APPLICATION。

            /**
             * Window type: a normal application window.  The {@link #token} must be
             * an Activity token identifying who the window belongs to.
             * In multiuser systems shows only on the owning user's window.
             */
            public static final int TYPE_APPLICATION        = 2;
    

    从注释可以看出,该type的值是2,属于应用Window。

    总结

    从Dialog的构造方法出发,为了确定type的类型,追根溯源一直找到WindowManager.LayoutParams的构造方法。最后发现type的值是TYPE_APPLICATION ,所以我得出结论:由于Dialog的type值是TYPE_APPLICATION,该值属于应用Window的范围,所以Dialog是属于应用Window。如果觉得我这种理解有问题的,或者有什么别的不同理解的,欢迎大家提出来。

    参考资料
    https://blog.csdn.net/u010375364/article/details/51866330

    展开全文
  • WindowManager 和Dialog提升Windowtype等级

    千次阅读 2016-03-04 10:37:24
    Dialog dialog = new Dialog(this.getApplicationContext());   TextView textView = new TextView(this);   textView.setText("this is toast!");   dialog.setContentView...

    Dialog dialog = new Dialog(this.getApplicationContext());
      TextView textView = new TextView(this);
      textView.setText("this is toast!");
      dialog.setContentView(textView);
      dialog.getWindow().setType(LayoutParams.TYPE_SYSTEM_ERROR);
     

    dialog.show();


    mFloatingButton = new Button(this);
      mFloatingButton.setText("click me");
      mLayoutParams = new WindowManager.LayoutParams(
      LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, 0, 0,
      PixelFormat.TRANSPARENT);
      mLayoutParams.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL
      | LayoutParams.FLAG_NOT_FOCUSABLE
      | LayoutParams.FLAG_SHOW_WHEN_LOCKED;
      mLayoutParams.type = LayoutParams.TYPE_SYSTEM_ERROR;
      mLayoutParams.gravity = Gravity.LEFT | Gravity.TOP;
      mLayoutParams.x = 100;
      mLayoutParams.y = 300;
      mFloatingButton.setOnTouchListener(this);
      mWindowManager.addView(mFloatingButton, mLayoutParams);

    展开全文
  • 预期实现一个如下的Dialog 使用一下代码更改Dialog的位置 private void updateDialog() { WindowManager.LayoutParams attributes = getWindow().getAttributes(); getWindow().setGravity(Gravity.BOTTOM|...
  • 我们可以通过dialog或者窗口对象 window 然后通过window 去设置dialog的上下中的位置   例如 dialog.getWindow(); 得到window对象  window.setGravity(Gravity.CENTER); 中间位置  window.setGravity(Gravity...
  • Android-Popupwindow和Dialog做弹出窗口

    热门讨论 2014-05-04 15:45:51
    (4)Popupwindow没有标题,Dialog默认有标题,可以通过dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);取消标题 (5)二者显示的时候都要设置Gravity。如果不设置,Dialog默认是Gravity.CENTER。 (6)...
  • QT中 widget window dialog 的区别

    千次阅读 2017-07-04 11:16:51
    A widget is an object that can be displayed on the screen. For example a window or a button...A dialog is a top-level widget, that is always displayed in separate window (i.e. you can’t put it on anothe
  • has leaked window(窗体泄漏):dialog.show();finish();注意这两行代码,当弹窗显示在屏幕中,Activity却被销毁了(就是没有在Activity销毁之前及时调用dialog.dismiss)。该错误不会导致应用崩溃。 not attached ...
  • (札记)EasyUI Window Dialog

    千次阅读 2014-12-29 11:44:04
    $('#win').window({ title:'添加数据', width:300, height:200, collapsible:false,//是否显示可折叠按钮 minimizable:false,//是否显示最小化按钮 maximizable:false,//是否显示最大化按钮 resizable:false,...
  • @null true @drawable/dialog_bg ...@null :DialogwindowFrame框为无true:是否浮现在activity之上false:是否半透明true:是否显示title@drawable/dia_bg:设置dialog的背景false: 背景是否模糊显示 设置
  • Android窗口设计之Dialog、PopupWindow、系统窗口的实现

    千次阅读 热门讨论 2020-12-19 16:07:43
        窗口设计之Dialog、PopupWindow、系统窗口的实现 Android应用程序窗口设计系列博客: Android应用程序窗口设计之Window及WindowManager的创建 Android应用程序窗口设计之setContentView布局加载的实现 普法...
  • 关于Window的windowIsFloating属性,...// 是否是dialog样式的界面,android:windowIsFloating属性 mIsFloating = a.getBoolean(com.android.internal.R.styleable.Window_windowIsFloating, false); int flagToUpd
  • dialog自适应高度

    2021-05-26 17:48:04
    继承Dialog,自定义从底部弹出diglog,因为有列表所以,自适应高度,目前设置0.5倍屏幕高度,如下:@Overridepublic void onWindowFocusChanged(boolean hasFocus) {Window window = this.getWindow();window....
  • wex5 windowDialog组件

    千次阅读 2018-11-29 08:44:22
    windowDialog组件可以实现页面跳转   xid:windowDialog src:$UI/lohas/detailedknowledge.w 父页:myknowledge.w Model.prototype.row1Click = function(event){ var row = event.bindingContext.$object; ...
  • 二、自定义dialog类 package com.wwy.customdialog_demo; import android.app.Dialog; import android.content.Context; import android.content.DialogInterface; import android.util.TypedValue; import a...
  • WPF Prism框架之Dialog

    千次阅读 2021-04-22 22:41:14
    Height="350" Width="500"> <Grid> </Grid> </Window> 2、窗体后台代码中继承IDialogWindow接口 /// /// DialogWindow.xaml 的交互逻辑 /// public partial class DialogWindow : Window, IDialogWindow { public ...
  • 如果创建Dialog时不传入一个Theme,则默认会使用R.style....为什么Dialog和Activity的窗口都是使用Phone Window,都是DecorView,却显得不一样,主要因素是Theme不一样,导致其窗口属性和View的属性都不一样了。 ...
  • import android.app.Activity;...import android.app.Dialog; import android.os.Bundle; import android.view.Gravity; import android.view.Window; import android.view.WindowManager; public class DialogDemoAc
  • 1 背景之所以写这一篇博客的原因是因为之前有写过一篇《Android应用setContentView与LayoutInflater加载解析机制源码分析》,然后有人在文章下面评论和微博私信中问我关于Android应用Activity、Dialog、PopWindow...
  • 1.总结下Dialog相关的异常 1.异常信息android.view.WindowLeaked: Activity com.hy.fly.ui.activity.usercenter.SettingActivity has leaked window com.android.internal.policy.PhoneWindow$DecorView{d33b756 V....
  • 使用Dialog可能引起的内存泄漏

    千次阅读 2020-03-22 20:11:21
    关于View Dialog中的视图View都是由Window去创建的。Window有自己的管理办法去销毁View,因此不会造成泄漏。 3.关于Interface 很大的可能性是由于Interface造成了泄漏,比如DialogInterface.OnClickListener它持有了...
  • 自定dialog 布局没有居中解决方案

    千次阅读 2019-03-08 17:53:05
    自定义的 dialog 忽然跑到左上角了?... Window dialogWindow = getWindow(); dialogWindow.setGravity(Gravity.CENTER); WindowManager.LayoutParams lp = dialogWindow.getAttributes(); lp.widt...
  • Window window = this.getWindow(); window.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); //隐藏标题,否则可能顶部可能存在空白 requestWindowFeature(FEATURE_NO_TITLE); ...
  • Widget翻译过来是小部件的意思,Widget、Dialog和MainWindow三者都可以独立创建用户界面。QWidget类是所有用户界面对象的基类,QMainWindow和QDialog其实就是QWidget的子类,唯一有点血缘关系的就是继承了QWidget类...
  • 在开发完这一期的项目之后,终于有点时间来总结下了~ 这两个月我...看到这个需求,我脑海里立刻就复现了一个问题,“这个需求用自定义toast,自定义popupwindow,自定义dialog不是都能实现吗?这三者有什么区别呢?...
  • 上一篇博客我们介绍了Activity的Window创建过程及最后如何展示出来的,这一篇我们接着分析DialogWindow创建及展示。 Dialog在我们的开发中也算是一个特别常用的组件了, 比如AlertDialog, ProgressDialog、...
  • dialog设置最大高度占比

    千次阅读 2018-11-19 10:18:07
    /** * 在dialog.show()前调用此方法 * * @param mView dialog要显示的view */ private void setDialogSize(final View mView) { mView.addOnLayoutChangeListener(new View.OnLayoutChang...
  • Dialog居中显示

    2019-08-29 10:17:06
    最新使用Dialog时,发现在以下低版本系统中会出现Dialog弹窗在左上角显示的情况,... Window window = dialog.getWindow(); if (dialog != null && window != null) { WindowManager.LayoutParams attr = ...
  • android中Dialog居中显示

    千次阅读 2018-07-17 15:34:42
    //dialog 居中显示 Window window = dialog.getWindow(); if (dialog != null &amp;&amp; window != null) { WindowManager.LayoutParams attr = window.getAttr...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 94,771
精华内容 37,908
关键字:

dialogwindow