SystemUI通知在阿拉伯语下布局方向RTL下appName显示异常

分析

在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;
    }
相关推荐
用户091 小时前
Kotlin Flow的6个必知高阶技巧
android·面试·kotlin
用户091 小时前
Flutter插件与包的本质差异
android·flutter·面试
用户091 小时前
Jetpack Compose静态与动态CompositionLocal深度解析
android·面试·kotlin
聆风吟º4 小时前
【Spring Boot 报错已解决】别让端口配置卡壳!Spring Boot “Binding to target failed” 报错解决思路
android·java·spring boot
非专业程序员Ping12 小时前
HarfBuzz概览
android·ios·swift·font
Jeled13 小时前
「高级 Android 架构师成长路线」的第 1 阶段 —— 强化体系与架构思维(Clean Architecture 实战)
android·kotlin·android studio·1024程序员节
明道源码15 小时前
Kotlin 控制流、函数、Lambda、高阶函数
android·开发语言·kotlin
消失的旧时光-194317 小时前
Kotlin × Gson:为什么遍历 JsonObject 要用 entrySet()
android·kotlin·数据处理·1024程序员节
G果18 小时前
安卓APP页面之间传参(Android studio 开发)
android·java·android studio
曾凡宇先生19 小时前
无法远程连接 MySQL
android·开发语言·数据库·sql·tcp/ip·mysql·adb