今天这里我们聊四个话题:
1. Activity的界面渲染过程;
2. 子view的添加刷新过程
3. WindowManager的添加渲染过程 【比如添加一个子window】
4. view的渲染流程
5. view的事件传递过程
相关类:
• Acitivty
• PhoneWindow
• Window
• WindowManager
• WidnowManagerImpl
• WindowManagerGlobal
• LayoutInflater
• ViewGroup
话题1:Activity的界面渲染过程
下图就是手机屏幕的布局层次,最外层是phoneWindow,我用的是虚线,意思是并没有一个真正的这样一个层级,他只是Activity用来操作DecorView上的一个工具库,每个APP都有个的window窗口, 每个Activity都会有一个默认的DecorView,这是Activity的顶层层级视图, 在这个view 层级上面,是Android官方人员为了方便开发者使用, 添加到默认的一个titleBar标题栏视图和content视图, 一般我们在activity 的oncreate()方法里,调用的setContentView(),就是放在里面了。

window 是 activity 里的一个实例变量,本质是一个接口,唯一的实现类是 PhoneWindow。
activity 的 setContentView 方法实际上是就是交给 phonewindow 去做的。window 和 View 的关系可以类比为显示器和显示的内容。
每个 activity 都有一个“显示器” window,“显示的内容”就是 DecorView。这个“显示器”定义了一些方法来决定如何显示内容。比如 setTitleColor setTitle 是设置导航栏的颜色和 title , setAllowReturnTransitionOverlap 设置进/出场动画等等。
所以 window 是 activity 的一个成员变量,window 和 View 是“显示器”和“显示内容”的关系
activity的层级显示,有生成布局和加载两个过程:
生成布局
在onCreate()中的setContentView():
setContentView(R.layout.activity_main)
Activity里的setContentView:
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
可以细看一下getWindow的mWindow的赋值时机,是在Activity的Attach()方法里:
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
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,
IBinder shareableActivityToken) {
attachBaseContext(context);
mFragments.attachHost(null /*parent*/);
mActivityInfo = info;
//这里实例化了
mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow.setWindowControllerCallback(mWindowControllerCallback);
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);
}
这样就能看出来,使用的PhoneWindow 去做下一步的布局工作。PhoneWindow一般是在androidStudio看不到的, 所以可以看http://androidxref.com/9.0.0_r3/xref/frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java链接。
/**
* XINHAO_HAN 设置Activity的View
* @param layoutResID
*/
@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.
/**
* 如果第一次设置 mContentParent为ViewGroup
*/
if (mContentParent == null) {
/**
* 就开始初始化
*/
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
/**
* 否则就移除掉所有子View等待重新添加
*/
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;
}
这里面有两个点,一个是 installDecor()方法,用来初始化DecorView的,一个是inflate方法,用来将content布局绑定到 root上的
private void installDecor() {
mForceDecorInstall = false;
//如果DecorView为空就开始设置DecorView
if (mDecor == null) {
//Decor就是在这里创建的,我们看一下这个方法
mDecor = generateDecor(-1);
//获取焦点
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
} else {
mDecor.setWindow(this);
}
......
}
我们看一下这个方法 generateDecor(-1);
protected DecorView generateDecor(int featureId) {
// 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;
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();
}
return new DecorView(context, featureId, this, getAttributes());
}
这块是直接new了一个DecorView,就相当于要添加到PhoneWindow里边的View
。这就是最顶层布局decorView添加的 流程
另外一部分执行,就是LayoutInflater里的inflate方法,将view添加到了root里面:
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;
if (root != null && root.getViewRootImpl() != null) {
root.getViewRootImpl().notifyRendererOfExpensiveFrame();
}
try {
advanceToRootNode(parser);
final String name = parser.getName();
if (DEBUG) {
System.out.println("**************************");
System.out.println("Creating root view: "
+ name);
System.out.println("**************************");
}
if (TAG_MERGE.equals(name)) {
if (root == null || !attachToRoot) {
throw new InflateException("<merge /> can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
}
rInflate(parser, root, inflaterContext, attrs, false);
} else {
// Temp is the root view that was found in the xml
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
if (root == null && temp != null && temp.getViewRootImpl() != null) {
temp.getViewRootImpl().notifyRendererOfExpensiveFrame();
}
ViewGroup.LayoutParams params = null;
if (root != null) {
if (DEBUG) {
System.out.println("Creating params from root: " +
root);
}
// Create layout params that match root, if supplied
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
// Set the layout params for temp if we are not
// attaching. (If we are, we use addView, below)
temp.setLayoutParams(params);
}
}
if (DEBUG) {
System.out.println("-----> start inflating children");
}
// Inflate all children under temp against its context.
rInflateChildren(parser, temp, attrs, true);
if (DEBUG) {
System.out.println("-----> done inflating children");
}
// We are supposed to attach all the views we found (int temp)
// to root. Do that now.
if (root != null && attachToRoot) {
root.addView(temp, params);
}
// Decide whether to return the root that was passed in or the
// top view found in xml.
if (root == null || !attachToRoot) {
result = temp;
}
}
} catch (XmlPullParserException e) {
final InflateException ie = new InflateException(e.getMessage(), e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} catch (Exception e) {
final InflateException ie = new InflateException(
getParserStateDescription(inflaterContext, attrs)
+ ": " + e.getMessage(), e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} finally {
// Don't retain static reference on context.
mConstructorArgs[0] = lastContext;
mConstructorArgs[1] = null;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
return result;
}
}
上面部分就是, 界面的添加流程了。 下面来说 界面的渲染流程, 他是在Activity 的Resum方法里.。大家要明白 window和Acitivty、view的关系, 根据上面的代码来看,
Window并不是真实地存在着的,而是以View的形式存在。好吧,我知道这句话听起来有点矛盾。Window本身就只是一个抽象的概念,而View是Window的表现形式,也就是说View是Window的代表。当我们在谈论Window时其实是在谈论Window的根View。
要想显示窗口,就必须调用WindowManager.addView(View view, ViewGroup.LayoutParams params)。参数view就代表着一个窗口。在Activity和Dialog的显示过程中都会调用到wm.addView(decor, l);所以Activity和Dialog的DecorView就代表着各自的窗口。
渲染布局
我们会在onCreate()调用setContentView(),里面会把对应的view添加到mDecorView里面,执行完onCreate()之后ActivityManagerService会回调onResume(),在回调的过程中:
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
...
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
//得到attach里面的 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) {
a.mWindowAdded = true;
//把decorView添加进来
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;
}
...
if (r.activity.mVisibleFromClient) {
//把mDecorView显示出来
r.activity.makeVisible();
}
}
...
} 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的显示过程实际就是调用windowManager.addView();
Window是一个抽象的概念,每一个Window都对应着一个View和一个ViewRootImpl,Window和View通过ViewRootImpl来建立联系,就像(activity的)phoneWindow和popUpWindow 我们设置setContentView来绑定对应的view;
说明View才是Window存在的实体,在实际使用中无法直接访问Window,对Window的访问必须通过WindowManager,比如window的添加过程我们会通过WindowManager.addView(),但是windowManager是一个接口,它具体的实现类是WindowManagerImpl类,但是WindowManagerImpl没有具体实现ViewManager的addView()、updateLayoutView()、removeView(),而是交给WindowManagerGlobal去实现。添加view具体实现步骤如下:
1、检查参数是否合法,如果是子Window那么嗨需要调整一些布局参数
2、创建ViewRootImpl并将View添加到列表
3、通过ViewRootImpl来更新界面并完成Window的添加过程

