《Android 源码 输入系统之窗口关联》 一节着重考虑了窗口怎样和输入系统关联,而这一节的重点在于窗口如何添加到图形系统。
先来看一个整体概览,再来详细分析。
先从 setContentView 说起。此函数的作用是:从布局资源设置 Activity 内容。资源将被解析,将所有顶级视图添加到 Activity 中。首先调用 getWindow() 获取 Window 对象(实际为 PhoneWindow 对象),然后调用其 setContentView(…) 方法。
frameworks/base/core/java/android/app/Activity.java
public class Activity extends ContextThemeWrapper
implements LayoutInflater.Factory2,
Window.Callback, KeyEvent.Callback,
OnCreateContextMenuListener, ComponentCallbacks2,
Window.OnWindowDismissedCallback {
......
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
// 初始化 ActionBar
initWindowDecorActionBar();
}
......
}
getWindow() 直接返回 mWindow 指向的 Window 对象。这可用于直接访问 “Activity/Screen” 无法使用的 Window API 部分。
frameworks/base/core/java/android/app/Activity.java
public class Activity extends ContextThemeWrapper
implements LayoutInflater.Factory2,
Window.Callback, KeyEvent.Callback,
OnCreateContextMenuListener, ComponentCallbacks2,
Window.OnWindowDismissedCallback {
......
private Window mWindow;
......
public Window getWindow() {
return mWindow;
}
......
}
mWindow 是在 attach(…) 函数中赋值的。实际指向一个 PhoneWindow 对象。
frameworks/base/core/java/android/app/Activity.java
public class Activity extends ContextThemeWrapper
implements LayoutInflater.Factory2,
Window.Callback, KeyEvent.Callback,
OnCreateContextMenuListener, ComponentCallbacks2,
Window.OnWindowDismissedCallback {
......
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) {
attachBaseContext(context);
mFragments.attachHost(null /*parent*/);
// 创建 PhoneWindow 对象
mWindow = new PhoneWindow(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);
}
......
// 设置 WindowManager,和 WMS 建立了关联
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());
}
// 获取 WindowManager 对象
mWindowManager = mWindow.getWindowManager();
mCurrentConfig = config;
}
......
}
tips:
安卓 5.0 中 Activity 和 Fragment 变换是建立在名叫 Transitions 的安卓新特性之上的。这个诞生于 4.4 的 transition 框架为在不同的 UI 状态之间产生动画效果提供了非常方便的 API。该框架主要基于两个概念:场景(scenes)和变换(transitions)。场景(scenes)定义了当前的 UI 状态,变换(transitions)则定义了在不同场景之间动画变化的过程。
当一个场景改变的时候,Transition 主要负责:
一、捕捉每个 View 在开始场景和结束场景时的状态。二、根据两个场景(开始和结束)之间的区别创建一个 Animator。
可以通过如下两种方式来启用:
假设我们没有启用 Window.FEATURE_CONTENT_TRANSITIONS 特性,setContentView(…) 主要完成了三个动作:
frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java
public class PhoneWindow extends Window implements MenuBuilder.Callback {
......
@Override
public void setContentView(int layoutResID) {
// 注意:当主题属性等明确化时,
// 可以在安装 Window Decor 的过程中设置 FEATURE_CONTENT_TRANSITIONS。
// 在此之前不要检查此特性。
if (mContentParent == null) {
// 1. 安装 Decor
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
// 2. 清空 mContentParent
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
// 3. 解析 layoutResID,填充到父容器 mContentParent
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}
......
}
frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java
public class PhoneWindow extends Window implements MenuBuilder.Callback {
......
private void installDecor() {
if (mDecor == null) {
// 生成 DecorView
mDecor = generateDecor();
// 焦点分发相关
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
......
}
if (mContentParent == null) {
// 生成 mContentParent
mContentParent = generateLayout(mDecor);
// 如果合适,将 UI 的 Decor 部分设置为忽略 fitsSystemWindows。
mDecor.makeOptionalFitsSystemWindows();
// 获取 Decor 内容父容器,实际为内容跟容器
final DecorContentParent decorContentParent = (DecorContentParent) mDecor.findViewById(
R.id.decor_content_parent);
if (decorContentParent != null) {
mDecorContentParent = decorContentParent;
mDecorContentParent.setWindowCallback(getCallback());
if (mDecorContentParent.getTitle() == null) {
mDecorContentParent.setWindowTitle(mTitle);
}
......
} else {
......
}
......
}
}
......
}
generateDecor() 方法非常简单,直接调用 DecorView 构造器返回 DecorView 对象。
frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java
public class PhoneWindow extends Window implements MenuBuilder.Callback {
......
protected DecorView generateDecor() {
return new DecorView(getContext(), -1);
}
......
}
frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java
public class PhoneWindow extends Window implements MenuBuilder.Callback {
......
private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
......
public DecorView(Context context, int featureId) {
super(context);
// 面板的功能部件 ID;如果这是应用程序的 DecorView,则为 -1
mFeatureId = featureId;
// 动画-显示插值器初始化
mShowInterpolator = AnimationUtils.loadInterpolator(context,
android.R.interpolator.linear_out_slow_in);
// 动画-隐藏插值器初始化
mHideInterpolator = AnimationUtils.loadInterpolator(context,
android.R.interpolator.fast_out_linear_in);
// Bar 进入、退出时间间隔初始化
mBarEnterExitDuration = context.getResources().getInteger(
R.integer.dock_enter_exit_duration);
}
......
}
......
}
再来分析 generateLayout(…)。此函数主要解析了根布局文件,获取了内容根容器和内容容器。
frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java
public class PhoneWindow extends Window implements MenuBuilder.Callback {
......
protected ViewGroup generateLayout(DecorView decor) {
......
// Inflate the window decor.
int layoutResource;
int features = getLocalFeatures();
// System.out.println("Features: 0x" + Integer.toHexString(features));
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
......
} else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
......
} else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
&& (features & (1 << FEATURE_ACTION_BAR)) == 0) {
......
} else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
......
} 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) {
......
} else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
// 假如使用了 ActionBar
layoutResource = a.getResourceId(
R.styleable.Window_windowActionBarFullscreenDecorLayout,
R.layout.screen_action_bar);
} else {
......
}
} else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
......
} else {
......
}
mDecor.startChanging();
// 解析根布局文件
View in = mLayoutInflater.inflate(layoutResource, null);
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
// 内容根容器
mContentRoot = (ViewGroup) in;
// 内容容器,ID_ANDROID_CONTENT 定义在 Window 类中,
// ID_ANDROID_CONTENT = com.android.internal.R.id.content
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
......
mDecor.finishChanging();
return contentParent;
}
......
}
我们来看 screen_action_bar 这个 layout 文件。根容器 ActionBarOverlayLayout 对应 mContentRoot;第一个 FrameLayout,其 android:id="@android:id/content",这就是 contentParent。
frameworks/base/core/res/res/layout/screen_action_bar.xml
<com.android.internal.widget.ActionBarOverlayLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/decor_content_parent"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:splitMotionEvents="false"
android:theme="?attr/actionBarTheme">
<FrameLayout android:id="@android:id/content"
android:layout_width="match_parent"
android:layout_height="match_parent" />
......
com.android.internal.widget.ActionBarOverlayLayout>
接下来继续分析解析 layoutResID,填充到父容器 mContentParent。inflate 方法有很多重载,最后会调用 xml PULL 方式解析布局 layout,将其添加到 mContentParent 代表的 FrameLayout 中,完成内容视图添加。
frameworks/base/core/java/android/view/LayoutInflater.java
public abstract class LayoutInflater {
......
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null);
}
......
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
if (DEBUG) {
Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
+ Integer.toHexString(resource) + ")");
}
final XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
......
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");
final Context inflaterContext = mContext;
final AttributeSet attrs = Xml.asAttributeSet(parser);
Context lastContext = (Context) mConstructorArgs[0];
mConstructorArgs[0] = inflaterContext;
View result = root;
try {
// 查找根节点。
int type;
while ((type = parser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT) {
// Empty
}
if (type != XmlPullParser.START_TAG) {
throw new InflateException(parser.getPositionDescription()
+ ": No start tag found!");
}
final String name = parser.getName();
......
// merge 标签处理
if (TAG_MERGE.equals(name)) {
if (root == null || !attachToRoot) {
throw new InflateException(" can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
}
rInflate(parser, root, inflaterContext, attrs, false);
} else {
// Temp 是在 xml 中找到的根 View
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
if (root != null) {
if (DEBUG) {
System.out.println("Creating params from root: " +
root);
}
// 创建与 root 匹配的布局参数(如果提供)
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
temp.setLayoutParams(params);
}
}
......
// 解析子布局
rInflateChildren(parser, temp, attrs, true);
......
// 将 temp View 添加到 root。
if (root != null && attachToRoot) {
root.addView(temp, params);
}
......
}
} catch (XmlPullParserException e) {
......
} catch (Exception e) {
......
} finally {
......
}
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
return result;
}
}
......
}
在 Activity 启动流程分析中,我们知道启动一个 Activity 当调用了 ActivityThread 中 handleLaunchActivity 方法后,马上就会调用 handleResumeActivity 方法,奥秘就在这里!
frameworks/base/core/java/android/app/ActivityThread.java
public final class ActivityThread {
......
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume) {
......
// 会触发 activity 生命周期方法 onResume(...)
ActivityClientRecord r = performResumeActivity(token, clearHide);
if (r != null) {
final Activity a = r.activity;
......
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
// 设置 DecorView 为不可见
decor.setVisibility(View.INVISIBLE);
// 获取 WindowManager 对象
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
if (a.mVisibleFromClient) {
a.mWindowAdded = true;
// 将 DecorView 添加到 ViewManager
wm.addView(decor, l);
}
} else if (!willBeVisible) {
......
}
......
// 告诉活动管理器(AMS)我们已经恢复。
if (reallyResume) {
try {
ActivityManagerNative.getDefault().activityResumed(token);
} catch (RemoteException ex) {
}
}
} else {
......
}
}
......
}
不出所料获取 Window 的 DecorView 就是在 PhoneWindow 类中的全局变量 mDecor 指向的 DecorView。
frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java
public class PhoneWindow extends Window implements MenuBuilder.Callback {
......
@Override
public final View getDecorView() {
if (mDecor == null) {
installDecor();
}
return mDecor;
}
......
}
wm 局部变量实际指向 WindowManagerImpl 对象,所以实际调用了 WindowManagerImpl 类的 addView 方法。此处又调用了 WindowManagerGlobal 单例的 addView 方法。
frameworks/base/core/java/android/view/WindowManagerImpl.java
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, mDisplay, mParentWindow);
}
......
}
frameworks/base/core/java/android/view/WindowManagerImpl.java
public final class WindowManagerGlobal {
......
private final ArrayList<View> mViews = new ArrayList<View>();
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
private final ArrayList<WindowManager.LayoutParams> mParams =
new ArrayList<WindowManager.LayoutParams>();
private final ArraySet<View> mDyingViews = new ArraySet<View>();
......
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");
}
// 获取 WindowManager.LayoutParams
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
if (parentWindow != null) {
parentWindow.adjustLayoutParamsForSubWindow(wparams);
} else {
......
}
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
......
// mViews 中查找新添加 DecorView
int index = findViewLocked(view, false);
if (index >= 0) {
if (mDyingViews.contains(view)) {
......
} else {
// 已经添加过,抛出 IllegalStateException 异常
throw new IllegalStateException("View " + view
+ " has already been added to the window manager.");
}
// The previous removeView() had not completed executing. Now it has.
}
// 从 mViews 中找到 panelParentView
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);
}
}
}
// 创建 ViewRootImpl 对象
root = new ViewRootImpl(view.getContext(), display);
// DecorView 设置 LayoutParams
view.setLayoutParams(wparams);
// 向容器中添加各种对象
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
}
try {
// ViewRootImpl 设置 DecorView
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
......
}
}
......
}
现在重点关注 ViewRootImpl setView(…) 方法:
frameworks/base/core/java/android/view/ViewRootImpl.java
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks {
......
final W mWindow;
......
// Surface 对象创建
final Surface mSurface = new Surface();
......
public ViewRootImpl(Context context, Display display) {
mContext = context;
mWindowSession = WindowManagerGlobal.getWindowSession();
mDisplay = display;
mBasePackageName = context.getBasePackageName();
mDisplayAdjustments = display.getDisplayAdjustments();
mThread = Thread.currentThread();
mLocation = new WindowLeaked(null);
mLocation.fillInStackTrace();
mWidth = -1;
mHeight = -1;
mDirty = new Rect();
mTempRect = new Rect();
mVisRect = new Rect();
mWinFrame = new Rect();
mWindow = new W(this);
mTargetSdkVersion = context.getApplicationInfo().targetSdkVersion;
mViewVisibility = View.GONE;
mTransparentRegion = new Region();
mPreviousTransparentRegion = new Region();
mFirst = true; // true for the first time the view is added
mAdded = false;
mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this);
mAccessibilityManager = AccessibilityManager.getInstance(context);
mAccessibilityInteractionConnectionManager =
new AccessibilityInteractionConnectionManager();
mAccessibilityManager.addAccessibilityStateChangeListener(
mAccessibilityInteractionConnectionManager);
mHighContrastTextManager = new HighContrastTextManager();
mAccessibilityManager.addHighTextContrastStateChangeListener(
mHighContrastTextManager);
mViewConfiguration = ViewConfiguration.get(context);
mDensity = context.getResources().getDisplayMetrics().densityDpi;
mNoncompatDensity = context.getResources().getDisplayMetrics().noncompatDensityDpi;
mFallbackEventHandler = new PhoneFallbackEventHandler(context);
mChoreographer = Choreographer.getInstance();
mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
loadSystemProperties();
}
......
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;
......
// 安排第一次 layout,在添加到 WMS 之前
// 确保在从系统接收任何其他事件之前进行重新布局
requestLayout()
......
try {
......
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mInputChannel);
} catch (RemoteException e) {
......
} finally {
......
}
......
}
}
}
......
static class W extends IWindow.Stub {
private final WeakReference<ViewRootImpl> mViewAncestor;
private final IWindowSession mWindowSession;
W(ViewRootImpl viewAncestor) {
mViewAncestor = new WeakReference<ViewRootImpl>(viewAncestor);
mWindowSession = viewAncestor.mWindowSession;
}
......
}
......
}
requestLayout() 方法主要调用了 scheduleTraversals() 进一步处理,后续详细分析。
frameworks/base/core/java/android/view/ViewRootImpl.java
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks {
......
boolean mHandlingLayoutInLayoutRequest = false;
......
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
......
}
现在开始关注 Session 类 addToDisplay 方法。
Session 类表示活动的客户端会话。通常,每个与窗口管理器交互的进程都有一个 Session 对象。
addToDisplay 方法实际工作由 WindowManagerService 类 addWindow 方法完成。
frameworks/base/services/core/java/com/android/server/wm/Session.java
final class Session extends IWindowSession.Stub
implements IBinder.DeathRecipient {
final WindowManagerService mService;
......
@Override
public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets,
Rect outOutsets, InputChannel outInputChannel) {
return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
outContentInsets, outStableInsets, outOutsets, outInputChannel);
}
......
}
addWindow 方法首先创建了 WindowState 对象,然后调用了其 attach() 方法进行附着,最后将其添加到了 mWindowMap 中。attach() 以后的流程在接下来的文章中详细分析。
frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
public class WindowManagerService extends IWindowManager.Stub
implements Watchdog.Monitor, WindowManagerPolicy.WindowManagerFuncs {
......
// 从 IWindow IBinder 映射到服务端的 Window 对象(WindowState)。
final HashMap<IBinder, WindowState> mWindowMap = new HashMap<>();
......
public int addWindow(Session session, IWindow client, int seq,
WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
InputChannel outInputChannel) {
......
WindowState attachedWindow = null;
......
synchronized(mWindowMap) {
......
WindowState win = new WindowState(this, session, client, token,
attachedWindow, appOp[0], seq, attrs, viewVisibility, displayContent);
......
// From now on, no exceptions or errors allowed!
res = WindowManagerGlobal.ADD_OKAY;
......
win.attach();
mWindowMap.put(client.asBinder(), win);
......
}
......
}
......
}
写在最后画时序图总结一下。