概述
一直想写一篇从源码角度深入剖析关于window、View以及Activity之间关系的文章,帮助那些初级、中级Android开发人员加深一下对于这三者之间的理解,但是这几年都没能静下心来,好好写几篇好的文章。现在很多博客抄袭现象很严重,让很多开发者很头疼,随手搜索几篇就会发现,都是一样的,不知道我写的内容会不会和其他博主雷同,但是我觉得应该会有所区别。
废话不多说,window(窗口)在Android显示方面是比较基础且重要的概念,很多同学去面试的时候,都会被问到,“请你讲讲window、view以及activity三者的关系”,很多人懵了不知道从哪里入手去回答这个问题,那我告诉同学,你就从Activity设置布局Layout入口着手回答。
一、Activity的setContentView
在onCreate方法中点开setContentView源码进去看一下,发现是下面这段代码块:
这里说明下,有的同学打开后是下面这段代码块:
@Override
public void setContentView(@LayoutRes int layoutResID) {
getDelegate().setContentView(layoutResID);
}
因为你使用的是 AppCompatActivity,但是它最终也是继承Activity,本次我们不针对AppCompatActivity源码解读,其实AppCompatActivity的源码我们可以发现Activity的大部分生命周期都交给AppCompatDelegate进行委托处理,好了就此打住。
大家只看Activity源码里的,那么看下getWindow 返回的是 Activity 中的全局变量 mWindow,它是 Window 窗口类型。那么它是什么时候赋值的呢?
这个时候需要大家回忆下想要打开一个Activity需要先干嘛?那肯定是先startActivity()啦,最终代码会调用到 ActivityThread 中的 performLaunchActivity 方法,通过反射创建 Activity 对象,并执行其 attach 方法。Window 就是在这个方法中被创建的,代码如下:
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 = new PhoneWindow(this, window, activityConfigCallback);
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);
}
mUiThread = Thread.currentThread();
//省略
。。。
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
if (mParent != null) {
mWindow.setContainer(mParent.getWindow());
}
mWindowManager = mWindow.getWindowManager();
//省略
。。。。
}
在 Activity 的 attach 方法中将 mWindow 赋值给一个 PhoneWindow 对象,实际上整个 Android 系统中 Window 只有一个实现类,就是 PhoneWindow。
然后再看下 mWindow.setWindowManager 方法,将系统 WindowManager 传给 PhoneWindow,如下所示:
public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
boolean hardwareAccelerated) {
mAppToken = appToken;
mAppName = appName;
mHardwareAccelerated = hardwareAccelerated;
if (wm == null) {
wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
}
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}
public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
return new WindowManagerImpl(mContext, parentWindow);
}
根据上面代码可以看到,在 PhoneWindow 中实例化一个 WindowManagerImpl 对象,有同学疑惑了,WindowManagerImpl是啥呀,咱们先不讨论,等一下看。
二、phoneWindow.setContentView()
上面看完了attach方法,创建window等等一些列过程后,我们再回到一开始的
getWindow.setContentView(layoutResID)代码块根据上面分析也就是phoneWindow.setContentView(layoutResID);
图中 1 处调用 installDecor 初始化 DecorView 和 mContentParent。
图中 2 处调用 setContentView 传入的布局添加到 mContentParent 中。可以看出在 PhoneWindow 中默认有一个 DecorView,在 DecorView 中默认自带一个 mContentParent。我们自己实现的布局是被添加到 mContentParent 中的,因此经过 setContentView 之后,PhoneWindow 内部的 View 关系如下所示:
经过上面的步骤,目前为止 PhoneWindow 中只是创建出了一个 DecorView,并在 DecorView 中填充了我们在 Activity 中传入的 layoutId 布局,可是 DecorView 还没有跟 Activity 建立任何联系,也没有被绘制到界面上显示。也就是说还没有被 WindowManager 正式添加到 Window 中。
那 DecorView 是何时被绘制到屏幕上的呢?(注意下面讲解的一般是面试回答这个问题比较重要的地方)
在 ActivityThread 的 handleResumeActivity方法中,首先会调用 Acitivy 的 onResume 方法,接着会调用 Acitivy 的 makeVisible() 方法,正是在 makeVisible 方法中,DecorView 才真正的完成了显示过程,到这里 Activity 的视图才能被用户看到。
Activity 执行到 onCreate 时并不可见,只有执行完 onResume 之后 Activity 中的内容才是屏幕可见状态。造成这种现象的原因就是,onCreate 阶段只是初始化了 Activity 需要显示的内容,而在 onResume 阶段才会将 PhoneWindow 中的 DecorView 真正的绘制到屏幕上。
在 ActivityThread 的 handleResumeActivity 中,会调用 WindowManager 的 addView 方法将 DecorView 添加到 WMS(WindowManagerService) 上,如下所示:
@Override
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward, String reason) {
//省略
。。。。
。。。。
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;
ViewRootImpl impl = decor.getViewRootImpl();
if (impl != null) {
impl.notifyChildRebuilt();
}
}
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
wm.addView(decor, l);
} else {
a.onWindowAttributesChanged(l);
}
}
//省略
。。。。
。。。。
}
看下上面关键的代码, wm.addView(decor, l),
三、WindowManager 的 addView()
通过上面代码,知道交给了WindowManger来处理的,但它是个接口类型,它真正的实现者是 WindowManagerImpl 类,看一下它的 addView 方法如下:
发现它又调用了 WindowManagerGlobal 的 addView 方法,那WindowManagerGlobal是个啥?
说实话我也不知道,因为它做的事情太多了,没法定义它,简单点WindowMangerGlobal 是一个单例,每一个进程中只有一个实例对象。来看下它的addView方法,如下所示:
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
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 {
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
}
在其 addView 方法中,创建了一个最关键的 ViewRootImpl 对象,然后通过 ViewRootImpl 的 setView 方法将 view 添加到 WMS 中。
四、ViewRootImpl 的 setView()
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;
int res; /* = WindowManagerImpl.ADD_OKAY; */
requestLayout();
if ((mWindowAttributes.inputFeatures
& WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0)
mInputChannel = new InputChannel();
}
mForceDecorViewVisibility = (mWindowAttributes.privateFlags
& PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY) != 0;
try {
mOrigWindowType = mWindowAttributes.type;
mAttachInfo.mRecomputeGlobalAttributes = true;
collectViewAttributes();
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame,
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel,
mTempInsets);
setFrame(mTmpFrame);
}
上述代码中比较重要的requestLayout()方法 是刷新布局的操作,调用此方法后 ViewRootImpl 所关联的 View 也执行 measure - layout - draw 操作,确保在 View 被添加到 Window 上显示到屏幕之前,已经完成测量和绘制操作。
然后第二个重要的是调用 mWindowSession 的 addToDisplay 方法将 View 添加到 WMS 中。
WindowSession 是 WindowManagerGlobal 中的单例对象,初始化代码如下:
public static IWindowSession getWindowSession() {
synchronized (WindowManagerGlobal.class) {
if (sWindowSession == null) {
try {
InputMethodManager.ensureDefaultInstanceForDefaultDisplayIfNecessary();
IWindowManager windowManager = getWindowManagerService();
sWindowSession = windowManager.openSession(
new IWindowSessionCallback.Stub() {
@Override
public void onAnimatorScaleChanged(float scale) {
ValueAnimator.setDurationScale(scale);
}
});
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
return sWindowSession;
}
}
sWindowSession 实际上是 IWindowSession 类型,是一个 Binder 类型,真正的实现类是 System 进程中的 Session。上图中中就是用 AIDL 获取 System 进程中 Session 的对象。
-
检查参数合法性,如果是子 Window 做适当调整
-
创建 ViewRootImpl 并将 View 添加到集合中
-
通过 ViewRootImpl 来更新界面并完成 Window 的添加过程
-
在学习 View 的工作原理时,我们知道 View 的绘制过程是由 ViewRootImpl 来完成的,这里当然也不例外,具体是通过 ViewRootImpl 的 setView 方法来实现的。在 setView 内部会通过 requestLayout 来完成异步刷新请求,其中的 mWindowSession 的类型是 IWindowSession,它是一个 Binder 对象,真正的实现类是 Session,这也就是之前提到的 IPC 调用的位置。在 Session 内部会通过 WindowManagerService 来实现 Window 的添加
至此,Window 已经成功的被传递给了 WMS。剩下的工作就全部转移到系统进程中的 WMS 来完成最终的添加操作。
针对上面的流程我放一张图,同学看执行流程就会很清晰啦,如下图:
总结:
通过 setContentView 的流程,分析了 Activity、Window、View 之间的关系。整个过程 Activity 表面上参与度比较低,大部分 View 的添加操作都被封装到 Window 中实现。而 Activity 就相当于 Android 提供给开发人员的一个管理类,通过它能够更简单的实现 Window 和 View 的操作逻辑。
希望上面的讲解能够给大家理解这三者关系提供小小的帮助,也希望大家多多支持。
最后
以上就是爱笑柠檬为你收集整理的2022年最新源码讲解Window、View和Activity底层关系的全部内容,希望文章能够帮你解决2022年最新源码讲解Window、View和Activity底层关系所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复