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;
    }
相关推荐
千里马学框架2 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台2 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone2 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc2 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo2 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077002 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼2 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone2 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen2 天前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone2 天前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui