[Android开发艺术探索阅读笔记]第8章 Window 的创建过程

Android 中所有的视图都是通过 Window 来呈现的,不管是 Activity、Dialog 还是 Toast,它们的视图实际上都是附加在 Window 上的,因此 Window 实际是 View 的直接管理者。

Window 和 WindowManager

使用示例: 将一个 Button 添加到屏幕坐标为 (100,300) 的位置。

      
    Button button = new Button(this);
    button.setText("button");
    WindowManager.LayoutParams layoutParams =
        new WindowManager.LayoutParams(WindowManager.LayoutParams.WRAP_CONTENT,
            WindowManager.LayoutParams.WRAP_CONTENT);

    layoutParams.gravity = Gravity.START | Gravity.TOP;
    layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
        | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
        | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
    layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION;
    layoutParams.format = PixelFormat.TRANSPARENT;

    layoutParams.x = 100;
    layoutParams.y = 300;

    WindowManager windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
    windowManager.addView(button, layoutParams);
复制代码

WindowManager.LayoutParams 的 Api 文档:https://developer.android.com/reference/android/view/WindowManager.LayoutParams.html

Flag

表示 Window 的属性,控制 Window 的显示特征。

常用的几个 FLAG:

  1. FLAG_NOT_FOCUSABLE:

    不会接收事件的焦点,此标记会同时启用 FLAG_NOT_TOUCH_MODAL,最终事件会直接传递给下层的具有焦点的 Window。

  2. FLAG_NOT_TOUCH_MODAL

    在此模式下,系统会将当前 Window 区域以外的单击事件传递给底层的 Window,当前 Window 区域以内的单击事件则自己处理。这个标记很重要,一般来说都需要开启此标记,否则其他 Window 将无法收到单击事件。

  3. FLAG_SHOW_WHEN_LOCKED

    开启此模式可以让 Window 显示在锁屏的界面。

Type

表示 Window 的类型。

  1. 应用 Window

    对应着一个 Activity

  2. 子 Window

    不能单独存在,需要附属在特定的父 Window 中。例如:Dialog

  3. 系统 Window

    需要声明权限才能创建,例如:Toast 和系统状态栏

z-ordered

Window 是分层的,每个 Window 都有对应的 z-ordered。层级大的在上面。

  • 应用 Window 的层级为 1~99
  • 子 Window 的层级为 100~1999
  • 系统 Window 的层级为 2000~2999

WindowManager

继承自 ViewManager

public interface WindowManager extends ViewManager
复制代码

android.view.ViewManager

public interface ViewManager
{
    /**
     * Assign the passed LayoutParams to the passed View and add the view to the window.
     * <p>Throws {@link android.view.WindowManager.BadTokenException} for certain programming
     * errors, such as adding a second view to a window without removing the first view.
     * <p>Throws {@link android.view.WindowManager.InvalidDisplayException} if the window is on a
     * secondary {@link Display} and the specified display can't be found
     * (see {@link android.app.Presentation}).
     * @param view The view to be added to this window.
     * @param params The LayoutParams to assign to view.
     */
    public void addView(View view, ViewGroup.LayoutParams params);
    public void updateViewLayout(View view, ViewGroup.LayoutParams params);
    public void removeView(View view);
}

复制代码

Window 的内部机制

Window 是个抽象类,每一个 Window 都对应一个 View 和一个 ViewRootImpl,window 并不是实际存在的,是以 View 的形式存在。

通过 WindowManager 来访问 Window。

WindowManagerImp 内部通过 WindowManagerGlobal 来添加,更新,移除 View。这是典型的桥接模式。

WindowManagerGlobal 的列表集合

  /** 存储 View */
  private final ArrayList<View> mViews = new ArrayList<View>();

  /** 存储 ViewRootImpl */
  private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();

  /** 存储 LayoutParams */
  private final ArrayList<WindowManager.LayoutParams> mParams =
      new ArrayList<WindowManager.LayoutParams>();

  /** 存储正在被删除的 View */
  private final ArraySet<View> mDyingViews = new ArraySet<View>();
  
复制代码

添加

WindowManagerGlobal#addView

 public void addView(View view, ViewGroup.LayoutParams params,
      Display display, Window parentWindow) {

    /* 错误检查 */
    if (view == null) {
      throw new IllegalArgumentException("view must not be null");
    }
    if (display == null) {
      throw new IllegalArgumentException("display must not be null");
    }
    if (!(params instanceof WindowManager.LayoutParams)) {
      throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
    }

    /* 检查是否是子 window,是的话就调整下参数 */
    final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
    if (parentWindow != null) {
      parentWindow.adjustLayoutParamsForSubWindow(wparams);
    } else {
      // If there's no parent, then hardware acceleration for this view is
      // set from the application's hardware acceleration setting.

      // 如果没有父类window,按应用程序的硬件加速设置这个 View 的硬件加速
      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;

    /* 观察系统属性的变化 */
    synchronized (mLock) {
      // Start watching for system property changes.
      if (mSystemPropertyUpdater == null) {
        mSystemPropertyUpdater = new Runnable() {
          @Override public void run() {
            synchronized (mLock) {
              for (int i = mRoots.size() - 1; i >= 0; --i) {
                mRoots.get(i).loadSystemProperties();
              }
            }
          }
        };
        SystemProperties.addChangeCallback(mSystemPropertyUpdater);
      }

      int index = findViewLocked(view, false);

      /* 检查是否有正在的删除的 View */
      if (index >= 0) {
        if (mDyingViews.contains(view)) {
          // Don't wait for MSG_DIE to make it's way through root's queue.
          mRoots.get(index).doDie();
        } else {
          throw new IllegalStateException("View " + view
              + " has already been added to the window manager.");
        }
        // The previous removeView() had not completed executing. Now it has.
      }

      // If this is a panel window, then find the window it is being
      // attached to for future reference.
      if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
          wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
        final int count = mViews.size();
        for (int i = 0; i < count; i++) {
          if (mRoots.get(i).mWindow.asBinder() == wparams.token) {
            panelParentView = mViews.get(i);
          }
        }
      }

      // 将对象存储到列表中
      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 更新界面并完成 Window 的添加过程。
         内部通过 requestLayout 绘制界面
         */

        root.setView(view, wparams, panelParentView);
      } catch (RuntimeException e) {
        // BadTokenException or InvalidDisplayException, clean up.
        if (index >= 0) {
          removeViewLocked(index, true);
        }
        throw e;
      }
    }
  }
复制代码

ViewRootImpl#setView 最终通过 IWindowSession 这个 Binder 对象来完成添加过程。实现类是 Session。这个过程是一个 IPC 调用。

android.view.ViewRootImpl.java # setView 方法片段

                try {
                    mOrigWindowType = mWindowAttributes.type;
                    mAttachInfo.mRecomputeGlobalAttributes = true;
                    collectViewAttributes();
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(),
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mInputChannel);
                } catch (RemoteException e) {
                    mAdded = false;
                    mView = null;
                    mAttachInfo.mRootView = null;
                    mInputChannel = null;
                    mFallbackEventHandler.setView(null);
                    unscheduleTraversals();
                    setAccessibilityFocus(null, null);
                    throw new RuntimeException("Adding window failed", e);
                } finally {
                    if (restore) {
                        attrs.restore();
                    }
                }
复制代码

在 mWindowSession 内部会通过 WindowManagerService 来实现 Window 的添加。

IWindowSession.Stub#addToDisplay

public int addToDisplay(IWindow window,
                        int seq,
                        WindowManager.LayoutParams attrs,
                        int viewVisibility,
                        int displayId, 
                        Rect outContentInsets, 
                        InputChannel outInputChannel) {
                        
    return mService.addWindow(this,window,seq,attrs,viewVisibility,displayId, outContentInsets,outInputChannel);
}
复制代码

