Android 控件系统

Posted by Young Ken on 2017-06-26

控件系统

控件就是继承自view的控件和WidowManager,代表Button,ViewGroup等等。

深入理解WindowManager

让用更简单的把控件添加到窗口上。

1
2
3
4
5
package android.view;
public interface WindowManager extends ViewManager {
}

WindowManager是个接口,继承ViewManager。

1
2
3
4
5
6
7
8
9
package android.view;
public interface ViewManager {
void addView(View var1, LayoutParams var2);
void updateViewLayout(View var1, LayoutParams var2);
void removeView(View var1);
}
WindowManagerImpl addView分析

WindowManager是接口,WindowManagerImpl实现了这个接口。

1
2
3
4
5
6
7
8
9
10
11
package android.view;
public 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.getDisplay(), mParentWindow);
}
}

调用了applyDefaultToken方法和mGlobal.addView(),applyDefaultToken比较简单,主要是看mGlobal.addView(),从代码可以看出来,addView实际交给了mGlobal去做。

WindowManagerGlobal addView分析
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package android.view;
public final class WindowManagerGlobal {
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
//如果当期的窗口需要被添加为另一个窗口的附属窗口。
if (parentWindow != null) {
parentWindow.adjustLayoutParamsForSubWindow(wparams);
}
ViewRootImpl root;
View panelParentView = null;
//创建ViewRootImpl对象
root = new ViewRootImpl(view.getContext(), display);
//添加到窗口,布局参数中
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
try {
//将作为窗口的控件设置给ViewRootImpl
root.setView(view, wparams, panelParentView);
}
}
}

新建一个ViewRootImpl,控件的布局参数保存到自己的三个List中,控件被ViewRootImpl进行托管,从而完成控件的添加。

WindowManagerGlobal updateViewLayout()分析
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package android.view;
public final class WindowManagerGlobal {
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
//参数布局保存到控件中
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
view.setLayoutParams(wparams);
synchronized (mLock) {
int index = findViewLocked(view, true);
ViewRootImpl root = mRoots.get(index);
mParams.remove(index);
mParams.add(index, wparams);
root.setLayoutParams(wparams, false);
}
}
}

调用 root.setLayoutParams(wparams, false)更新布局生效

深入理解ViewRootImpl

ViewRootImpl实现了ViewParent接口,控件的整个根部,控件的测量,布局,绘制和事件的派发都是ViewRootImpl的工作。

构造函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package android.view;
public final class ViewRootImpl implements ViewParent {
public ViewRootImpl(Context context, Display display) {
//这个是和wms通讯的
mWindowSession = WindowManagerGlobal.getWindowSession();
//得到当前线程
mThread = Thread.currentThread();
/*mDirty是无效区域,无效区域是指数据或者状态发生改变的时候需要重新绘制的区域。当应用程序修改 TextView文字的时候,那么view.invalidate从新绘制无效区域。
*/
mDirty = new Rect();
mTempRect = new Rect();
mVisRect = new Rect();
//当前的窗口和尺寸
mWinFrame = new Rect();
//存储当前窗口有用的信息
mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this);
mFallbackEventHandler = new PhoneFallbackEventHandler(context);
mChoreographer = Choreographer.getInstance();
}
}

构造函数比较长,初始化了一数据。

ViewRootImpl setView()分析
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package android.view;
public final class ViewRootImpl implements ViewParent {
public void setView(View view,
WindowManager.LayoutParams attrs, View panelParentView) {
//把attrs拷贝
mWindowAttributes.copyFrom(attrs);
//经验告诉我们这很重要,下面重点分析
requestLayout();
//初始化inputChannel
if ((mWindowAttributes.inputFeatures
& WindowManager.LayoutParams.
INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
mInputChannel = new InputChannel();
}
try {
//将窗口添加到wms中,mWindow被添加到Display中
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mInputChannel);
}
if(mInputChannel != null) {
//创建WindowInputEventReceiver接收输入事件
mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,
Looper.myLooper());
}
//ViewRootImple 为Veiw的parent
view.assignParent(this);
}
}

这个是WindowManagerGlobal的addView()中创建的ViewRootImpl调用的setView(),这个方法的关键是requestLayout()。

