分析
在frameworks/base/core/java/android/view/NotificationTopLineView.java的onLayout中添加log
js
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
final int width = getWidth();
int start = getPaddingStart();//关注点1
...
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
if (child.getVisibility() == GONE) {
continue;
}
...
if (mViewsToDisappear.contains(child)) {
child.layout(start, childTop, start, childTop + childHeight);
} else {
start += params.getMarginStart();
int end = start + child.getMeasuredWidth();
int layoutLeft = isRtl ? width - end : start;
int layoutRight = isRtl ? width - start : end;
start = end + params.getMarginEnd();
//关注点2
child.layout(layoutLeft, childTop, layoutRight, childTop + childHeight);
}
}
}
layoutLeft直接影响Id为app_name_text的子view布局,layoutLeft受start的影响
通过log跟踪知道阿拉伯语下isRtl是true,layoutLeft对比正常的log发现:
- 正常时getPaddingStart()值为69
- 异常时getPaddingStart()为0
getPaddingStart是69,刚好可以绕过图标布局
js
frameworks/base/core/java/android/view/View.java
public int getPaddingStart() {
if (!isPaddingResolved()) {
resolvePadding();
}
return (getLayoutDirection() == LAYOUT_DIRECTION_RTL) ?// 由于isRtl是true,所以此时获取的是mPaddingRight的值
mPaddingRight : mPaddingLeft;
}
//关注点3 mPaddingRight和mPaddingLeft的赋值
protected void internalSetPadding(int left, int top, int right, int bottom) {
...
if (mPaddingLeft != left) {
changed = true;
mPaddingLeft = left;
}
if (mPaddingRight != right) {
changed = true;
mPaddingRight = right;
}
...
}
public void resolvePadding() {
final int resolvedLayoutDirection = getLayoutDirection();
if (!isRtlCompatibilityMode()) {
...
switch (resolvedLayoutDirection) {
case LAYOUT_DIRECTION_RTL:
if (mUserPaddingStart != UNDEFINED_PADDING) {
mUserPaddingRight = mUserPaddingStart;//关注点4
} else {
mUserPaddingRight = mUserPaddingRightInitial;
}
if (mUserPaddingEnd != UNDEFINED_PADDING) {
mUserPaddingLeft = mUserPaddingEnd;
} else {
mUserPaddingLeft = mUserPaddingLeftInitial;
}
break;
case LAYOUT_DIRECTION_LTR:
default:
if (mUserPaddingStart != UNDEFINED_PADDING) {
mUserPaddingLeft = mUserPaddingStart;//关注点5
} else {
mUserPaddingLeft = mUserPaddingLeftInitial;
}
if (mUserPaddingEnd != UNDEFINED_PADDING) {
mUserPaddingRight = mUserPaddingEnd;
} else {
mUserPaddingRight = mUserPaddingRightInitial;
}
}
mUserPaddingBottom = (mUserPaddingBottom >= 0) ? mUserPaddingBottom : mPaddingBottom;
}
internalSetPadding(mUserPaddingLeft, mPaddingTop, mUserPaddingRight, mUserPaddingBottom);//关注点6
onRtlPropertiesChanged(resolvedLayoutDirection);
mPrivateFlags2 |= PFLAG2_PADDING_RESOLVED;//关注点7
}
mUserPaddingStart在初始化时由android:paddingStart赋予,resolvePadding中会根据布局方向将mUserPaddingStart赋值给mUserPaddingLeft或者mUserPaddingRight,并且会设置标志PFLAG2_PADDING_RESOLVED
在resolvePadding添加log发现UserPaddingStart为69,但是走了LAYOUT_DIRECTION_LTR(关注点5),mPaddingLeft=69,mPaddingRight=0。调用堆栈如下:
js
at android.view.View.resolvePadding(View.java:22925)
at android.view.ViewGroup.resolvePadding(ViewGroup.java:8141)
at android.view.ViewGroup.resolvePadding(ViewGroup.java:8146)
at android.view.ViewGroup.resolvePadding(ViewGroup.java:8146)
at android.view.ViewGroup.resolvePadding(ViewGroup.java:8146)
at android.view.ViewGroup.resolvePadding(ViewGroup.java:8146)
at android.view.View.getPaddingStart(View.java:27557)
//关注点8
at com.android.systemui.shade.NotificationPanelViewController.setNotificationStackScrollPadding(go/retraceme bd70bdd6abedbed99da0262d7d7cea31b7090f7a0745207247f1957c41f20398:84)
at com.android.systemui.shade.NotificationPanelViewController.requestScrollerTopPaddingUpdate(go/retraceme bd70bdd6abedbed99da0262d7d7cea31b7090f7a0745207247f1957c41f20398:168)
at com.android.systemui.shade.NotificationPanelViewController$10.onHeightChanged(go/retraceme bd70bdd6abedbed99da0262d7d7cea31b7090f7a0745207247f1957c41f20398:51)
at com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.notifyHeightChangeListener(go/retraceme bd70bdd6abedbed99da0262d7d7cea31b7090f7a0745207247f1957c41f20398:5)
at com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.onChildHeightChanged(go/retraceme bd70bdd6abedbed99da0262d7d7cea31b7090f7a0745207247f1957c41f20398:135)
at com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout$6.onHeightChanged(go/retraceme bd70bdd6abedbed99da0262d7d7cea31b7090f7a0745207247f1957c41f20398:3)
at com.android.systemui.statusbar.notification.row.ExpandableView.notifyHeightChanged(go/retraceme bd70bdd6abedbed99da0262d7d7cea31b7090f7a0745207247f1957c41f20398:5)
at com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.notifyHeightChanged(go/retraceme bd70bdd6abedbed99da0262d7d7cea31b7090f7a0745207247f1957c41f20398:1)
at com.android.systemui.statusbar.notification.collection.coordinator.RowAppearanceCoordinator$attach$2.onAfterRenderEntry(go/retraceme bd70bdd6abedbed99da0262d7d7cea31b7090f7a0745207247f1957c41f20398:38)
at com.android.systemui.statusbar.notification.collection.render.RenderStageManager.dispatchOnAfterRenderEntries(go/retraceme bd70bdd6abedbed99da0262d7d7cea31b7090f7a0745207247f1957c41f20398:70)
at com.android.systemui.statusbar.notification.collection.ShadeListBuilder$$ExternalSyntheticLambda6.run(go/retraceme bd70bdd6abedbed99da0262d7d7cea31b7090f7a0745207247f1957c41f20398:1747)
at com.android.systemui.statusbar.notification.collection.NotifPipelineChoreographerImpl$frameCallback$1.doFrame(go/retraceme bd70bdd6abedbed99da0262d7d7cea31b7090f7a0745207247f1957c41f20398:39)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1865)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1876)
at android.view.Choreographer.doCallbacks(Choreographer.java:1287)
//关注点9
at android.view.Choreographer.doAnimationLoad(Choreographer.java:1144)
at android.view.Choreographer.doFrame(Choreographer.java:1109)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1693)
js
src/com/android/systemui/shade/NotificationPanelViewController.java
public void setNotificationStackScrollPadding(int paddingTop) {
NotificationStackScrollLayout view = mNotificationStackScrollLayoutController.getView();
if (view != null) {
view.getPaddingStart()// 1
...
}
frameworks/base/core/java/android/view/View.java
public int getPaddingStart() {
if (!isPaddingResolved()) {
resolvePadding();// 2
}
return (getLayoutDirection() == LAYOUT_DIRECTION_RTL) ?
mPaddingRight : mPaddingLeft;
}
public void resolvePadding() {
super.resolvePadding();
int count = getChildCount();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.isLayoutDirectionInherited() && !child.isPaddingResolved()) {
child.resolvePadding();// 3
}
}
}
由于(关注点8)setNotificationStackScrollPadding调用了NotificationStackScrollLayout的getPaddingStart从而触发了其自身和child view的resolvePadding,child view包含了NotificationTopLineView 重点是(关注点9)Choreographer animation中触发了,此时NotificationTopLineView还没有获取到正确的布局方向
js
at android.view.View.resolveLayoutDirection(View.java:22751)
at android.view.ViewGroup.resolveLayoutDirection(ViewGroup.java:8086)
at android.view.ViewGroup.resolveLayoutDirection(ViewGroup.java:8092)
at android.view.ViewGroup.resolveLayoutDirection(ViewGroup.java:8092)
at android.view.ViewGroup.resolveLayoutDirection(ViewGroup.java:8092)
at android.view.ViewGroup.resolveLayoutDirection(ViewGroup.java:8092)
at android.view.ViewGroup.resolveLayoutDirection(ViewGroup.java:8092)
at android.view.View.resolveRtlPropertiesIfNeeded(View.java:22583)
at android.view.ViewGroup.resolveRtlPropertiesIfNeeded(ViewGroup.java:8067)
at android.view.ViewGroup.resolveRtlPropertiesIfNeeded(ViewGroup.java:8074)
at android.view.View.measure(View.java:28645)
at androidx.constraintlayout.widget.ConstraintLayout$Measurer.measure(go/retraceme bd70bdd6abedbed99da0262d7d7cea31b7090f7a0745207247f1957c41f20398:525)
at androidx.constraintlayout.core.widgets.analyzer.DependencyGraph.measure(go/retraceme bd70bdd6abedbed99da0262d7d7cea31b7090f7a0745207247f1957c41f20398:13)
at androidx.constraintlayout.core.widgets.analyzer.DependencyGraph.basicMeasureWidgets(go/retraceme bd70bdd6abedbed99da0262d7d7cea31b7090f7a0745207247f1957c41f20398:758)
at androidx.constraintlayout.widget.ConstraintLayout.resolveSystem(go/retraceme bd70bdd6abedbed99da0262d7d7cea31b7090f7a0745207247f1957c41f20398:628)
at androidx.constraintlayout.widget.ConstraintLayout.onMeasure(go/retraceme bd70bdd6abedbed99da0262d7d7cea31b7090f7a0745207247f1957c41f20398:511)
at android.view.View.measure(View.java:28678)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:7232)
at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
at android.view.View.measure(View.java:28678)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:7232)
at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
at com.android.systemui.shade.NotificationShadeWindowView.onMeasure(go/retraceme bd70bdd6abedbed99da0262d7d7cea31b7090f7a0745207247f1957c41f20398:10)
at android.view.View.measure(View.java:28678)
at android.view.ViewRootImpl.performMeasure(ViewRootImpl.java:5115)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:4251)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:3128)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:10788)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1867)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1876)
//关注点10
at android.view.Choreographer.doCallbacks(Choreographer.java:1287)
at android.view.Choreographer.doFrame(Choreographer.java:1113)
而正确的布局方向的调用栈如上,是在(关注点10)Choreographer traversal阶段获得,ViewRootImpl measure时将resolveRtlPropertiesIfNeeded遍历child view并设置正确布局方向
js
frameworks/base/core/java/android/view/Choreographer.java
void doFrame(long frameTimeNanos, int frame,
DisplayEventReceiver.VsyncEventData vsyncEventData) {
doAnimationLoad(frameIntervalNanos);// 1144行
doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameIntervalNanos);
mFrameInfo.markPerformTraversalsStart();
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameIntervalNanos);// 1287行
doCallbacks(Choreographer.CALLBACK_COMMIT, frameIntervalNanos);
Choreographer animation比traversal更早,由于NotificationTopLineView在animation阶段就设置了PFLAG2_PADDING_RESOLVED,导致traversal阶段resolveRtlPropertiesIfNeeded无法调用resolvePadding,从而无法矫正mPaddingLeft和mPaddingRight的值
js
frameworks/base/core/java/android/view/View.java
public boolean resolveRtlPropertiesIfNeeded() {
if (!isLayoutDirectionResolved()) {
resolveLayoutDirection();
resolveLayoutParams();
}
if (!isPaddingResolved()) {//isPaddingResolved返回true,即不会再进resolvePadding
resolvePadding();
}
onRtlPropertiesChanged(getLayoutDirection());
return true;
}
boolean isPaddingResolved() {
//此处由于已经调用过resolvePadding,设置了PFLAG2_PADDING_RESOLVED标志,将返回true
return (mPrivateFlags2 & PFLAG2_PADDING_RESOLVED) == PFLAG2_PADDING_RESOLVED;
}