这样,Window 的添加请求最终交给了 WindowManagerService 去处理。

Window 的删除过程

和添加一样,先通过 WindowManagerImpl, 再通过 WindowManagerGlobal 来实现的。

android.view.WindowManagerGlobal#removeView

    public void removeView(View view, boolean immediate) {
        if (view == null) {
            throw new IllegalArgumentException("view must not be null");
        }

        synchronized (mLock) {
        // 查找待删除的 View 的索引。
            int index = findViewLocked(view, true);
            View curView = mRoots.get(index).getView();
            // 删除
            removeViewLocked(index, immediate);
            if (curView == view) {
                return;
            }

            throw new IllegalStateException("Calling with view " + view
                    + " but the ViewAncestor is attached to " + curView);
        }
    }
复制代码

android.view.WindowManagerGlobal#removeViewLocked

    private void removeViewLocked(int index, boolean immediate) {
    // 通过 ViewRootImpl 来完成删除操作
        ViewRootImpl root = mRoots.get(index);
        View view = root.getView();

        if (view != null) {
            InputMethodManager imm = InputMethodManager.getInstance();
            if (imm != null) {
                imm.windowDismissed(mViews.get(index).getWindowToken());
            }
        }
        boolean deferred =
        // 执行 die,完成具体的删除操作
        root.die(immediate);
        if (view != null) {
            view.assignParent(null);
            if (deferred) {
                mDyingViews.add(view);
            }
        }
    }
复制代码

android.view.ViewRootImpl#die

// immediate 为true代表同步删除,false 代表异步执行。一般不需要同步以免发生错误。
    boolean die(boolean immediate) {
        // Make sure we do execute immediately if we are in the middle of a traversal or the damage
        // done by dispatchDetachedFromWindow will cause havoc on return.
        if (immediate && !mIsInTraversal) {
            doDie();
            return false;
        }

        if (!mIsDrawing) {
            destroyHardwareRenderer();
        } else {
            Log.e(mTag, "Attempting to destroy the window while drawing!\n" +
                    "  window=" + this + ", title=" + mWindowAttributes.getTitle());
        }
        // 异步删除时只是发送了一个 DIE 消息,Handler 执行 doDie()
        mHandler.sendEmptyMessage(MSG_DIE);
        return true;
    }
复制代码

die 方法中如果是异步删除,就发送一个 MSG, ViewRootImpl 中的 Handler 会执行 doDie()。如果是同步,直接执行 diDie()。

