1、ActivityThread.handleResumeActivity()解析
ActivityThread.handleResumeActivity()
--> final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
--> r.activity.performResume(r.startsNotResumed, reason);
--> mInstrumentation.callActivityOnResume(this);@activity
//获取到PhoneWindow,PhoneWindow是在attach()中创建的。
--> r.window = r.activity.getWindow();
//获取到DecorView。DecorView是在setContent()中创建的。
--> View decor = r.window.getDecorView();
//ViewManager也是在activity.attach()中创建,wm实际也就是WindowManagerImpl
--> ViewManager wm = a.getWindowManager();
--> WindowManager.LayoutParams l = r.window.getAttributes();
//将DecorView添加到window上的
--> wm.addView(decor, l);
--> mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
mContext.getUserId());@WindowManagerImpl
//ViewRootImpl
--> root = new ViewRootImpl(view.getContext(), display);@WindowManagerGlobal
//将三个参数进行保存
--> mViews.add(view); //DecorView
--> mRoots.add(root); //ViewRootImpl
--> mParams.add(wparams); //WindowManager.LayoutParams
//将DecorView添加到ViewRootImpl中去
--> root.setView(view, wparams, panelParentView, userId);
重要的三个类的作用:WindowManagerImpl、WindowManagerGlobal、ViewRootImpl
WindowManagerImpl:确定 View 属于哪个屏幕,哪个父窗口
WindowManagerGlobal:管理整个进程,所有的窗口信息。每个进程都有一个对应的WindowManagerGlobal
ViewRootImpl:是WindowManagerGlobal的实际操作者。只操作自己的窗口,和wms交互都是ViewRootImpl来操作
2、ViewRootImpl.setView()解析
ViewRootImpl.setView();
//请求遍历
--> requestLayout();
--> scheduleTraversals();
--> mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
--> final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
//绘制view流程
--> performTraversals();
//1、预测量
--> windowSizeMayChange |= measureHierarchy(host, lp, res, desiredWindowWidth, desiredWindowHeight);
//只有在WRAP_CONTENT情况才会进行预测量
--> if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT) {
//父控件给你一个值,设置baseSize
res.getValue(com.android.internal.R.dimen.config_prefDialogWidth, mTmpValue, true);
if (baseSize != 0 && desiredWindowWidth > baseSize) {
...
//第一次进行测量
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) {
//如果父控件给你的值,满意的话,那就预测量结束
goodMeasure = true;
} else {
//如果不满意,就改变baseSize大小
baseSize = (baseSize+desiredWindowWidth)/2;
//然后进行第二次测量
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) {
//满意,就结束
goodMeasure = true;
}
}
}
}
--> if (!goodMeasure) {
//如果还不满意,直接给自己的最大值,
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
//然后第三次测量
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
//如果我给子view的大小,我的父控件不允许,那就还需要再测量
if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) {
//true表示还需要测量
windowSizeMayChange = true;
}
}
//2、布局窗口,了解下即可
--> relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
--> mWindowSession.relayout
//最后还是调用的是WMS的relayoutWindow
--> int res = mService.relayoutWindow@Session
//3、控制树测量
--> performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
--> mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
--> onMeasure(widthMeasureSpec, heightMeasureSpec);
--> setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
//标志位。如果我们重写onMeasure(),没有调用setMeasuredDimension()这个方法,下面会抛一个异常
--> mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
--> 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()");
}
//4、布局
--> performLayout(lp, mWidth, mHeight);
--> host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
--> boolean changed = isLayoutModeOptical(mParent) ? setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
//这里进行赋值。所以getHeight() = mBottom - mTop,要在layout之后获取才有值。
--> mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;
//view没有实现,具体的容器才会实现这个方法。
--> onLayout(changed, l, t, r, b);
//再交由子view去layout
-->child.layout(st.mLeft, st.mTop, st.mRight, st.mBottom);
//5、绘制
--> performDraw();
--> boolean canUseAsync = draw(fullRedrawNeeded);
//这个键盘输入时,被软键盘顶上去的距离
--> scrollToRectOrFocus(null, false);
//下面就是绘制了,有两种绘制方式:硬件加速绘制、软件绘制
--> if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) {
//如果开了硬件加速,并且支持硬件加速
mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
} else {
//软件绘制
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty, surfaceInsets)) {
return false;
}
//滚动画布,就是上面说的被软键盘顶上去的距离
--> canvas.translate(-xoff, -yoff);
--> mView.draw(canvas);
//绘制背景色
--> drawBackground(canvas);
//如果横竖向都没有渐变色
--> if (!verticalEdges && !horizontalEdges) {
// Step 3, draw the content
onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
drawAutofilledHighlight(canvas);
// Overlay is part of the content and draws beneath Foreground
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
// 绘制前景色
onDrawForeground(canvas);
// Step 7, draw the default focus highlight
drawDefaultFocusHighlight(canvas);
return;
}
}
}
}
//将窗口添加到WMS上面 WindowManagerService
--> res = mWindowSession.addToDisplayAsUser()
//事件处理
--> mInputEventReceiver = new WindowInputEventReceiver(inputChannel,Looper.myLooper());
//将ViewRootImpl设置为view的父容器,ViewRootImpl就是控件树的根部
--> view.assignParent(this);
--> if (mParent == null) {
mParent = parent;
} else if (parent == null) {
mParent = null;
} else {
throw new RuntimeException("view " + this + " being added, but"
+ " it already has a parent");
}
view在绘制的时候,需要去管理自己的padding
父容器在分配空间时,给子view分配的大小是子view的宽高+子view设置的margin值。
3、ViewRootImpl 构造方法
// 拿到创建它的线程,MainThread --- 默认
--> mThread = Thread.currentThread();
// 脏区域:收集哪些区域需要修改
--> mDirty = new Rect();
// 窗口的位置和尺寸
--> mmWinFrame = new Rect();
// 保存当前窗口的一些信息
--> mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this, context);
// 参数mWindowSession,相当于是对wms的一个封装,相当于代理
--> mWindowSession = WindowManagerGlobal.getWindowSession()
--> IWindowManager windowManager = getWindowManagerService();
--> sWindowSession = windowManager.openSession(
new IWindowSessionCallback.Stub() {
@Override
public void onAnimatorScaleChanged(float scale) {
ValueAnimator.setDurationScale(scale);
}
});
--> return new Session(this, callback);
4、总结
View绘制流程图.jpglayoutRequested?什么时候是true呢?调用requestLayout()为true;invalidate()走的是false
面试题:
UI 刷新只能在主线程进行吗?
不是,哪个线程创建了ViewRootImpl就在哪个线程创建ui。
原因看代码。
两种刷新方式:
view.requestLayout();
//一层层往上调用,最终调到ViewRootImpl.requestLayout();
--> mParent.requestLayout();@View
--> checkThread();@ViewRootImpl
//如果当前线程不等于mThread,则抛出异常。mThread是在ViewRootImpl的构造方法中赋值。
--> if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
view.invalidate();
--> invalidate(true);
--> invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
//p就是viewParent
--> p.invalidateChild(this, damage);
//一层层往上调用,最终调到ViewRootImpl.requestLayout();
--> parent = parent.invalidateChildInParent(location, dirty);
//同样是需要去检测当前线程
--> checkThread();@ViewRootImpl
如何实现在子线程刷新Ui?
-
在ViewRootImpl 创建之前调用
-
在需要刷新Ui的子线程 创建ViewRootImpl
1.1 AndroidManifest.xml中添加权限<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
1.2 6.0之后的版本上还需要用户主动的允许权限申请。而为了让系统弹出提示用户允许权限申请操作的界面
if (Build.VERSION.SDK_INT >= 23) { if (!canDrawOverlays(this)) { val intent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION) intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivityForResult(intent, 1); } else { childThreadChangeUI() } }
1.3 子线程创建悬浮窗
private fun childThreadChangeUI() { object : Thread() { @RequiresApi(api = Build.VERSION_CODES.R) override fun run() { Looper.prepare() val wm = applicationContext.getSystemService(WINDOW_SERVICE) as WindowManager val view: View = View.inflate(this@Main2Activity, R.layout.item, null) val tv: TextView = view.findViewById(R.id.tv) val params = WindowManager.LayoutParams() params.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY // 设置不拦截焦点 params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL params.width = (60 * resources.displayMetrics.density).toInt() params.height = (60 * resources.displayMetrics.density).toInt() params.gravity = Gravity.LEFT or Gravity.TOP // 且设置坐标系 左上角 params.format = PixelFormat.TRANSPARENT val width = wm.defaultDisplay.width val height = wm.defaultDisplay.height params.y = height / 2 - params.height / 2 wm.addView(view, params) view.setOnTouchListener(object : View.OnTouchListener { private var y = 0 private var x = 0 override fun onTouch(v: View?, event: MotionEvent): Boolean { when (event.action) { MotionEvent.ACTION_DOWN -> { x = event.rawX.toInt() y = event.rawY.toInt() } MotionEvent.ACTION_MOVE -> { val minX = (event.rawX - x).toInt() val minY = (event.rawY - y).toInt() params.x = (width - params.width).coerceAtMost(0.coerceAtLeast(minX + params.x)) params.y = (height - params.height).coerceAtMost(0.coerceAtLeast(minY + params.y)) wm.updateViewLayout(view, params) x = event.rawX.toInt() y = event.rawY.toInt() } MotionEvent.ACTION_UP -> if (params.x > 0 && params.x < width - params.width) { val x = params.x if (x > (width - params.width) / 2) { params.x = width - params.width } else { params.x = 0 } wm.updateViewLayout(view, params) } else if (params.x == 0 || params.x == width - params.width) { Toast.makeText(this@Main2Activity, "被点击了", Toast.LENGTH_SHORT) .show() tv.text = "更改了item的TextView值" } } return true } }) Looper.loop() } }.start() }
网友评论