从ActivityThread.handleResumeActivity()进行阐述
当创建Activity时,调用AMS通过Binder机制调用ApplicationThread对象的方法,进而调用ActivityThread中的handleResumeActivity()方法
handleResumeActivity方法是在TransactionExecutor.java类中performLifecycleSequence()方法调用过来的
TransactionExecutor.java
private void performLifecycleSequence(ActivityClientRecord r, IntArray path,
ClientTransaction transaction) {
// 通过mHelper调用getLifecyclePath返回的path 是 ON_START
final int size = path.size();
for (int i = 0, state; i < size; i++) {
state = path.get(i); // 执行startActivity操作,path其实是保存了ONCREATE、ON_START、ONRESMUME三种状态
if (DEBUG_RESOLVER) {
Slog.d(TAG, tId(transaction) + "Transitioning activity: "
+ getShortActivityName(r.token, mTransactionHandler)
+ " to state: " + getStateName(state));
}
switch (state) {
case ON_CREATE:
mTransactionHandler.handleLaunchActivity(r, mPendingActions,
null /* customIntent */);
break;
case ON_START:
mTransactionHandler.handleStartActivity(r, mPendingActions,
null /* activityOptions */);
break;
case ON_RESUME:
mTransactionHandler.handleResumeActivity(r, false /* finalStateRequest */,
r.isForward, "LIFECYCLER_RESUME_ACTIVITY");
break;
case ON_PAUSE:
mTransactionHandler.handlePauseActivity(r, false /* finished */,
false /* userLeaving */, 0 /* configChanges */, mPendingActions,
"LIFECYCLER_PAUSE_ACTIVITY");
break;
case ON_STOP:
mTransactionHandler.handleStopActivity(r, 0 /* configChanges */,
mPendingActions, false /* finalStateRequest */,
"LIFECYCLER_STOP_ACTIVITY");
break;
case ON_DESTROY:
mTransactionHandler.handleDestroyActivity(r, false /* finishing */,
0 /* configChanges */, false /* getNonConfigInstance */,
"performLifecycleSequence. cycling to:" + path.get(size - 1));
break;
case ON_RESTART:
mTransactionHandler.performRestartActivity(r, false /* start */);
break;
default:
throw new IllegalArgumentException("Unexpected lifecycle state: " + state);
}
}
}
ActivityThread.java
@Override
public void handleResumeActivity(ActivityClientRecord r, boolean finalStateRequest,
boolean isForward, String reason) {
...
// 代码1
// 执行onResume()流程
if (!performResumeActivity(r, finalStateRequest, reason)) {
return;
}
...
if (r.window == null && !a.mFinished && willBeVisible) {
// 拿到window,也就是PhoenWindow,PhoneWindow的创建是在Activity类的attach()方法中
// mWindow = new PhoneWindow(this, window, activityConfigCallback);
r.window = r.activity.getWindow();
// 拿到DecorView
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
// 获得WindowManager,WindowManager也是在Activity类的attach()方法中进行设置
/* mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
*/
ViewManager wm = a.getWindowManager();
// 拿到window的LayoutParams参数
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;
r.mPreserveWindow = false;
// Normally the ViewRoot sets up callbacks with the Activity
// in addView->ViewRootImpl#setView. If we are instead reusing
// the decor view we have to notify the view root that the
// callbacks may have changed.
ViewRootImpl impl = decor.getViewRootImpl();
if (impl != null) {
impl.notifyChildRebuilt();
}
}
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
// 代码2
// 执行WindowManger的addView(),WindowManager实现ViewManager接口,
// WindowManager是一个接口,具体实现是WindowManagerImpl
wm.addView(decor, l);
} else {
// The activity will get a callback for this {@link LayoutParams} change
// earlier. However, at that time the decor will not be set (this is set
// in this method), so no action will be taken. This call ensures the
// callback occurs with the decor set.
a.onWindowAttributesChanged(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;
}
}
从这两处代码我们就可以知道,如果onResume()流程还没执行完,获取控件的getMeasureWidth()和getMeasureHeight()值都是为0。
这里有两个地方需要注意下:
1:代码1 performResumeActivity(...)
这行代码就是执行onResume()流程
–> performResumeActivity
--> r.activity.performResume
--> mInstrumentation.callActivityOnResume
ActivityThread.java
@VisibleForTesting
public boolean performResumeActivity(ActivityClientRecord r, boolean finalStateRequest,
String reason) {
...
// 直接跳转到Activity的performResume()
r.activity.performResume(r.startsNotResumed, reason);
...
}
Activity.java
final void performResume(boolean followedByPause, String reason) {
...
// 执行Instrumentation的callActivityOnResume()方法
mInstrumentation.callActivityOnResume(this);
...
}
Instrumentation.java
public void callActivityOnResume(Activity activity) {
activity.mResumed = true;
// 启动应用的onResume()方法
activity.onResume();
if (mActivityMonitors != null) {
synchronized (mSync) {
final int N = mActivityMonitors.size();
for (int i=0; i<N; i++) {
final ActivityMonitor am = mActivityMonitors.get(i);
am.match(activity, activity, activity.getIntent());
}
}
}
}
2:代码2 wm.addView(decor, l);
其中,wm指WindowManager,decor指DecorView
,WindowManager是一个接口,具体实现是WindowManagerImpl,从而跳转到WindowManagerImpl的addView()
方法。
–> wm.addView(decor, l); (WindowManagerImpl.java)
--> WindowManagerGlobal.addView
--> root = new ViewRootImpl(view.getContext(), display);
-->mViews.add(view); // DecorView
mRoots.add(root); // ViewRootImpl
mParams.add(wparams); //WindowManager.LayoutParams
--> root.setView(view, wparams, panelParentView, userId);
WindowManagerImpl.java
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyTokens(params);
// WindowManagerImpl的实现就相当于一个中转,都是跳转到WindowManagerGlobal类中实现具体功能
mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
mContext.getUserId());
}
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;
...
// 判定view是否已经添加,如果已经添加是不允许再次添加的,不然会报错
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.
}
...
// 创建ViewRootImpl对象
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
// 保存view、root、wparams三个参数
mViews.add(view); // view指DecorView
mRoots.add(root); // root指ViewRootImpl
mParams.add(wparams); // wparams指WindowManager.LayoutParams参数
try {
// 将DecorView保存到ViewRootImpl中
root.setView(view, wparams, panelParentView, userId);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
...
}
上面的流程涉及到三个类:WindowManagerImpl、WindowManagerGlobal、ViewRootImpl
-
WindowManagerImpl主要功能:确定View属于哪个屏幕、哪个父窗口(
确定窗口
) -
WindowManagerGlobal主要功能:管理整个进程 所有的窗口信息,即主要包含view(DecorView)、root(ViewRootImpl)、wparams(WindowManager.LayoutParams)(
管理信息
)
每个进程都对应一个WindowManagerGlobal,也就说每个app进程都对应有自己的WindowManagerGlobal -
ViewRootImpl主要功能:WindowManagerGlobal实际操作者,操作自己的窗口(做大量的事情,
真正的执行者
)
ViewRootImpl存在多个,一个窗口对应一个ViewRootImpl
实际功能:
① View树的树根并管理View树
② 触发View的测量、布局和绘制
③ 输入响应的中转站
④ 负责与WMS进行进程间通信(通过binder)
最后执行到 root.setView(view, wparams, panelParentView, userId);,其中root是ViewRootImpl,view是,这条语句其实就是将DecorView保存到ViewRootImpl中
,从而执行到ViewRootImpl类的setView()方法。
ViewRootImpl.setView
--> requestLayout(); // 请求遍历
--> scheduleTraversals
--> mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
--> doTraversal
--> performTraversals(); // 绘制View
--> res = mWindowSession.addToDisplayAsUser // 将窗口添加到WMS上面 WindowManagerService
--> 事件处理
--> view.assignParent(this); // getParent ViewRootImpl
ViewRootImpl.java
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
int userId) {
...
// 1. 请求遍历
requestLayout();
...
// 2. 将窗口添加到WMS上面
res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), userId,
mInsetsController.getRequestedVisibility(), inputChannel, mTempInsets,
mTempControls);
...
// 3. 创建接收事件,进行事件处理
mInputEventReceiver = new WindowInputEventReceiver(inputChannel,
Looper.myLooper());
...
// 4. 将view添加到ViewRootImpl父容器中,循环通过getparent()向上遍历树就可以获取到ViewRootImpl
// 将ViewRootImpl作为DecorView的parent
view.assignParent(this);
...
}
这个方法里面主要做了这四件事,但是最主要还是requestLayout()
流程。
这里打断分析下ViewRootImpl构造函数,其中有几个参数比较重要:
ViewRootImpl.java
public ViewRootImpl(@UiContext Context context, Display display, IWindowSession session,
boolean useSfChoreographer) {
mContext = context;
mWindowSession = session;
mDisplay = display;
mBasePackageName = context.getBasePackageName();
mThread = Thread.currentThread(); // 拿到创建它的线程,默认是MainThread,这个变量UI刷新的时候会用到比较
mLocation = new WindowLeaked(null);
mLocation.fillInStackTrace();
mWidth = -1;
mHeight = -1;
mDirty = new Rect(); // 脏区域,收集哪些地方需要进行修改,比如textView需要更改它的文字,那这块区域就作为脏区域
mTempRect = new Rect();
mVisRect = new Rect();
mWinFrame = new Rect();
mWindow = new W(this);
mLeashToken = new Binder();
mTargetSdkVersion = context.getApplicationInfo().targetSdkVersion;
mViewVisibility = View.GONE;
mTransparentRegion = new Region();
mPreviousTransparentRegion = new Region();
mFirst = true; // true for the first time the view is added
mPerformContentCapture = true; // also true for the first time the view is added
mAdded = false;
// 保存当前窗口的一些信息,mWindowSession就是一个session对象
mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,
context);
...
}
回到requestLayout()
流程继续分析:
ViewRootImpl.java
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
//检查线程,哪个线程创建了ViewRootImpl,哪个线程进行更新UI
checkThread();
mLayoutRequested = true;
// 执行该语句进行后续流程
scheduleTraversals();
}
}
checkThread()
:这个在进行UI更新的时候时常会进行检查,也就是常说的只能在主线程上更新UI,其实准确的说是谁创建了ViewRootImpl,就在那个线程中更新UI。
ViewRootImpl.java
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
ViewRootImpl会调用scheduleTraversals准备重绘,但是,重绘一般不会立即执行,而是往Choreography.CALLBACK_TRAVERSAL队列中添加一个mTraversalRunnable,同时申请VSYNC,这个mTraversalRunnable要一直等到申请的VSYNC到来后才会被执行。
ViewRootImpl.java
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
// 这里面其实也是通过handler发送消息,最终执行mTraversalRunnable的run()方法
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null)
:执行这条语句其实也是通过handler发送消息,最终执行mTraversalRunnable的run()方法。
ViewRootImpl.java
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
final class TraversalRunnable implements Runnable {
@Override
public void run() {
// 执行该语句进行后续流程
doTraversal();
}
}
ViewRootImpl.java
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
// 执行该语句,进行后续的View绘制流程
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
performTraversals@ViewRootImpl.java
-->windowSizeMayChange |= measureHierarchy(); // 预测量
-->relayoutResult = relayoutWindow(params, viewVisibility, insetsPending); // 布局窗口
-->performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); // 控件树测量
--> performLayout(lp, mWidth, mHeight); // 布局
--> performDraw(); // 绘制
绘制流程:
ViewRootImpl.java
private void performTraversals() {
...
// Ask host how big it wants to be
// 1.首先进行预测量,最多会执行3次测量
windowSizeMayChange |= measureHierarchy(host, lp, res,
desiredWindowWidth, desiredWindowHeight);
...
// 2.布局窗口,具体实现在WMS中
relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
...
// 3.控件树测量
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
// 4.执行layout布局
performLayout(lp, mWidth, mHeight);
...
// 5.执行绘制
performDraw();
...
}
performTraversals()
方法主要包含5个流程:
- 预测量
- 布局窗口–》这一流程具体实现在WMS中执行
- 执行performMeasure()进行控件树测量
- 执行performLayout()进行layout布局
- 执行performDraw()进行绘制
1. 预测量流程:执行measureHierarchy()
ViewRootImpl.java
// 预测量其实就是子view与父控件在协商大小应该多大,这个控件的协商测量
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 (DEBUG_ORIENTATION || DEBUG_LAYOUT) Log.v(mTag,
"Measuring " + host + " in display " + desiredWindowWidth
+ "x" + desiredWindowHeight + "...");
boolean goodMeasure = false;
//只是设置了WRAP_CONTENT才需要进行协商
if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT) {
// On large screens, we don't want to allow dialogs to just
// stretch to fill the entire width of the screen to display
// one line of text. First try doing the layout at a smaller
// size to see if it will fit.
final DisplayMetrics packageMetrics = res.getDisplayMetrics();
// 父控件首先会给子view一个值config_prefDialogWidth(320dp),并设置给mTmpValue
res.getValue(com.android.internal.R.dimen.config_prefDialogWidth, mTmpValue, true);
int baseSize = 0;
if (mTmpValue.type == TypedValue.TYPE_DIMENSION) {
// 将初始值赋值给baseSize
baseSize = (int)mTmpValue.getDimension(packageMetrics);
}
if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": baseSize=" + baseSize
+ ", desiredWindowWidth=" + desiredWindowWidth);
if (baseSize != 0 && desiredWindowWidth > baseSize) {
childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
// 首先进行第一次测量
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": measured ("
+ host.getMeasuredWidth() + "," + host.getMeasuredHeight()
+ ") from width spec: " + MeasureSpec.toString(childWidthMeasureSpec)
+ " and height spec: " + MeasureSpec.toString(childHeightMeasureSpec));
// 获取一个状态值
if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) {
goodMeasure = true;
} else {
// Didn't fit in that size... try expanding a bit.
// 如果不满意,更改baseSize的大小
baseSize = (baseSize+desiredWindowWidth)/2;
if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": next baseSize="
+ baseSize);
childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
// 再次进行测量(第二次测量)
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": measured ("
+ host.getMeasuredWidth() + "," + host.getMeasuredHeight() + ")");
if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) {
if (DEBUG_DIALOG) Log.v(mTag, "Good!");
goodMeasure = true;
}
}
}
}
if (!goodMeasure) {
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
// 如果还不满意,就直接将窗口的宽高(自己的最大值)全部给子view,进行第三次测量
// 这里面的最大值指父控件的大小,父控件的大小还是要看它的父控件的设置是否能提供这么大,
// 所以后续还需要进行一次测量
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
// 如果宽跟父控件给的宽不一样或者高跟父控件给的高不一样
if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) {
windowSizeMayChange = true; //windowSizeMayChange = true表示测量无效,还需要进行一次测量
}
}
if (DBG) {
System.out.println("======================================");
System.out.println("performTraversals -- after measure");
host.debug();
}
return windowSizeMayChange;
}
上面代码可以简单理解为:(预测量流程中最多执行三次测量)
- 首先设置一个值,进行第一次测量,MEASURED_STATE_TOO_SMALL
- 获取一个状态值,并且与View.MEASURED_STATE_TOO_SMALL进行相与,看是否满足要求
- 如果不满足,改变大小 baseSize = (baseSize+desiredWindowWidth)/2;
- 进行第二次测量
- 如果还不满意,直接给自己的最大值,然后第三次测量 – 不确定是否合适,这里的最大值指父控件的大小,父控件的大小还是要看它的父控件的设置是否能提供这么大,所以在performTraversals()方法的预测量后续流程中还需要进行一次performMeasure()测量
如果 windowSizeMayChange = true; --》 表示还需要测量
2. 执行performMeasure()进行控件树测量
performMeasure
--> mView.measure
--> onMeasure --> 如果重写onMeasure()方法一定要调用setMeasuredDimension();
ViewRootImpl.java
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
if (mView == null) {
return;
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
// 直接调用View的measure()方法
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
View.java
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
...
// 执行到onMeasure()方法
onMeasure(widthMeasureSpec, heightMeasureSpec);
...
// flag not set, setMeasuredDimension() was not invoked, we raise
// an exception to warn the developer
// 重写了onMeasure()方法之后,一定要执行setMeasuredDimension(),不然会报错
if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
throw new IllegalStateException("View with id " + getId() + ": "
+ getClass().getName() + "#onMeasure() did not set the"
+ " measured dimension by calling"
+ " setMeasuredDimension()");
}
...
}
View.java
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 执行setMeasuredDimension()继续后续流程
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
3.执行performLayout()进行layout布局
performLayout
--> host.layout
--> onLayout(changed, l, t, r, b);
--> child.layout
ViewRootImpl.java
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
...
// host就是view
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
...
}
View.java
public void layout(int l, int t, int r, int b) {
...
// 执行onLaout()
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) {
// 对onLayoutChange 进行监听,对布局改变的监听
listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
}
}
...
}
View.java
// view是一个控件,不是一个容器,所以去一个具体实现容器查看具体的代码,比如RelativeLayout
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
RelativeLayout.java
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// The layout has actually already been performed and the positions
// cached. Apply the cached values to the children.
final int count = getChildCount();
// 遍历子view
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
if (child.getVisibility() != GONE) {
RelativeLayout.LayoutParams st =
(RelativeLayout.LayoutParams) child.getLayoutParams();
// 对子view进行layout布局,这里面的左、上、右、下是针对父容器(控件)
child.layout(st.mLeft, st.mTop, st.mRight, st.mBottom);
}
}
}
4. 执行performDraw()进行绘制
performDraw@ViewRootImpl.java
--> draw@ViewRootImpl.java
-->scrollToRectOrFocus 计算滚动,防止核心的控件被遮住,比如输入的时候弹出输入框
--> 硬件加速绘制 mAttachInfo.mThreadedRenderer.draw() 效果更好
--> 软件绘制 drawSoftware()
--> mView.draw(canvas);
-> onDraw(canvas);
--> dispatchDraw(canvas);
ViewRootImpl.java
private void performDraw() {
...
// 调用draw()方法
boolean canUseAsync = draw(fullRedrawNeeded);
...
}
ViewRootImpl.java
private boolean draw(boolean fullRedrawNeeded) {
...
// 计算滚动,防止核心的控件被遮住,比如输入的时候弹出输入框,整个view就会向上移动,就是这里进行了处理
scrollToRectOrFocus(null, false);
...
// The app owns the surface, we won't draw.
// 在绘制之前进行脏数据的置空,置空的目的是为了有改动的情况下还可以进行添加
// 脏区域的理解:如果该控件有改变会被标记为脏区域,重绘的时候就会把这块进行重绘,不用重绘整个布局
dirty.setEmpty();
...
if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty || mNextDrawUseBlastSync) {
// 这里绘制分两种请情况,一种是硬件加速绘制(效果更好些),一种是软件绘制
// mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()
if (isHardwareEnabled()) {
...
// 进行硬件加速绘制
mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
} else {
// 进行软件绘制drawSoftware()
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,
scalingRequired, dirty, surfaceInsets)) {
return false;
}
}
}
下面以软件绘制drawSoftware()
进行流程分析
ViewRootImpl.java
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty, Rect surfaceInsets) {
...
// 首先拿到canvas
canvas = mSurface.lockCanvas(dirty);
...
// 进行平移
canvas.translate(-xoff, -yoff);
...
// 最终调用view 的draw()
mView.draw(canvas);
...
}
最终执行到View的draw()
方法
View.java
public void draw(Canvas canvas) {
final int privateFlags = mPrivateFlags;
mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
/*
* Draw traversal performs several drawing steps which must be executed
* in the appropriate order:
*
* 1. Draw the background
* 2. If necessary, save the canvas' layers to prepare for fading
* 3. Draw view's content
* 4. Draw children
* 5. If necessary, draw the fading edges and restore layers
* 6. Draw decorations (scrollbars for instance)
* 7. If necessary, draw the default focus highlight
*/
// Step 1, draw the background, if needed
int saveCount;
// 绘制background
drawBackground(canvas);
// skip step 2 & 5 if possible (common case)
final int viewFlags = mViewFlags;
boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
// 判定边界是否有渐变色
if (!verticalEdges && !horizontalEdges) {
// Step 3, draw the content
// 绘制自己
onDraw(canvas);
// Step 4, draw the children
// 绘制子view
dispatchDraw(canvas);
drawAutofilledHighlight(canvas);
// Overlay is part of the content and draws beneath Foreground
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
// Step 6, draw decorations (foreground, scrollbars)
// 绘制前景色和上下滚动条
onDrawForeground(canvas);
// Step 7, draw the default focus highlight
drawDefaultFocusHighlight(canvas);
if (isShowingLayoutBounds()) {
debugDrawFocus(canvas);
}
// we're done...
return;
}
/*
* Here we do the full fledged routine...
* (this is an uncommon case where speed matters less,
* this is why we repeat some of the tests that have been
* done above)
*/
boolean drawTop = false;
boolean drawBottom = false;
boolean drawLeft = false;
boolean drawRight = false;
float topFadeStrength = 0.0f;
float bottomFadeStrength = 0.0f;
float leftFadeStrength = 0.0f;
float rightFadeStrength = 0.0f;
// Step 2, save the canvas' layers
int paddingLeft = mPaddingLeft;
final boolean offsetRequired = isPaddingOffsetRequired();
if (offsetRequired) {
paddingLeft += getLeftPaddingOffset();
}
int left = mScrollX + paddingLeft;
int right = left + mRight - mLeft - mPaddingRight - paddingLeft;
int top = mScrollY + getFadeTop(offsetRequired);
int bottom = top + getFadeHeight(offsetRequired);
if (offsetRequired) {
right += getRightPaddingOffset();
bottom += getBottomPaddingOffset();
}
final ScrollabilityCache scrollabilityCache = mScrollCache;
final float fadeHeight = scrollabilityCache.fadingEdgeLength;
int length = (int) fadeHeight;
// clip the fade length if top and bottom fades overlap
// overlapping fades produce odd-looking artifacts
if (verticalEdges && (top + length > bottom - length)) {
length = (bottom - top) / 2;
}
// also clip horizontal fades if necessary
if (horizontalEdges && (left + length > right - length)) {
length = (right - left) / 2;
}
if (verticalEdges) {
topFadeStrength = Math.max(0.0f, Math.min(1.0f, getTopFadingEdgeStrength()));
drawTop = topFadeStrength * fadeHeight > 1.0f;
bottomFadeStrength = Math.max(0.0f, Math.min(1.0f, getBottomFadingEdgeStrength()));
drawBottom = bottomFadeStrength * fadeHeight > 1.0f;
}
if (horizontalEdges) {
leftFadeStrength = Math.max(0.0f, Math.min(1.0f, getLeftFadingEdgeStrength()));
drawLeft = leftFadeStrength * fadeHeight > 1.0f;
rightFadeStrength = Math.max(0.0f, Math.min(1.0f, getRightFadingEdgeStrength()));
drawRight = rightFadeStrength * fadeHeight > 1.0f;
}
saveCount = canvas.getSaveCount();
int topSaveCount = -1;
int bottomSaveCount = -1;
int leftSaveCount = -1;
int rightSaveCount = -1;
int solidColor = getSolidColor();
if (solidColor == 0) {
if (drawTop) {
topSaveCount = canvas.saveUnclippedLayer(left, top, right, top + length);
}
if (drawBottom) {
bottomSaveCount = canvas.saveUnclippedLayer(left, bottom - length, right, bottom);
}
if (drawLeft) {
leftSaveCount = canvas.saveUnclippedLayer(left, top, left + length, bottom);
}
if (drawRight) {
rightSaveCount = canvas.saveUnclippedLayer(right - length, top, right, bottom);
}
} else {
scrollabilityCache.setFadeColor(solidColor);
}
// Step 3, draw the content
onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
// Step 5, draw the fade effect and restore layers
final Paint p = scrollabilityCache.paint;
final Matrix matrix = scrollabilityCache.matrix;
final Shader fade = scrollabilityCache.shader;
// must be restored in the reverse order that they were saved
if (drawRight) {
matrix.setScale(1, fadeHeight * rightFadeStrength);
matrix.postRotate(90);
matrix.postTranslate(right, top);
fade.setLocalMatrix(matrix);
p.setShader(fade);
if (solidColor == 0) {
canvas.restoreUnclippedLayer(rightSaveCount, p);
} else {
canvas.drawRect(right - length, top, right, bottom, p);
}
}
if (drawLeft) {
matrix.setScale(1, fadeHeight * leftFadeStrength);
matrix.postRotate(-90);
matrix.postTranslate(left, top);
fade.setLocalMatrix(matrix);
p.setShader(fade);
if (solidColor == 0) {
canvas.restoreUnclippedLayer(leftSaveCount, p);
} else {
canvas.drawRect(left, top, left + length, bottom, p);
}
}
if (drawBottom) {
matrix.setScale(1, fadeHeight * bottomFadeStrength);
matrix.postRotate(180);
matrix.postTranslate(left, bottom);
fade.setLocalMatrix(matrix);
p.setShader(fade);
if (solidColor == 0) {
canvas.restoreUnclippedLayer(bottomSaveCount, p);
} else {
canvas.drawRect(left, bottom - length, right, bottom, p);
}
}
if (drawTop) {
matrix.setScale(1, fadeHeight * topFadeStrength);
matrix.postTranslate(left, top);
fade.setLocalMatrix(matrix);
p.setShader(fade);
if (solidColor == 0) {
canvas.restoreUnclippedLayer(topSaveCount, p);
} else {
canvas.drawRect(left, top, right, top + length, p);
}
}
canvas.restoreToCount(saveCount);
drawAutofilledHighlight(canvas);
// Overlay is part of the content and draws beneath Foreground
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
// Step 7, draw the default focus highlight
drawDefaultFocusHighlight(canvas);
if (isShowingLayoutBounds()) {
debugDrawFocus(canvas);
}
}
自定义View的最基本的三个方法分别是:onMeasure()、onLayout()、onDraw();View在Activity中显示出来,要经历测量、布局和绘制三个步骤,分别对应三个动作:measure、layout和draw。
- 测量:onMeasure()决定View的大小
- 布局:onLayout()决定View在ViewGroup中的位置
- 绘制:onDraw()决定绘制这个View
自定义控件分类
- 自定义View:只需要重写onMeasure()和onDraw()
- 自定义ViewGroup:则只需要重写onMeasure()和onLayout()
自定义view的注意事项
- 让View支持wrap_content属性
直接继承View或ViewGroup的控件,如果不在onMeasure中做处理,当控件设置wrap_content属性时无法达到预期效果。wrap_content属性会失效。 - 让View支持padding属性
直接继承View的控件,如果不处理padding属性,则padding会失效。如果继承ViewGroup的控件,还需要处理子元素的margin属性。 - 为了让控件使用更方便,尽量添加自定义属性。
- 如果View需要响应用户touch事件,需要处理好滑动冲突。
- 尽量不要在View中使用Handler,可以用post方法代替。
- 如果View中有子线程或者动画,要在onDetachedFromWindow中及时停止。
- 在onDraw方法中尽量不要创建临时对象,不要做任何耗时的操作,不要执行大数据量
Android view的刷新有三个方式:
-
(1) invalidate();
只会触发执行onDraw方法,只会改变绘制里面的内容,条目的绘制 -
(2) postInvalidate();
只会触发执行onDraw方法,但是可以在子线程中刷新 -
(3) requestLayout();
view的布局参数改变之后刷新,比如view的宽度和高度都修改了,只能通过requestLayout()方法刷新