android.view.ViewRootImpl#doDie

    void doDie() {
        checkThread();
        if (LOCAL_LOGV) Log.v(TAG, "DIE in " + this + " of " + mSurface);
        synchronized (this) {
            if (mRemoved) {
                return;
            }
            mRemoved = true;
            if (mAdded) {
                // 真正删除 View 的操作在这个方法中
               dispatchDetachedFromWindow();
           }

            if (mAdded && !mFirst) {
                destroyHardwareRenderer();

                if (mView != null) {
                    int viewVisibility = mView.getVisibility();
                    boolean viewVisibilityChanged = mViewVisibility != viewVisibility;
                    if (mWindowAttributesChanged || viewVisibilityChanged) {
                       // If layout params have been changed, first give them
                       // to the window manager to make sure it has the correct
                        // animation info.
                        try {
                           if ((relayoutWindow(mWindowAttributes, viewVisibility, false)
                                   & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
                                mWindowSession.finishDrawing(mWindow);
                            }
                        } catch (RemoteException e) {
                        }
                    }

                    mSurface.release();
                }
            }
            mAdded = false;
        }
        
        // 刷新数据,包括 mRoots、mParams、以及  mDyingViews. 需要将当前 Window 所关联的这三类对象从列表中删除。
        WindowManagerGlobal.getInstance().doRemoveView(this);
    }

复制代码

主要的删除操作由这个方法完成: android.view.ViewRootImpl#dispatchDetachedFromWindow

    void dispatchDetachedFromWindow() {
    
        // 垃圾回收相关的工作。移除回调,清除数据和消息
        if (mView != null && mView.mAttachInfo != null) {
            mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(false);
            // 当 View 从 Window 中移除时,这个方法就会被调用。可以在这个
            // 方法中做一些资源回收的工作,比如终止动画、停止线程。
            mView.dispatchDetachedFromWindow();
        }

        mAccessibilityInteractionConnectionManager.ensureNoConnection();
        mAccessibilityManager.removeAccessibilityStateChangeListener(
                mAccessibilityInteractionConnectionManager);
        mAccessibilityManager.removeHighTextContrastStateChangeListener(
                mHighContrastTextManager);
        removeSendWindowContentChangedCallback();

        destroyHardwareRenderer();

        setAccessibilityFocus(null, null);

        mView.assignParent(null);
        mView = null;
        mAttachInfo.mRootView = null;

        mSurface.release();

        if (mInputQueueCallback != null && mInputQueue != null) {
            mInputQueueCallback.onInputQueueDestroyed(mInputQueue);
            mInputQueue.dispose();
            mInputQueueCallback = null;
            mInputQueue = null;
        }
        if (mInputEventReceiver != null) {
            mInputEventReceiver.dispose();
            mInputEventReceiver = null;
        }
        
        // 通过 Session 的 remove 方法删除。这同样是是一个 IPC 过程,最终调用 WindowManagerService 的 RemoveWindow
        try {
            mWindowSession.remove(mWindow);
        } catch (RemoteException e) {
        }

        // Dispose the input channel after removing the window so the Window Manager
        // doesn't interpret the input channel being closed as an abnormal termination.
        if (mInputChannel != null) {
            mInputChannel.dispose();
            mInputChannel = null;
        }
        
        mDisplayManager.unregisterDisplayListener(mDisplayListener);
        unscheduleTraversals();
    }
复制代码

Window 的更新过程

android.view.WindowManagerGlobal#updateViewLayout


 public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
        // 检查异常
        if (view == null) {
            throw new IllegalArgumentException("view must not be null");
        }
        if (!(params instanceof WindowManager.LayoutParams)) {
            throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
        }

        // 给View设置新的params
        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;

        view.setLayoutParams(wparams);
        
        // 将老的 params 删除,添加新的 params。
        synchronized (mLock) {
            int index = findViewLocked(view, true);
            ViewRootImpl root = mRoots.get(index);
            mParams.remove(index);
            mParams.add(index, wparams);
            // 更新 ViewRootImpl 中的 LayoutParams
            root.setLayoutParams(wparams, false);
        }
    }
复制代码

在 ViewRootImpl 中会通过 scheduleTraversals 方法对 View 重新布局。

还会通过 WindowSession 来更新 Window 的视图,这个过程最终是由 WindowMangerService 的 relayoutWindow 来具体实现的,同样是个 IPC 过程。

Window 的创建过程

View 不能单独存在,必须依附在 Window 这个抽象的概念上。

有 View 的地方就有 Window。

Activity、Dialog、Toast。

Activity 的创建过程

Activity 的启动过程很复杂,最终会由 ActivityThread 中的 performLaunchActivity() 来完成整个启动过程。

android.app.ActivityThread#performLaunchActivity()

  try {
            java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
            StrictMode.incrementExpectedActivityCount(activity.getClass());
            r.intent.setExtrasClassLoader(cl);
            r.intent.prepareToEnterProcess();
            if (r.state != null) {
                r.state.setClassLoader(cl);
            }
        } catch (Exception e) {
            if (!mInstrumentation.onException(activity, e)) {
                throw new RuntimeException(
                    "Unable to instantiate activity " + component
                    + ": " + e.toString(), e);
            }
        }


 if (activity != null) {
                Context appContext = createBaseContextForActivity(r, activity);
                CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
                Configuration config = new Configuration(mCompatConfiguration);
                if (r.overrideConfig != null) {
                    config.updateFrom(r.overrideConfig);
                }
                if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity "
                        + r.activityInfo.name + " with config " + config);
                Window window = null;
                if (r.mPendingRemoveWindow != null && r.mPreserveWindow) {
                    window = r.mPendingRemoveWindow;
                    r.mPendingRemoveWindow = null;
                    r.mPendingRemoveWindowManager = null;
                }
               
               //  调用 attach() 为其关联运行过程中所需要的上下文环境变量
              activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config,
                        r.referrer, r.voiceInteractor, window);

}
复制代码

