Activity下的奥秘
首先我们得这知道 每个Activity下持有着单独的window对象,是他在感知用户的触摸事件并反馈给activity
但window只是规定功能规范的抽象类 真正的实现类为PhoneWindow 他将感知的事件发送给Activity处理
window因为是比较核心的代码有着非常重要的功能,android为其创建了一个管理接口WindowManager,真正实现为WindowManagerImpl,其他类通过WindowManger来间接使用window的功能
虽然window是人机交互的核心但是真正显示视图界面的是View,依附在Window上的View根视图为decorview,decorview下有着一个LinearLayout子视图
他包含着titlebar子视图和contentParent子视图,titlebar就是标题栏视图了,contentparent视图就是我们熟悉的setContentView(R.layout.activity_main)
的父视图了,正是这样嵌套的view与window与activity的布局,形成了我们android交互显示的基础,而这正是前人积累的宝贵经验,接下来让我们逐个分析
Activity与Window
Question1:Activity是在什么时机与Window联系上的呢?
先看看下面的代码:
private WindowManager mWindowManager;
private Window mWindow;
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) {
attachBaseContext(context);
mFragments.attachHost(null /*parent*/);
mWindow = new PhoneWindow(this, window, activityConfigCallback);//根据传进来的抽象类创建PhoneWindow实例
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);
}
mUiThread = Thread.currentThread();
mMainThread = aThread;
mInstrumentation = instr;
mToken = token;
mIdent = ident;
mApplication = application;
mIntent = intent;
mReferrer = referrer;
mComponent = intent.getComponent();
mActivityInfo = info;
mTitle = title;
mParent = parent;
mEmbeddedID = id;
mLastNonConfigurationInstances = lastNonConfigurationInstances;
if (voiceInteractor != null) {
if (lastNonConfigurationInstances != null) {
mVoiceInteractor = lastNonConfigurationInstances.voiceInteractor;
} else {
mVoiceInteractor = new VoiceInteractor(voiceInteractor, this, this,
Looper.myLooper());
}
}
mWindow.setWindowManager(//获取Window管理器接口创建WindowManagerImpl实现类并与window关联
(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();
mCurrentConfig = config;
mWindow.setColorMode(info.colorMode);
}
持有window的全局引用,在attach()函数中 创建了WindowManager并创建了PhoneWindow为window赋值,调用mWindow.setWindowManager使得window和管理器关联
Window与DecorView
Question2:Window是在什么时机与DecorView联系上的呢?
DecorView的深入
Window作为view的管理依附者,在Window被创建时应该就有DecorView存在 那在Window被创建的这段时间里发生了什么?
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
这段代码是不是很熟,这是我们在oncreat生命周期中设置自身视图布局的代码,我们可以看到他会交由自身的window设置xml布局,继续跟进
@Override
public void setContentView(int layoutResID) {
// mContentParent 是放置窗口内容的父 viewgroup ,可能是 decorView 本身,也有可能是它的子 viewgroup
// 如果 mContentParent 是空的,那么就说明 decorView 是空的
if (mContentParent == null) {
installDecor();//创建decorview
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
// 将 layout 布局加入到 mContentParent 中并去解析 layout xml 文件
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
// 通知 activity 窗口内容已经发生变化了
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
这里出现了两个分支:1.decorview创建分支 创建好后分支2.把我们设置的布局通过inflate解析并放入mContentParent内容根视图
private void installDecor() {//创建DecorView1
mForceDecorInstall = false;
if (mDecor == null) {
// 如果 decorview 为空,调用 generateDecor 来创建 decorview
mDecor = generateDecor(-1);
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
} else {
mDecor.setWindow(this);//这不是关联代码 只是将接口作为全局变量 真正的关联需要走windowManager
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);//传入decorview生成layout
// Set up decor part of UI to ignore fitsSystemWindows if appropriate.
mDecor.makeOptionalFitsSystemWindows();
final DecorContentParent decorContentParent = (DecorContentParent) mDecor.findViewById(
R.id.decor_content_parent);
...
}
}
}
分支1出现了两个分支11:如果不存在缓存decorview就执行生成函数,继续走若存在 依赖window接口 分支12.若没有缓存内容视图需要生成内容视图
protected DecorView generateDecor(int featureId) {//创建DecorView2 分支11
// System process doesn't have application context and in that case we need to directly use
// the context we have. Otherwise we want the application context, so we don't cling to the
// activity.
// 得到 context 上下文
Context context;
if (mUseDecorContext) {
Context applicationContext = getContext().getApplicationContext();
if (applicationContext == null) {
context = getContext();
} else {
context = new DecorContext(applicationContext, getContext().getResources());
if (mTheme != -1) {
context.setTheme(mTheme);
}
}
} else {
context = getContext();
}
// 创建 DecorView 对象
return new DecorView(context, featureId, this, getAttributes());
}
分支11 在处理好context环境后最终通过new Decorview生成Decorview对象
//分支12
protected ViewGroup generateLayout(DecorView decor) {//生成decorview的布局 返回mContentParent即我们设置的布局加上一些系统的主题等
// 应用当前的主题,比如设置一些 window 属性等
...
// 根据主题设置去选择 layoutResource
// 这个 layoutResource 也就是 DecorView 的子 View 的布局
int layoutResource;
int features = getLocalFeatures();
...
else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
// If no other features and not embedded, only need a title.
// If the window is floating, we need a dialog layout
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
R.attr.dialogTitleDecorLayout, res, true);
layoutResource = res.resourceId;
} else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
layoutResource = a.getResourceId(
R.styleable.Window_windowActionBarFullscreenDecorLayout,
R.layout.screen_action_bar);
} else {
layoutResource = R.layout.screen_title; //mContentParent视图也就是我们设置contentview的父视图 分支121
}
// System.out.println("Title!");
} else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
layoutResource = R.layout.screen_simple_overlay_action_mode;
} else {
// Embedded, so no decoration is needed.
layoutResource = R.layout.screen_simple;
// System.out.println("Simple!");
}
mDecor.startChanging();
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);//将layoutResource资源转换增加到decorview的布局中 分支122
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);//setcontentview的内容布局
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) {
ProgressBar progress = getCircularProgressBar(false);
if (progress != null) {
progress.setIndeterminate(true);
}
}
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
registerSwipeCallbacks();
}
// Remaining setup -- of background and title -- that only applies
// to top-level windows.
// 背景设置和标题设置
if (getContainer() == null) {
final Drawable background;
if (mBackgroundResource != 0) {
background = getContext().getDrawable(mBackgroundResource);
} else {
background = mBackgroundDrawable;
}
mDecor.setWindowBackground(background);
final Drawable frame;
if (mFrameResource != 0) {
frame = getContext().getDrawable(mFrameResource);
} else {
frame = null;
}
mDecor.setWindowFrame(frame);
mDecor.setElevation(mElevation);
mDecor.setClipToOutline(mClipToOutline);
if (mTitle != null) {
setTitle(mTitle);
}
if (mTitleColor == 0) {
mTitleColor = mTextColor;
}
setTitleColor(mTitleColor);
}
mDecor.finishChanging();//完成回调
return contentParent;
}
分支12 又分出两个分支 mContentParent的布局layout为分支121 最核心的是 将所有layout资源通过解析器加载 为分支122
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:fitsSystemWindows="true">
/* 弹出动作栏 */
<ViewStub android:id="@+id/action_mode_bar_stub"
android:inflatedId="@+id/action_mode_bar"
android:layout="@layout/action_mode_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="?attr/actionBarTheme" />
/* 标题栏 */
<FrameLayout
android:layout_width="match_parent"
android:layout_height="?android:attr/windowTitleSize"
style="?android:attr/windowTitleBackgroundStyle">
<TextView android:id="@android:id/title"
style="?android:attr/windowTitleStyle"
android:background="@null"
android:fadingEdge="horizontal"
android:gravity="center_vertical"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
/* 我们自设置的视图为取代这个Framelayout*/
<FrameLayout android:id="@android:id/content"
android:layout_width="match_parent"
android:layout_height="0dip"
android:layout_weight="1"
android:foregroundGravity="fill_horizontal|top"
android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>
分支121 将mcontentParent布局清晰易懂
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
mStackId = getStackId();
if (mBackdropFrameRenderer != null) {
loadBackgroundDrawablesIfNeeded();
mBackdropFrameRenderer.onResourcesLoaded(
this, mResizingBackgroundDrawable, mCaptionBackgroundDrawable,
mUserCaptionBackgroundDrawable, getCurrentColor(mStatusColorViewState),
getCurrentColor(mNavigationColorViewState));
}
mDecorCaptionView = createDecorCaptionView(inflater);
final View root = inflater.inflate(layoutResource, null);//将布局资源解析为root 也就是decorview下的mContentRoot
if (mDecorCaptionView != null) {
if (mDecorCaptionView.getParent() == null) {
addView(mDecorCaptionView,
new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));//给decorview加个根布局
}
mDecorCaptionView.addView(root,
new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));//给之前处理过的view加个根布局裹着mContentRoot
} else {
// Put it below the color views.
// 将 root 视图添加到 DecorView 中
addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
// 可以看出成员变量 mContentRoot 就是 DecorView 的直接子 View
// 也就是 mContentParent 的父视图
mContentRoot = (ViewGroup) root;
initializeElevation();
}
分支122 给decorview下 contentParent上加了个父布局 可能是因为资源配置或者布局需要
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX至此decorview的源码解析结束
之前说过,DecorView下有着titlebar布局和content布局 在大多数app没有titlebar的情况下 decorview可能就是content本身或是其父布局 在setContentView这个方法下 做了以下工作 1.创建DecorView 并在installDecor()中mDecor.setWindow(this);将decorview和window联系起来 创建了contentview的父视图为decorview内容 最终经过context环境配置下new DecorView创建 2.将自定义的视图 layout_main.xml 进行解析并添加到 mContentParent 中 代码:mLayoutInflater.inflate(layoutResID, mContentParent); 3.通知 activity 窗口视图已经改变了,进行相关操作 cb.onContentChanged();回调通知
目前为止的内容是decorview内容部分 。。。。。
###Window与DecorView连接
我们还缺一个内容没讲 即 decorview与window的连接 我们知道所有与window的操作要通过WindowManager 那么如何让decorview与window关联呢 让我们看这段代码
void makeVisible() {
if (!mWindowAdded) {
// WindowManager 是 ViewManager 的实现类
ViewManager wm = getWindowManager();
// 将 decorview 添加到 window 中
wm.addView(mDecor, getWindow().getAttributes());//真正的关联代码addview
mWindowAdded = true;
}
// 设置 decorview 可见
mDecor.setVisibility(View.VISIBLE);
}
这段代码是ActivityThread 的 handleResumeActivity()中回调了onResume()之后执行了 也就是activity处于前台时才与window绑定可以交互,就绑定window
ps:这里我们补充说明 ActivityThread 是Android APP进程的初始类 main函数为androidapp的程序入口 但这个线程不是主线程 主线程是在其他地方创建的专门处理ui 而main函数肯定是在主线程中执行的
至此activity window decorview三者之间的连接 构建成我们能交互的动态视图
最后总结
最终献上学长总结的一幅图 感谢🙏