问题2:子view的添加刷新过程
我们添加子view,都是调用的ViewGroup.addView()方法,viewGroup的 子视图,都是树状视图,无论是xml还是通过代码手动添加的,其数据结构展现出来的就是一个树,一个一对多的存储关系的集合。
在代码中我们表现这个树状数据结构的方式:在ViewGroup中设置了一个子View的List,通过让最上层的父节点DevorView(他也是一个ViewGroup)—持有好几个ViewGroup,里面的ViewGroup中每个又持有多个ViewGroup,层层嵌套到最底层的View为止。

xml和在java中实例化添加的视图,都是集成的ViewGroup的类。 这个类可以看一下他的 源码:

我们分析一下ViewGroup的加载子view的流程, 这个方法,一般都会使用。

我们调用的,会调用上面的这个重载方法,里面做的事情呢, 会
判断一下子view是不是空,也会去获取一下laoutParam,也就是长宽的模式,这个是用来计算布局宽高的, 如果没有,就会默认使用:
protected LayoutParams generateDefaultLayoutParams() {
return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
}
再继续看:

这里面做了三件大事:
请求重新计算布局
//frameworks/base/core/java/android/view/ViewRootImpl.java
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks {
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread(); //校验所在线程
mLayoutRequested = true;
scheduleTraversals(); //调度视图的遍历和绘制过程
}
}
这个就不展开说了, 可以关注下 checkThread()这个方法, 里面其实就是判断是否是主线程,不是主线程刷新,就会抛出异常错误【前面文章有说过】。最终走到:
ViewRootImpl performTraversals
调用ViewRootImpl的performTraversals方法,执行View的三大流程(measure、layout、draw):
Android13 ViewRootImpl performTraversals流程分析-CSDN博客
添加view
//frameworks/base/core/java/android/view/ViewGroup.java
public abstract class ViewGroup extends View implements ViewParent, ViewManager {
private void addViewInner(View child, int index, LayoutParams params,
boolean preventRequestLayout) {
if (mTransition != null) {
// Don't prevent other add transitions from completing, but cancel remove
// transitions to let them complete the process before we add to the container
mTransition.cancel(LayoutTransition.DISAPPEARING);
}
if (child.getParent() != null) {
throw new IllegalStateException("The specified child already has a parent. " +
"You must call removeView() on the child's parent first.");
}
if (mTransition != null) {
mTransition.addChild(this, child);
}
if (!checkLayoutParams(params)) {
params = generateLayoutParams(params);
}
if (preventRequestLayout) {
child.mLayoutParams = params;
} else {
child.setLayoutParams(params);
}
if (index < 0) {
index = mChildrenCount;
}
addInArray(child, index);
// tell our children
if (preventRequestLayout) {
child.assignParent(this);
} else {
child.mParent = this;
}
if (child.hasUnhandledKeyListener()) {
incrementChildUnhandledKeyListeners();
}
final boolean childHasFocus = child.hasFocus();
if (childHasFocus) {
requestChildFocus(child, child.findFocus());
}
AttachInfo ai = mAttachInfo;
if (ai != null && (mGroupFlags & FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW) == 0) {
boolean lastKeepOn = ai.mKeepScreenOn;
ai.mKeepScreenOn = false;
child.dispatchAttachedToWindow(mAttachInfo, (mViewFlags&VISIBILITY_MASK));
if (ai.mKeepScreenOn) {
needGlobalAttributesUpdate(true);
}
ai.mKeepScreenOn = lastKeepOn;
}
if (child.isLayoutDirectionInherited()) {
child.resetRtlProperties();
}
dispatchViewAdded(child);
if ((child.mViewFlags & DUPLICATE_PARENT_STATE) == DUPLICATE_PARENT_STATE) {
mGroupFlags |= FLAG_NOTIFY_CHILDREN_ON_DRAWABLE_STATE_CHANGE;
}
if (child.hasTransientState()) {
childHasTransientStateChanged(child, true);
}
if (child.getVisibility() != View.GONE) {
notifySubtreeAccessibilityStateChangedIfNeeded();
}
if (mTransientIndices != null) {
final int transientCount = mTransientIndices.size();
for (int i = 0; i < transientCount; ++i) {
final int oldIndex = mTransientIndices.get(i);
if (index <= oldIndex) {
mTransientIndices.set(i, oldIndex + 1);
}
}
}
if (mCurrentDragStartEvent != null && child.getVisibility() == VISIBLE) {
notifyChildOfDragStart(child);
}
if (child.hasDefaultFocus()) {
// When adding a child that contains default focus, either during inflation or while
// manually assembling the hierarchy, update the ancestor default-focus chain.
setDefaultFocus(child);
}
touchAccessibilityNodeProviderIfNeeded(child);
调用的主要是 dispatchViewAdded(child)方法,用来将其添加
问题3 :WindowManager的添加渲染过程 【比如添加一个子window】
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Button floatingButton = new Button(this);
floatingButton.setText("button");
WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT,
0, 0,
PixelFormat.TRANSPARENT
);
// flag 设置 Window 属性
layoutParams.flags= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
// type 设置 Window 类别(层级)
layoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY;
layoutParams.gravity = Gravity.CENTER;
WindowManager windowManager = getWindowManager();
windowManager.addView(floatingButton, layoutParams);
}
}
上面代码就是通过一个windowManager 添加View的过程。
首先我们要根据上面的代码,逐个来看, 先看windowManager这个对象的创建过程,activity 的getWindowManager()走到了, Window类中的
public WindowManager getWindowManager() {
return mWindowManager;
}
再看mWindowManager从哪里来的:
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);
}
获取了一个WindowManagerImpl 对象,这是一个WindowManger的具体操作类,他里面有方法:
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
public void setDefaultToken(IBinder token) {
mDefaultToken = token;
}
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyTokens(params);
mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
mContext.getUserId());
}
@Override
public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyTokens(params);
mGlobal.updateViewLayout(view, params);
}
private void applyTokens(@NonNull ViewGroup.LayoutParams params) {
if (!(params instanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
}
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
assertWindowContextTypeMatches(wparams.type);
// Only use the default token if we don't have a parent window and a token.
if (mDefaultToken != null && mParentWindow == null && wparams.token == null) {
wparams.token = mDefaultToken;
}
wparams.mWindowContextToken = mWindowContextToken;
}
这些都是对WindowManager方法的的实例化。而且他其实也是代理类, 操作的是WindowManagerGlobal 他才是真正去把view和 dispaly去绑定起来的主要方法:
看WindowManagerGlobal .java文件中:
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;
}
}
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);
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);
}
}
}
IWindowSession windowlessSession = null;
// If there is a parent set, but we can't find it, it may be coming
// from a SurfaceControlViewHost hierarchy.
if (wparams.token != null && panelParentView == null) {
for (int i = 0; i < mWindowlessRoots.size(); i++) {
ViewRootImpl maybeParent = mWindowlessRoots.get(i);
if (maybeParent.getWindowToken() == wparams.token) {
windowlessSession = maybeParent.getWindowSession();
break;
}
}
}
if (windowlessSession == null) {
root = new ViewRootImpl(view.getContext(), display);
} else {
root = new ViewRootImpl(view.getContext(), display,
windowlessSession, new WindowlessWindowLayout());
}
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, userId);
} catch (RuntimeException e) {
final int viewIndex = (index >= 0) ? index : (mViews.size() - 1);
// BadTokenException or InvalidDisplayException, clean up.
if (viewIndex >= 0) {
removeViewLocked(viewIndex, true);
}
throw e;
}
}
}
在 WindowManagerGlobal 内部有如下几个集合比较重要:
1. private final ArrayList<View> mViews = new ArrayList<View>();
2. private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
3. private final ArrayList<WindowManager.LayoutParams> mParams = new ArrayList<WindowManager.LayoutParams>();
4. private final ArraySet<View> mDyingViews = new ArraySet<View>();
其中 mViews 存储的是所有 Window 所对应的 View,mRoots 存储的是所有 Window 所对应的 ViewRootImpl,mParams 存储的是所有 Window 所对应的布局参数,mDyingViews 存储了那些正在被删除的 View 对象,或者说是那些已经调用了 removeView 方法但是操作删除还未完成的 Window 对象,可以通过表格直观的表示:
集合 存储内容
mViews Window 所对应的 View
mRoots Window 所对应的 ViewRootImpl
mParams Window 所对应的布局参数
mDyingViews 正在被删除的 View 对象
addView 操作时会将相关对象添加到对应集合中:
5. root = new ViewRootImpl(view.getContext(),display);
6. view.setLayoutParams(wparams);
7. mViews.add(view);
8. mRoots.add(root);
9. mParams.add(wparams);
在学习 View 的工作原理时,我们知道 View 的绘制过程是由 ViewRootImpl 来完成的,这里当然也不例外,具体是通过 ViewRootImpl 的 setView 方法来实现的。在 setView 内部会通过 requestLayout 来完成异步刷新请求,如下:
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 手上了,在 WindowManagerService 内部会为每一个应用保留一个单独的 Session,具体 Window 在 WindowManagerService 内部是怎么添加的,就不对其进一步的分析,因为到此为止我们对 Window 的添加这一从应用层到 Framework 的流程已经清楚了,下面通过图示总结一下:
理解了 Window 的添加过程,Window 的删除过程和更新过程都是类似的,也就容易理解了,它们最终都会通过一个 IPC 过程将操作移交给 WindowManagerService 这个位于 Framework 层的窗口管理服务来处理。
所有的window显示都是跨进程通信IPC,交给了WMS进行在surfaceView上绘制的
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);
}
看这里, 每个window都有一个windowManager , 他是通过获取系统服务创建的,都有一个WindowManagerGlobal 对象。
这里稍微说一下SurfaceFlinger,
urfaceFlinger
SurfaceFlinger 作用是合成所有 Layer 并送显。在 App 请求创建 Surface 时,SurfaceFlinger 会创建一个 Layer,在拿到从 WMS 传过来的 Window 宽高、位置,以及 App 提供需要绘制的 View 后合成该 Layer,然后送给屏幕进行显示。
在 Android 平台上创建的每个 Window 都由 Surface 提供支持,所有被渲染的可见 Surface 都被 SurfaceFlinger 合成到屏幕。
添加过程经历这些:
Window/View
WindowManager.addView()
WindowManagerImpl.addView()
WindowManagerGlobal.addView()
ViewRootImpl.setView()
WindowManagerService
问题4:View的渲染流程
我们就从WindowManagerGlobal谈起, 因为从上面WindowManager的分析里看到, 最后都是通过ViewRootImpl 的addVIew方法:
// WindowManagerGlobal的addView方法
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) {
...
ViewRootImpl root;
View pannelParentView = null;
synchronized (mLock) {
...
// 创建ViewRootImpl实例
root = new ViewRootImpl(view..getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
}
try {
// 把DecorView加载到Window中
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
synchronized (mLock) {
final int index = findViewLocked(view, false);
if (index >= 0) {
removeViewLocked(index, true);
}
}
throw e;
}
}
绘制会从根视图ViewRoot的performTraversals()方法开始,从上到下遍历整个视图树,每个View控件负责绘制自己,而ViewGroup还需要负责通知自己的子View进行绘制操作。performTraversals()的核心代码如下。
private void performTraversals() {
...
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
...
//执行测量流程
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
//执行布局流程
performLayout(lp, desiredWindowWidth, desiredWindowHeight);
...
//执行绘制流程
performDraw();
}
preformLayout和performDraw的传递流程和performMeasure是类似的,唯一不同的是,performDraw的传递过程是在draw方法中通过dispatchDraw来实现的,不过这并没有本质区别。
获取content:
ViewGroup content = (ViewGroup)findViewById(android.R.id.content);
获取设置的View:
content.getChildAt(0);
至于里面的细节其实可以深挖,就不在本文里赘述了,本文主要说流程
问题5:view的事件传递过程
首先我们要关注触摸事件是怎么在驱动层传上来的, 这个我说下Framework层的。在Android的输入系统里主要关联到 InputManagerService(简称IMS)和WindowManagerService(简称WMS),IMS负责事件的捕获,WMS负责窗口的管理。
SystemServer与APP端的事件传递通过Socket通讯,APP端由InputEventReceiver的子类,WindowInputEventReceiver 接收事件并传递给InputStage责任链,InputStage责任链进行层层判断是否消费还是交个下一链处理,其中 ViewPostImeInputStage 负责分发InputEvent事件到View框架,View层由DecorView首先接收到事件,DecorView内分发给Window.callBack其实现类有Activity dialog等。
在Activity内部分发又调用PhoneWindow的superDispatchTouchEvent,最终又调用回DecorView的superDispatchTouchEvent,
DecorView为FrameLayout的子类固调用ViewGroup的DispatchTouchEvent。ViewGroup 先判断是否要拦截如果拦截自己处理,如果不拦截则子View负责进行后续分发。
/**
* 源码分析:ViewGroup.dispatchTouchEvent()
*/
public boolean dispatchTouchEvent(MotionEvent ev) {
// 仅贴出关键代码
...
if (disallowIntercept || !onInterceptTouchEvent(ev)) {
// 分析1:ViewGroup每次事件分发时,都需调用onInterceptTouchEvent()询问是否拦截事件
// 判断值1-disallowIntercept:是否禁用事件拦截的功能(默认是false),可通过调用requestDisallowInterceptTouchEvent()修改
// 判断值2-!onInterceptTouchEvent(ev) :对onInterceptTouchEvent()返回值取反
// a. 若在onInterceptTouchEvent()中返回false,即不拦截事件,从而进入到条件判断的内部
// b. 若在onInterceptTouchEvent()中返回true,即拦截事件,从而跳出了该条件判断
// c. 关于onInterceptTouchEvent() ->>分析1
// 分析2
// 1\. 通过for循环,遍历当前ViewGroup下的所有子View
for (int i = count - 1; i >= 0; i--) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
|| child.getAnimation() != null) {
child.getHitRect(frame);
// 2\. 判断当前遍历的View是不是正在点击的View,从而找到当前被点击的View
if (frame.contains(scrolledXInt, scrolledYInt)) {
final float xc = scrolledXFloat - child.mLeft;
final float yc = scrolledYFloat - child.mTop;
ev.setLocation(xc, yc);
child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
// 3\. 条件判断的内部调用了该View的dispatchTouchEvent()
// 即 实现了点击事件从ViewGroup到子View的传递(具体请看下面章节介绍的View事件分发机制)
if (child.dispatchTouchEvent(ev)) {
// 调用子View的dispatchTouchEvent后是有返回值的
// 若该控件可点击,那么点击时dispatchTouchEvent的返回值必定是true,因此会导致条件判断成立
// 于是给ViewGroup的dispatchTouchEvent()直接返回了true,即直接跳出
// 即该子View把ViewGroup的点击事件消费掉了
mMotionTarget = child;
return true;
}
}
}
}
}
}
return super.dispatchTouchEvent(ev);
// 若无任何View接收事件(如点击空白处)/ViewGroup本身拦截了事件(复写了onInterceptTouchEvent()返回true)
// 会调用ViewGroup父类的dispatchTouchEvent(),即View.dispatchTouchEvent()
// 因此会执行ViewGroup的onTouch() -> onTouchEvent() -> performClick() -> onClick(),即自己处理该事件,事件不会往下传递
// 具体请参考View事件分发机制中的View.dispatchTouchEvent()
...
}
/**
* 分析1:ViewGroup.onInterceptTouchEvent()
* 作用:是否拦截事件
* 说明:
* a. 返回false:不拦截(默认)
* b. 返回true:拦截,即事件停止往下传递(需手动复写onInterceptTouchEvent()其返回true)
*/
public boolean onInterceptTouchEvent(MotionEvent ev) {
// 默认不拦截
return false;
}
// 回到调用原处
viewGroup就会一级一级的往下传, dispatchTouchEvent()、onTouchEvent() 、onInterceptTouchEvent()通过这几个方法,这里这几张图从网上复制过来的, 感觉没必要画了,勿怪。 其实就比较清晰。
我看ViewGroup里的代码:
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
&& ev.getAction() == MotionEvent.ACTION_DOWN
&& ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
&& isOnScrollbarThumb(ev.getXDispatchLocation(0), ev.getYDispatchLocation(0))) {
return true;
}
return false;
}
判断了一条ev.getAction() == MotionEvent.ACTION_DOWN意思就是,从按下开始就开始传递、
事件是逐级向下传递到最深子view,但是onTouchEvent是从最深子view逐级向上运行的。
【A根布局, B->C的子布局】
* E/touchEvent: MyViewGroupA dispatchTouchEvent
* E/touchEvent: MyViewGroupA onInterceptTouchEvent
* E/touchEvent: MyViewGroupB dispatchTouchEvent
* E/touchEvent: MyViewGroupB onInterceptTouchEvent
* E/touchEvent: MyView dispatchTouchEvent
* E/touchEvent: MyView onInterceptTouchEvent
* E/touchEvent: MyView onTouchEvent
* E/touchEvent: MyViewGroupB onTouchEvent
* E/touchEvent: MyViewGroupA onTouchEvent
我们通过重写onInterceptTouchEvent四拦截ViewGroup的向下传递, 但是 View类里面, 是没有onInterceptTouchEvent方法的。我们也可以重写
onTouchEvent返回true,这个是很便捷的,就是时间传递下去了, 但是onTouchEvent消费了,父布局的onTouchEvent就不消费点击事件了
lic boolean dispatchTouchEvent(MotionEvent ev) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
}
// If the event targets the accessibility focused view and this is it, start
// normal event dispatch. Maybe a descendant is what will handle the click.
if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
ev.setTargetAccessibilityFocus(false);
}
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// Handle an initial down.
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Throw away all previous state when starting a new touch gesture.
// The framework may have dropped the up or cancel event for the previous gesture
// due to an app switch, ANR, or some other state change.
cancelAndClearTouchTargets(ev);
resetTouchState();
}
// Check for interception.
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}
// If intercepted, start normal event dispatch. Also if there is already
// a view that is handling the gesture, do normal event dispatch.
if (intercepted || mFirstTouchTarget != null) {
ev.setTargetAccessibilityFocus(false);
}
// Check for cancelation.
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
// Update list of touch targets for pointer down, if needed.
final boolean isMouseEvent = ev.getSource() == InputDevice.SOURCE_MOUSE;
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0
&& !isMouseEvent;
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
if (!canceled && !intercepted) {
// If the event is targeting accessibility focus we give it to the
// view that has accessibility focus and if it does not handle it
// we clear the flag and dispatch the event to all children as usual.
// We are looking up the accessibility focused host to avoid keeping
// state since these events are very rare.
View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
? findChildWithAccessibilityFocus() : null;

被折叠的 条评论
为什么被折叠?