在 Acrivity 的 attach 方法中,系统会创建 Activity 所属的 Window 对象并为其设置回调接口。Window 对象的创建是通过 PolicyManager 的 makeNewWindow 方法实现的。(新版本直接创建了 PhoneWindow)

由于 Activity 实现了 Window 的 callback 接口,因此当 Window 接收到外界状态的改变时就会回调 Activity 的方法。


        mWindow = new PhoneWindow(this, window);
        mWindow.setWindowControllerCallback(this);
        mWindow.setCallback(this);
        mWindow.setOnWindowDismissedCallback(this);
        mWindow.getLayoutInflater().setPrivateFactory(this);
        if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
            mWindow.setSoftInputMode(info.softInputMode);
        }
        if (info.uiOptions != 0) {
            mWindow.setUiOptions(info.uiOptions);
        }
复制代码

Activity 的视图是怎么加载在 Widnow 上的?

直接看 Activity 的 setContentView 的实现。

android.app.Activity#setContentView

   public void setContentView(@LayoutRes int layoutResID) {
   // 交给了 window 去实现
    getWindow().setContentView(layoutResID);
    initWindowDecorActionBar();
}
复制代码

Window 的具体实现是 PhoneWindow 类。

com.android.internal.policy.PhoneWidnow#setContentView

    @Override
    public void setContentView(int layoutResID) {
        // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
        // decor, when theme attributes and the like are crystalized. Do not check the feature
        // before this happens.
        if (mContentParent == null) {
        
        //  安装 Decor
            installDecor();
        } 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;
    }
复制代码
  1. 如果没有 DecorView 就创建它。

DecorView 是一个 FrameLayout,是 Activity 中的顶级 View,一般来说内部包含标题栏和内部栏,这个会随着主题而变化。内容栏的 id 是 android.R.id.content

com.android.internal.policy.PhoneWindow#installDecor

 private void installDecor() {
        mForceDecorInstall = false;
        if (mDecor == null) {
            // 直接创建,此时还是一个空白的 FrameLayout
            mDecor = generateDecor(-1);
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            }
        } else {
            mDecor.setWindow(this);
        }
        
        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);

            // Set up decor part of UI to ignore fitsSystemWindows if appropriate.
            mDecor.makeOptionalFitsSystemWindows();

            final DecorContentParent decorContentParent = (DecorContentParent) mDecor.findViewById(
                    R.id.decor_content_parent);

            if (decorContentParent != null) {
                mDecorContentParent = decorContentParent;
                // 设置一堆东西
                mDecorContentParent.setUiOptions(mUiOptions);

                // ......

            } else {
                mTitleView = (TextView) findViewById(R.id.title);
                if (mTitleView != null) {
                   // 设置title,省略部分代码
                }
            }

            if (mDecor.getBackground() == null && mBackgroundFallbackResource != 0) {
                mDecor.setBackgroundFallback(mBackgroundFallbackResource);
            }

            // Only inflate or create a new TransitionManager if the caller hasn't
            // already set a custom one.
            // 省略部分代码
        }
    }
复制代码

generateLayout() 初始化 mContentParent。

com.android.internal.policy.PhoneWindow#generateLayout

