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 Studio:键值对存储sharedPreferences
android·ide·android studio·键值对存储
雾里看山4 小时前
【MySQL】 表的约束(上)
android·mysql·adb
小墙程序员4 小时前
一文了解 Android 中 AAR、APK 和 AAB 的文件结构
android·gradle
q567315236 小时前
无法在Django 1.6中导入自定义应用
android·开发语言·数据库·django·sqlite
a3158238066 小时前
Android设置个性化按钮按键的快捷启动应用
android·开发语言·framework·源码·android13
XJSFDX_Ali7 小时前
安卓开发,底部导航栏
android·java·开发语言
云罗张晓_za8986689 小时前
抖音“碰一碰”发视频:短视频社交的新玩法
android·c语言·网络·线性代数·矩阵·php
货拉拉技术12 小时前
记一次无障碍测试引发app崩溃问题的排查与解决
android·前端·程序员
GrimRaider12 小时前
【逆向工程】破解unity的安卓apk包
android·unity·游戏引擎·软件逆向