公众号:字节数组,但愿对你有所帮助 😇😇java
Android 系统中,Window 在代码层次上是一个抽象类,在概念上表示的是一个窗口。Android 中全部的视图都是经过 Window 来呈现的,例如 Activity、Dialog 和 Toast 等,它们实际上都是挂载在 Window 上的。大部分状况下应用层开发者不多须要来和 Window 打交道,Activity 已经隐藏了 Window 的具体实现逻辑了,但我以为来了解 Window 机制的一个比较大的好处是能够加深咱们对 View 绘制流程以及事件分发机制的了解,这两个操做就涉及到咱们的平常开发了,实现自定义 View 和解决 View 的滑动冲突时都须要咱们掌握这方面的知识点,而这两个操做和 Window 机制有很大的关联。视图树只有被挂载到 Window 后才会触发视图树的绘制流程,以后视图树才有机会接收到用户的触摸事件。也就是说,视图树被挂载到了 Window 上是 Activity 和 Dialog 可以展现到屏幕上且和用户作交互的前置条件git
本文就以 Activity 为例子,展开讲解 Activity 是如何挂载到 Window 上的,基于 Android API 30 进行分析,但愿对你有所帮助 😇😇github
Window 存在的意义是什么呢?数组
大部分状况下,用户都是在和应用的 Activity 作交互,应用在 Activity 上接收用户的输入并在 Activity 上向用户作出交互反馈。例如,在 Activity 中显示了一个 Button,当用户点击后就会触发 OnClickListener,这个过程当中用户就是在和 Activity 中的视图树作交互,此时尚未什么问题。但是,当须要在 Activity 上弹出 Dialog 时,系统须要确保 Dialog 是会覆盖在 Activity 之上的,有触摸事件时也须要确保 Dialog 是先于 Activity 接收到的;当启动一个新的 Activity 时又须要覆盖住上一个 Activity 以及其显示的 Dialog;在弹出 Toast 时,又须要确保 Toast 是覆盖在 Activity 和 Dialog 之上的markdown
这种种要求就涉及到了一个层次管理问题,系统须要对当前屏幕上显示的多个视图树进行统一管理,这样才能来决定不一样视图树的显示层次以及在接收触摸事件时的优先级。系统就经过 Window 这个概念来实现上述目的app
想要在屏幕上显示一个 Window 并不算多复杂,代码大体以下所示ide
private val windowManager by lazy {
context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
}
private val floatBallView by lazy {
FloatBallView(context)
}
private val floatBallWindowParams: WindowManager.LayoutParams by lazy {
WindowManager.LayoutParams().apply {
width = FloatBallView.VIEW_WIDTH
height = FloatBallView.VIEW_HEIGHT
gravity = Gravity.START or Gravity.CENTER_VERTICAL
flags =
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
type = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
WindowManager.LayoutParams.TYPE_SYSTEM_ALERT
} else {
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
}
}
}
fun showFloatBall() {
windowManager.addView(floatBallView, floatBallWindowParams)
}
复制代码
显示一个 Window 最基本的操做流程有:函数
当中,WindowManager.LayoutParams 的 flags 属性就用于控制 Window 的显示特性和交互逻辑,常见的有如下几个:oop
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE。表示当前 Window 不须要获取焦点,也不须要接收各类按键输入事件,按键事件会直接传递给下层具备焦点的 Window布局
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL。表示当前 Window 区域的单击事件但愿本身处理,其它区域的事件则传递给其它 Window
WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED。表示当前 Window 但愿显示在锁屏界面
此外,WindowManager.LayoutParams 的 type 属性就用于表示 Window 的类型。Window 有三种类型:应用 Window、子 Window、系统 Window。应用类Window 对应 Activity。子 Window 具备依赖关系,不能单独存在,须要附属在特定的父 Window 之中,好比 Dialog 就是一个子 Window。系统 Window 是须要权限才能建立的 Window,好比 Toast 和 statusBar 都是系统 Window
从这也能够看出,系统 Window 是处于最顶层的,因此说 type 属性也用于控制 Window 的显示层级,显示层级高的 Window 就会覆盖在显示层级低的 Window 之上。应用 Window 的层级范围是 1~99,子 Window 的层级范围是 1000~1999,系统 Window 的层级范围是 2000~2999。若是想要让咱们建立的 Window 位于其它 Window 之上,那么就须要使用比较大的层级值了,但想要显示自定义的系统级 Window 的话就必须向系统申请权限了
WindowManager.LayoutParams 内就声明了这些层级值,咱们能够择需选取。例如,系统状态栏自己也是一个 Window,其 type 值就是 TYPE_STATUS_BAR
public static class LayoutParams extends ViewGroup.LayoutParams implements Parcelable {
public int type;
//应用 Window 的开始值
public static final int FIRST_APPLICATION_WINDOW = 1;
//应用 Window 的结束值
public static final int LAST_APPLICATION_WINDOW = 99;
//子 Window 的开始值
public static final int FIRST_SUB_WINDOW = 1000;
//子 Window 的结束值
public static final int LAST_SUB_WINDOW = 1999;
//系统 Window 的开始值
public static final int FIRST_SYSTEM_WINDOW = 2000;
//系统状态栏
public static final int TYPE_STATUS_BAR = FIRST_SYSTEM_WINDOW;
//系统 Window 的结束值
public static final int LAST_SYSTEM_WINDOW = 2999;
}
复制代码
每一个 Window 都会关联一个 View,想要显示 Window 也离不开 WindowManager,WindowManager 就提供了对 View 进行操做的能力。WindowManager 自己是一个接口,其又继承了另外一个接口 ViewManager,WindowManager 最基本的三种操做行为就由 ViewManager 来定义,即添加 View、更新 View、移除 View
public interface ViewManager {
public void addView(View view, ViewGroup.LayoutParams params);
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
public void removeView(View view);
}
复制代码
WindowManager 的实现类是 WindowManagerImpl,其三种基本的操做行为都交由了 WindowManagerGlobal 去实现,这里使用到了桥接模式
public final class WindowManagerImpl implements WindowManager {
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
mContext.getUserId());
}
@Override
public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.updateViewLayout(view, params);
}
@Override
public void removeView(View view) {
mGlobal.removeView(view, false);
}
}
复制代码
这里主要看下 WindowManagerGlobal 是如何实现 addView
方法的便可
首先,WindowManagerGlobal 会对入参参数进行校验,并对 LayoutParams 作下参数调整。例如,若是当前要显示的是子 Window 的话,那么就须要使其 LayoutParams 遵循父 Window 的要求才行
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow, int userId) {
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");
}
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.
final Context context = view.getContext();
if (context != null
&& (context.getApplicationInfo().flags
& ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
}
}
···
}
复制代码
以后就会为当前的视图树(即 view)构建一个关联的 ViewRootImpl 对象,经过 ViewRootImpl 来绘制视图树并完成 Window 的添加过程。ViewRootImpl 的 setView
方法会触发启动整个视图树的绘制流程,即完成视图树的 Measure、Layout、Draw 流程,具体流程能够看个人另外一篇文章:一文读懂 View 的 Measure、Layout、Draw 流程
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow, int userId) {
···
ViewRootImpl root;
View panelParentView = null;
···
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 {
//启动和 view 关联的整个视图树的绘制流程
root.setView(view, wparams, panelParentView, userId);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
复制代码
ViewRootImpl 内部最终会经过 WindowSession 来完成 Window 的添加过程,mWindowSession
是一个 Binder 对象,真正的实现类是 Session,也就是说,Window 的添加过程涉及到了 IPC 调用。后面就比较复杂了,能力有限就不继续看下去了
mOrigWindowType = mWindowAttributes.type;
mAttachInfo.mRecomputeGlobalAttributes = true;
collectViewAttributes();
adjustLayoutParamsForCompatibility(mWindowAttributes);
res = mWindowSession.addToDisplayAsUser(
mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), userId, mTmpFrame,
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mDisplayCutout, inputChannel,
mTempInsets, mTempControls
);
setFrame(mTmpFrame);
复制代码
须要注意的是,这里所讲的视图树表明的是不少种不一样的视图形式。在启动一个 Activity 或者显示一个 Dialog 的时候,咱们都须要为它们指定一个布局文件,布局文件会经过 LayoutInflater 加载映射为一个具体的 View 对象,即最终 Activity 和 Dialog 都会被映射为一个 View 类型的视图树,它们都会经过 WindowManager 的 addView
方法来显示到屏幕上,WindowManager 对于 Activity 和 Dialog 来讲具备统一的操做行为入口
这里就以 Activity 为例子来展开讲解 Window 相关的知识点,因此也须要先对 Activity 的组成结构作个大体的介绍。Activity 和 Window 之间的关系能够用如下图片来表示
每一个 Activity 均包含一个 Window 对象,即 Activity 和 Window 是一对一的关系
Window 是一个抽象类,其惟一的实现类是 PhoneWindow
PhoneWindow 内部包含一个 DecorView,DecorView 是 FrameLayout 的子类,其内部包含一个 LinearLayout,LinearLayout 中又包含两个自上而下的 childView,即 ActionBar 和 ContentParent。咱们平时在 Activity 中调用的 setContentView
方法实际上就是在向 ContentParent 执行 addView
操做
Window 这个抽象类里定义了多个和 UI 操做相关的方法,咱们平时在 Activity 中调用的setContentView
和findViewById
方法都会被转交由 Window 来实现,Window 是 Activity 和视图树系统交互的入口。例如,其 getDecorView()
方法就用于获取内嵌的 DecorView,findViewById()
方法就会将具体逻辑转交由 DecorView 来实现,由于 DecorView 才是真正包含 contentView
的容器类
public abstract class Window {
public Window(Context context) {
mContext = context;
mFeatures = mLocalFeatures = getDefaultFeatures(context);
}
public abstract void setContentView(@LayoutRes int layoutResID);
@Nullable
public <T extends View> T findViewById(@IdRes int id) {
return getDecorView().findViewById(id);
}
public abstract void setTitle(CharSequence title);
public abstract @NonNull View getDecorView();
···
}
复制代码
每一个 Activity 内部都包含一个 Window 对象 mWindow
,在 attach
方法中完成初始化,这说明 Activity 和 Window 是一对一的关系。mWindow
对象对应的是 PhoneWindow 类,这也是 Window 的惟一实现类
public class Activity extends ContextThemeWrapper implements LayoutInflater.Factory2, Window.Callback, KeyEvent.Callback, OnCreateContextMenuListener, ComponentCallbacks2, Window.OnWindowDismissedCallback, AutofillManager.AutofillClient, ContentCaptureManager.ContentCaptureClient {
@UnsupportedAppUsage
private Window mWindow;
@UnsupportedAppUsage
private WindowManager mWindowManager;
@UnsupportedAppUsage
final void attach(Context context, ActivityThread aThread, Instrumentation instr, IBinder token, int ident, Application application, Intent intent, ActivityInfo info, CharSequence title, Activity parent, String id, NonConfigurationInstances lastNonConfigurationInstances, Configuration config, String referrer, IVoiceInteractor voiceInteractor, Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken) {
attachBaseContext(context);
mFragments.attachHost(null /*parent*/);
//初始化 mWindow
mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow.setWindowControllerCallback(mWindowControllerCallback);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
···
}
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
}
复制代码
Activity 的attach
方法又是在 ActivityThread 的 performLaunchActivity
方法中被调用的,在经过反射生成 Activity 实例后就会调用attach
方法,且能够看到该方法的调用时机是早于 Activity 的 onCreate
方法的。因此说,在生成 Activity 实例后不久其 Window 对象就已经被初始化了,并且早于各个生命周期回调函数
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
···
Activity activity = null;
try {
java.lang.ClassLoader cl = appContext.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);
}
}
···
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, r.configCallback,
r.assistToken);
···
if (r.isPersistable()) {
mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
mInstrumentation.callActivityOnCreate(activity, r.state);
}
return activity;
}
复制代码
此外,从 Activity 的setContentView
的方法签名来看,具体逻辑都交由了 Window 的同名方法来实现,传入的 layoutResID
就是咱们但愿在屏幕上呈现的布局,那么 PhoneWindow 天然就须要去加载该布局文件生成对应的 View。而为了可以有一个对 View 进行统一管理的入口,View 应该要包含在一个指定的 ViewGroup 中才行,该 ViewGroup 指的就是 DecorView
PhoneWindow 的 setContentView
方法的逻辑能够总结为:
mDecor
。DecorView 是 FrameLayout 的子类,其内部包含两个咱们常常会接触到的 childView:actionBar 和 contentParent,actionBar 即 Activity 的标题栏,contentParent 即 Activity 的视图内容容器mContentParent
为 null 的话则调用 installDecor()
方法来初始化 DecorView,从而同时初始化 mContentParent
;不为 null 的话则移除 mContentParent
的全部 childView
,为 layoutResID
腾出位置(不考虑转场动画,实际上最终的操做都同样)LayoutInflater.inflate
生成 layoutResID
对应的 View,并将其添加到 mContentParent
中,从而将咱们的目标视图挂载到一个统一的容器中(不考虑转场动画,实际上最终的操做都同样)Callback.onContentChanged
方法,咱们能够经过重写 Activity 的该方法从而获得布局内容改变的通知因此说,Activity 的 setContentView
方法实际上就是在向 DecorView 的 mContentParent
执行 addView
操做,因此该方法才叫setContentView
而非setView
public class PhoneWindow extends Window implements MenuBuilder.Callback {
private DecorView mDecor;
ViewGroup mContentParent;
@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) {
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 {
//将 layoutResID 对应的 View 添加到 mContentParent 中
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
//回调通知 contentView 发生变化了
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
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.makeFrameworkOptionalFitsSystemWindows();
final DecorContentParent decorContentParent = (DecorContentParent) mDecor.findViewById(
R.id.decor_content_parent);
if (decorContentParent != null) {
mDecorContentParent = decorContentParent;
···
} else {
···
}
···
}
}
}
复制代码
mContentParent
经过 generateLayout
方法来完成初始化,该方法的逻辑能够分为三步:
<item name="windowNoTitle">true</item>
的话,那么就会执行 requestFeature(FEATURE_NO_TITLE)
来隐藏标题栏layoutResource
。之因此会有多种布局文件,是由于不一样的 Activity 会有不一样的显示要求,有的要求显示 title,有的要求显示 leftIcon,而有的可能全都不须要,为了不控件冗余就须要来选择合适的布局文件。而虽然每种布局文件结构上略有不一样,但均会包含一个 ID 名为content
的 FrameLayout,mContentParent
就对应该 FrameLayoutlayoutResource
生成对应的 View 对象并添加为本身的 childView,对应 DecorView 中的 mContentRoot
,后续执行的 findViewById(ID_ANDROID_CONTENT)
操做就都是交由 DecorView 来实现的了,而正常来讲每种 layoutResource
都会包含一个 ID 为 content
的 FrameLayout,若是发现找不到的话就直接抛出异常,不然就成功返回拿到 mContentParent
protected ViewGroup generateLayout(DecorView decor) {
// Apply data from current theme.
TypedArray a = getWindowStyle();
···
//第一步
if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
requestFeature(FEATURE_NO_TITLE);
} else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
// Don't allow an action bar if there is no title.
requestFeature(FEATURE_ACTION_BAR);
}
···
//第二步
int layoutResource;
···
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
//第三步
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
···
return contentParent;
}
复制代码
DecorView 是 FrameLayout 的子类,其 onResourcesLoaded
方法在拿到 PhoneWindow 传递过来的 layoutResource
后,就会生成对应的 View 并添加为本身的 childView,就像普通的 ViewGroup 执行 addView
方法同样,该 childView 就对应 mContentRoot
,咱们能够在 Activity 中经过(window.decorView as ViewGroup).getChildAt(0)
来获取到 mContentRoot
因此 DecorView 能够看作是 Activity 中整个视图树的根布局
public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
@UnsupportedAppUsage
private PhoneWindow mWindow;
ViewGroup mContentRoot;
DecorView(Context context, int featureId, PhoneWindow window,
WindowManager.LayoutParams params) {
···
}
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
if (mBackdropFrameRenderer != null) {
loadBackgroundDrawablesIfNeeded();
mBackdropFrameRenderer.onResourcesLoaded(
this, mResizingBackgroundDrawable, mCaptionBackgroundDrawable,
mUserCaptionBackgroundDrawable, getCurrentColor(mStatusColorViewState),
getCurrentColor(mNavigationColorViewState));
}
mDecorCaptionView = createDecorCaptionView(inflater);
final View root = inflater.inflate(layoutResource, null);
if (mDecorCaptionView != null) {
if (mDecorCaptionView.getParent() == null) {
addView(mDecorCaptionView,
new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
mDecorCaptionView.addView(root,
new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
} else {
// Put it below the color views.
addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
mContentRoot = (ViewGroup) root;
initializeElevation();
}
}
复制代码
完成以上步骤后,此时其实还只是完成了 Activity 整个视图树的加载工做,虽然 Activity 的 attach
方法已经建立了 Window 对象,但还须要将 DecorView 提交给 WindowManager 后才能正式将视图树展现到屏幕上
DecorView 具体的提交时机还须要看 ActivityThread 的 handleResumeActivity
方法,该方法用于回调 Activity 的 onResume
方法,里面还会回调到 Activity 的makeVisible
方法,从方法名能够猜出来makeVisible
方法就用于令 Activity 变为可见状态
@Override
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward, String reason) {
···
r.activity.makeVisible();
···
}
复制代码
makeVisible
方法会判断当前 Activity 是否已经将 DecorView 提交给 WindowManager 了,若是还没的话就进行提交,最后将 DecorView 的可见状态设为 VISIBLE,至此才创建起 Activity 和 WindowManager 之间的关联关系,以后 Activity 才正式对用户可见
void makeVisible() {
if (!mWindowAdded) {
ViewManager wm = getWindowManager();
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
mDecor.setVisibility(View.VISIBLE);
}
复制代码
对以上流程作下总结
setContentView
、findViewById
等操做都会交由 Window 来实现,Window 是 Activity 和整个 View 系统交互的入口layoutResource
,每种 layoutResource
虽然在布局结构上略有不一样,可是均会包含一个 ID 名为content
的 FrameLayout,contentParent
即该 FrameLayout。咱们能够经过 Window.ID_ANDROID_CONTENT
来拿到该 ID,也能够在 Activity 中经过 findViewById<View>(Window.ID_ANDROID_CONTENT)
来获取到contentParent
layoutResource
来生成对应的 rootView 并将开发者指定的 contentView 添加为contentParent
的 childView,因此能够将 DecorView 看作是视图树的根布局。正由于如此,Activity 的 findViewById
操做实际上会先交由 Window,Window 再交由 DecorView 去完成,由于 DecorView 才是实际持有 contentView 的容器类makeVisible
方法里提交给 WindowManager 的,以后 WindowManagerImpl 会经过 ViewRootImpl 来完成整个视图树的绘制流程,以后 Activity 才正式对用户可见这里我也提供一个自定义 Window 的 Demo,实现了基本的拖拽移动和点击事件,代码点击这里:AndroidOpenSourceDemo
最近比较倾向于只用一篇文章来写一个知识点,也懒得老是想文章标题,就一直沿用一开始用的一文读懂XXX,写着写着也攒了蛮多篇文章了,以前也已经写了几篇关于 View 系统的文章,但愿对你有所帮助 😇😇