protected ViewGroup generateLayout(DecorView decor) {
        // Apply data from current theme.

        TypedArray a = getWindowStyle();
                
        // 省略部分代码...
        

            
        // 从这里开始。Inflate the window decor.

        int layoutResource;
        int features = getLocalFeatures();
        
        // 省略一堆判断...
        
        
        mDecor.startChanging();
        // 加载布局
        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
        
        
        // 这个 id 所对应的 ViewGroup 就是 mContentParent。
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        if (contentParent == null) {
            throw new RuntimeException("Window couldn't find content container view");
        }

       

        // 省略设置背景标题等...

        mDecor.finishChanging();

        return contentParent;
    }
复制代码

上述方法中的 ID_ANDROID_CONTENT 的定义如下。

android.view.Window#ID_ANDROID_CONTENT

 /**
    * The ID that the main layout in the XML layout file should have.
     */
    public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
复制代码
  1. 将 View 添加到 DecoreView 的 mContentParent 中。
mLayoutInflater.inflate(layoutResID, mContentParent);
复制代码

将 Activity 的视图直接添加到 DecorView 的 mContentParent 中。因此设置布局的方法叫 setContentView

  1. 回调 Activity 的 onContentChanged 方法通知 Activity 视图改变
 if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
}
复制代码

Activity 实现了 Window 的 Callback 接口。Activity 的布局文件被添加到 DecorView 的 mContentParent 中,于是需要通知 Activity,使其可以做相应的处理。

Activity 中的 onContentChanged 是空实现。

 public void onContentChanged() {
}

复制代码
显示

经过上述步骤,DecorView 已经被创建并初始化完毕。Activity 的布局文件已经被添加到 DecorView 的 mContentParent 中,但是这时 DecorView 还没有被 WindowManager 正式添加到 Widnow 中。

Window 更多表示一种抽象的功能集合。

虽然在 Activity 的 attach 方法中 Window 已经被创建,此时由于 DecorView 没有被 WindowManager 识别,所以这时的 Window 无法提供具体功能,他无法接收外界的输入信息。

在 ActivityThread 的 handleResumeActivity() 中,首先调用 Activity 的 onResume(),接着调用 Activity 的 makeVisible()。在这个方法中,DecorView 真正完成了添加和显示这两个过程,此时 Activity 的视图才能被用户真正看到。