ViewRootImpl requestLayout()分析
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package android.view;
public final class ViewRootImpl implements ViewParent {
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
scheduleTraversals();
}
}
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
}
}
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
//最终调用这个方法,这个方法非常复杂
performTraversals();
}
}
}

上面可以看出来,经过一系列的调用,最后调用到了performTraversals()。这个方法是分析的关键,这个方法是每个View真正绘制开始。

performTraversals分析
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
private void performTraversals() {
//声明重要变量
int desiredWindowWidth;
int desiredWindowHeight;
//被最新测量的窗口
Rect frame = mWinFrame;
//第一次被测量
if (mFirst) {
//第一次采用了窗体的原始尺寸
desiredWindowWidth = size.x;
desiredWindowHeight = size.y;
} else {
//多次后,采用了最新的尺寸
desiredWindowWidth = frame.width();
desiredWindowHeight = frame.height();
}
//mAttachInfo.mHandler放入到RunQueue中,这个就是可以执行view.post的关键
getRunQueue().executeActions(mAttachInfo.mHandler);
if(layoutRequested) {
//进行预测测量
windowSizeMayChange |= measureHierarchy(host, lp, res,
desiredWindowWidth, desiredWindowHeight);
}
}

从上面可以看出,第一调用的时候,采用最大的尺寸,当WRAP_CONTENT的时候使用最大的尺寸,其他情况使用最新的尺寸。最后通过measureHierarchy进行测量。

measureHierarchy分析

协商解决测量问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp,
final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) {
int childWidthMeasureSpec;
int childHeightMeasureSpec;
boolean windowSizeMayChange = false;
//第一次协商
if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT) {
//宽度限定被保存
if (mTmpValue.type == TypedValue.TYPE_DIMENSION) {
baseSize = (int)mTmpValue.getDimension(packageMetrics);
}
if (baseSize != 0 && desiredWindowWidth > baseSize) {
//第一被测量
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) {
//协商完成
goodMeasure = true;
} else {
//第二次协商
baseSize = (baseSize+desiredWindowWidth)/2;
//第二次测量
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}
//最终测量
if (!goodMeasure) {
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight(){
windowSizeMayChange = true;
}
}
}

对窗体是match_parent不需要进行测量。最后看 performMeasure

1
2
3
4
5
6
7
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
try {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
}
}

直接调用了mView的测量方法,在进行追溯查看view.measure

view.measure分析
1
2
3
4
5
6
7
8
9
10
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
if (forceLayout || needsLayout) {
if (cacheIndex < 0 || sIgnoreMeasureCache) {
//这是一个非常常用的方法,每个View都要重载这个方法
onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
}

主要是调用onMeasure

1
2
3
4
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
1
2
3
4
5
6
7
8
9
10
11
12
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
boolean optical = isLayoutModeOptical(this);
if (optical != isLayoutModeOptical(mParent)) {
Insets insets = getOpticalInsets();
int opticalWidth = insets.left + insets.right;
int opticalHeight = insets.top + insets.bottom;
measuredWidth += optical ? opticalWidth : -opticalWidth;
measuredHeight += optical ? opticalHeight : -opticalHeight;
}
setMeasuredDimensionRaw(measuredWidth, measuredHeight);
}
1
2
3
4
5
6
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}

保存了两个测量结果,mPrivateFlags从新赋值。

最后回到oerformTraversals上继续分析

1
2
3
4
5
6
7
8
9
10
11
12
13
private void performTraversals() {
if (layoutRequested) {
//控件任何一个执行了requestLayout()都会从新便利一次
mLayoutRequested = false;
}
//确定是否要改变窗体
boolean windowShouldResize = layoutRequested && windowSizeMayChange
&& ((mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight())
|| (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT &&
frame.width() < desiredWindowWidth && frame.width() != mWidth)
|| (lp.height == ViewGroup.LayoutParams.WRAP_CONTENT &&
frame.height() < desiredWindowHeight && frame.height() != mHeight));
}

布局和最终测量

1
2
3
4
5
6
7
8
private void performTraversals() {
//布局窗口的5个条件
if (mFirst || windowShouldResize || insetsChanged ||
viewVisibilityChanged || params != null || mForceNextWindowRelayout) {
}else {
maybeHandleWindowMove(frame);
}
}
  1. mFirst,第一次调用,窗口添加到wms中,没有进行布局。
  2. windowShouldResize,wms单方面改变了窗口的尺寸和测量结果又差异。windowShouldResize为true下面的其中一个。
    • 测量结果也ViewRootImpl的当前保存的窗体有差异
    • 悬浮窗体和测量结果和最新的尺寸有差异
  3. insetsChanged,wms单方改变了ContentInsets。一般发生在输入法弹出和关闭的时候。
1
2
3
4
5
6
private void performTraversals() {
try {
//调用布局
relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
}
}
relayoutWindow分析

有是一个硬骨头,我们从控件被添加,到测量,再到布局,最后就剩下绘制了。

1
2
3
4
5
6
7
8
9
10
11
12
13
private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,
boolean insetsPending) throws RemoteException {
int relayoutResult = mWindowSession.relayout(
mWindow, mSeq, params,
(int) (mView.getMeasuredWidth() * appScale + 0.5f),
(int) (mView.getMeasuredHeight() * appScale + 0.5f),
viewVisibility, insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0,
mWinFrame, mPendingOverscanInsets, mPendingContentInsets, mPendingVisibleInsets,
mPendingStableInsets, mPendingOutsets, mPendingBackDropFrame, mPendingConfiguration,
mSurface);
return relayoutResult;
}

布局控件操作

控件进行布局,是根据测量完成的控件实际尺寸和位置。

1
2
3
4
5
6
private void performTraversals() {
final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
if (didLayout) {
performLayout(lp, mWidth, mHeight);
}
}

只要layoutRequested为true及调用过requestLayout()方法就行。

1
2
3
4
5
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
final View host = mView;
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
}

最终调用的是view.layout

1
2
3
4
5
6
7
8
9
10
11
12
13
public void layout(int l, int t, int r, int b) {
onLayout(changed, l, t, r, b);
//通知布局变化的监听着
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnLayoutChangeListeners != null) {
ArrayList<OnLayoutChangeListener> listenersCopy =
(ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
int numListeners = listenersCopy.size();
for (int i = 0; i < numListeners; ++i) {
listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
}
}
}

调用onLayout。

绘制

控件的大小确定了,位置也确定了,剩下的就是绘制了。

1
2
3
4
5
6
private void performTraversals() {
boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;
if (!cancelDraw && !newSurface) {
performDraw();
}
}

view可见自然要绘制。调用performDraw()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
private void performDraw() {
try {
draw(fullRedrawNeeded);
}
}
private void draw(boolean fullRedrawNeeded) {
Surface surface = mSurface;
scrollToRectOrFocus(null, false);
//硬件渲染
if (mAttachInfo.mHardwareRenderer != null && mAttachInfo.mHardwareRenderer.isEnabled()) {
mAttachInfo.mHardwareRenderer.draw(mView, mAttachInfo, this);
}
//软件渲染
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
return;
}
//正在播放动画,安排下次重新绘制
if (animating) {
mFullRedrawNeeded = true;
scheduleTraversals();
}
}
软件从新绘制原理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty) {
final Canvas canvas;
try {
//通过Surface得到Canvas
canvas = mSurface.lockCanvas(dirty);
}
try {
dirty.setEmpty();
try {
//滚动坐标的变换
canvas.translate(-xoff, -yoff);
//通过draw绘制整个控件树
mView.draw(canvas);
} finally {
try {
//显示绘制后的内容
surface.unlockCanvasAndPost(canvas);
}
}
}
View.draw()分析
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void draw(Canvas canvas) {
//绘制背景
if (!dirtyOpaque) {
drawBackground(canvas);
}
if (!verticalEdges && !horizontalEdges) {
//绘制自己
if (!dirtyOpaque) onDraw(canvas);
}
//绘制子控件
dispatchDraw(canvas);
//绘制装饰(前景,滚动条)
onDrawForeground(canvas);
}
dispatchDraw分析

总结

一张图搞定