android.app.ActivityThread#handleResumeActivity

 final void handleResumeActivity(IBinder token,
            boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
        ActivityClientRecord r = mActivities.get(token);
        if (!checkAndUpdateLifecycleSeq(seq, r, "resumeActivity")) {
            return;
        }

        // If we are getting ready to gc after going to the background, well
        // we are back active so skip it.
        unscheduleGcIdler();
        mSomeActivitiesChanged = true;

        // TODO Push resumeArgs into the activity for consideration
        r = performResumeActivity(token, clearHide, reason);

        if (r != null) {
            final Activity a = r.activity;

            if (localLOGV) Slog.v(
                TAG, "Resume " + r + " started activity: " +
                a.mStartedActivity + ", hideForNow: " + r.hideForNow
                + ", finished: " + a.mFinished);

            final int forwardBit = isForward ?
                    WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION : 0;

            // If the window hasn't yet been added to the window manager,
            // and this guy didn't finish itself or start another activity,
            // then go ahead and add the window.
            boolean willBeVisible = !a.mStartedActivity;
            if (!willBeVisible) {
                try {
                    willBeVisible = ActivityManagerNative.getDefault().willActivityBeVisible(
                            a.getActivityToken());
                } catch (RemoteException e) {
                    throw e.rethrowFromSystemServer();
                }
            }
            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;
                    wm.addView(decor, l);
                }

            // If the window has already been added, but during resume
            // we started another activity, then don't yet make the
            // window visible.
            } else if (!willBeVisible) {
                if (localLOGV) Slog.v(
                    TAG, "Launch " + r + " mStartedActivity set");
                r.hideForNow = true;
            }

            // Get rid of anything left hanging around.
            cleanUpPendingRemoveWindows(r, false /* force */);

            // The window is now visible if it has been added, we are not
            // simply finishing, and we are not starting another activity.
            if (!r.activity.mFinished && willBeVisible
                    && r.activity.mDecor != null && !r.hideForNow) {
                if (r.newConfig != null) {
                    performConfigurationChangedForActivity(r, r.newConfig, REPORT_TO_ACTIVITY);
                    if (DEBUG_CONFIGURATION) Slog.v(TAG, "Resuming activity "
                            + r.activityInfo.name + " with newConfig " + r.activity.mCurrentConfig);
                    r.newConfig = null;
                }
                if (localLOGV) Slog.v(TAG, "Resuming " + r + " with isForward="
                        + isForward);
                WindowManager.LayoutParams l = r.window.getAttributes();
                if ((l.softInputMode
                        & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION)
                        != forwardBit) {
                    l.softInputMode = (l.softInputMode
                            & (~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION))
                            | forwardBit;
                    if (r.activity.mVisibleFromClient) {
                        ViewManager wm = a.getWindowManager();
                        View decor = r.window.getDecorView();
                        wm.updateViewLayout(decor, l);
                    }
                }
                r.activity.mVisibleFromServer = true;
                mNumVisibleActivities++;
                if (r.activity.mVisibleFromClient) {
                    r.activity.makeVisible();
                }
            }

            if (!r.onlyLocalRequest) {
                r.nextIdle = mNewActivities;
                mNewActivities = r;
                if (localLOGV) Slog.v(
                    TAG, "Scheduling idle handler for " + r);
                Looper.myQueue().addIdleHandler(new Idler());
            }
            r.onlyLocalRequest = false;

            // Tell the activity manager we have resumed.
            if (reallyResume) {
                try {
                    ActivityManagerNative.getDefault().activityResumed(token);
                } catch (RemoteException ex) {
                    throw ex.rethrowFromSystemServer();
                }
            }

        } else {
            // If an exception was thrown when trying to resume, then
            // just end this activity.
            try {
                ActivityManagerNative.getDefault()
                    .finishActivity(token, Activity.RESULT_CANCELED, null,
                            Activity.DONT_FINISH_TASK_WITH_ACTIVITY);
            } catch (RemoteException ex) {
                throw ex.rethrowFromSystemServer();
            }
        }
    }


复制代码

Activity#makeVisible

void makeVisible() {
    if (!mWindowAdded) {
        ViewManager wm = getWindowManager();
        wm.addView(mDecor, getWindow().getAttributes());
        mWindowAdded = true;
    }
    mDecor.setVisibility(View.VISIBLE);
}
复制代码

Dialog 的 Window 创建过程

#1. 创建 Winow

同样是直接 new PhoneWindow 创建 Window,和 Activity 一致。

 Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
    
        // ...

        mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);

        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);
    }
复制代码

#2. 初始化 DecorView 并将 Dialog 的视图添加到 DecorView 中

和 Activity 一样。

 public void setContentView(@LayoutRes int layoutResID) {
        mWindow.setContentView(layoutResID);
}
复制代码

#3. 将 DecorView 添加到 Window 中并显示

    public void show() {
       
        // ...
        mWindowManager.addView(mDecor, l);
        mShowing = true;

        sendShowMessage();
    }

复制代码

删除的时候通过 mWindowManager.removeViewImmediate(mDecor); 移除 DecorView。

  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 必须采用 Activity 的 Context,如若使用 Apllication 的 Context,就会报错没有 token。

token 一般只有 Activity 才有。

系统 Window 不需要 token 就可以显示。

Caused by: android.view.WindowManager$BadToken-
Exception: Unable to add window --token null is not for an application
复制代码

Toast 的 Window 创建过程

Toast 也是基于 Window 来实现的。

内部有 2 类 IPC 过程:

  1. Toast 访问 NotificationManagerService
  2. NotificationManagerService 回调 Toast 里的 TN 接口

Toast 属于系统 Window。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值