修改壁纸后进桌面一直黑屏问题分析

【操作步骤】 在谷歌商店下载安装三方壁纸(Pixel 4D)选择一张动态壁纸设为锁屏和桌面

【实际结果】 重启后无法进入桌面,输入密码后桌面高概率黑屏显示

【期望结果】 可以正常进入桌面

分析

dumpsys

adb shell dumpsys activity com.android.launcher

ini 复制代码
    ViewRoot:
      mAdded=true
      mRemoved=false
      mStopped=false
...
      mTraversalScheduled=true
       (barrier=5159)
    Looper (main, tid 2) {21b2b4b}
      Message 0: { when=-16ms barrier=5159 }
      Message 1: { when=+58s948ms callback=android.widget.TextClock$2 target=android.view.ViewRootImpl$ViewRootHandler }
      Message 2: { when=+58s949ms callback=android.widget.TextClock$2 target=android.view.ViewRootImpl$ViewRootHandler }
      Message 3: { when=+58s949ms callback=android.widget.TextClock$2 target=android.view.ViewRootImpl$ViewRootHandler }

相关代码

frameworks/base/core/java/android/view/ViewRootImpl.java

scss 复制代码
    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            // 同步屏障
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            //通过Choreographer请求app-vsync
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        }
    }

Launcher提交了同步屏障,正在请求app-vsync,下一步应该是等待app-vsync到来,并执行mTraversalRunnable->doTraversal->performTraversals,移除屏障

adb shell dumpsys window w

ini 复制代码
  Window #11 Window{23ae5f7 u0 com.blackview.launcher/com.android.searchlauncher.SearchLauncher}-[Surface(name=*Title#821)/@0x16e58f3]:
....
    mBaseLayer=21000 mSubLayer=0    mToken=ActivityRecord{4aeddb2 u0 com.blackview.launcher/com.android.searchlauncher.SearchLauncher} t17 d0}
    mActivityRecord=ActivityRecord{4aeddb2 u0 com.blackview.launcher/com.android.searchlauncher.SearchLauncher} t17 d0}
...
    mHasSurface=true isReadyForDisplay()=true mWindowRemovalAllowed=false
    Frames: parent=[0,0][720,1600] display=[0,0][720,1600] frame=[0,0][720,1600] last=[0,0][720,1600] insetsChanged=false
     surface=[0,0][0,0]
    WindowStateAnimator{1d2404b com.blackview.launcher/com.android.searchlauncher.SearchLauncher}:
      mSurface=Surface(name=com.blackview.launcher/com.android.searchlauncher.SearchLauncher#821)/@0x16e58f3
      Surface: shown=false layer=0 alpha=1.0 rect=(0.0,0.0)  transform=(1.0, 0.0, 0.0, 1.0)
      mDrawState=DRAW_PENDING       mLastHidden=true

相关代码:

frameworks/base/services/core/java/com/android/server/wm/WindowStateAnimator.java

/** This is set after the Surface has been created but before the window has been drawn. During

  • this time the surface is hidden. /

static final int DRAW_PENDING = 1;

  • /** This is set after the window has finished drawing for the first time but before its surface

  • is shown. The surface will be displayed when the next layout is run. */

static final int COMMIT_DRAW_PENDING = 2;

Launcher已经有了Surface,但是还没绘制完成

Systrace

主线程在不断刷新

放大后可以看到onVsync->doFrame>performTraversals->scheduleVsyncLocked

相关代码

2.1 cancelAndRedraw为true,不会走createSyncIfNeeded后续也不能通过reportDrawFinished告诉WMS绘制完成即不能进到下一个COMMIT_DRAW_PENDING

2.2 cancelAndRedraw为true,继续请求scheduleTraversals,符合Systrace中的不断刷新的表象

scss 复制代码
frameworks/base/core/java/android/view/ViewRootImpl.java
    private void performTraversals() {
        if (host == null || !mAdded) {
            return;
        }
...
        // 2.1
        boolean cancelAndRedraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw();
        if (!cancelAndRedraw) {
            createSyncIfNeeded();
        }

        if (!isViewVisible) {
...
        } else if (cancelAndRedraw) {
            // 2.2
            scheduleTraversals();
        } else {
..
        }
...
    }

    private void createSyncIfNeeded() {
..
        final int seqId = mSyncSeqId;
        mSyncId = mSurfaceSyncer.setupSync(transaction -> {
            mHandler.postAtFrontOfQueue(() -> {
                mSurfaceChangedTransaction.merge(transaction);
                reportDrawFinished(seqId);
            });
        });
..
    }

    private void reportDrawFinished(int seqId) {
        try {
            mWindowSession.finishDrawing(mWindow, mSurfaceChangedTransaction, seqId);
        } catch (RemoteException e) {
            Log.e(mTag, "Unable to report draw finished", e);
            mSurfaceChangedTransaction.apply();
        } finally {
            mSurfaceChangedTransaction.clear();
        }
    }

关于OnPreDrawListeners的添加和移除

ini 复制代码
frameworks/base/core/java/android/view/ViewTreeObserver.java
     public final boolean dispatchOnPreDraw() {
        boolean cancelDraw = false;
        final CopyOnWriteArray<OnPreDrawListener> listeners = mOnPreDrawListeners;
        if (listeners != null && listeners.size() > 0) {
            CopyOnWriteArray.Access<OnPreDrawListener> access = listeners.start();
            try {
                int count = access.size();
                for (int i = 0; i < count; i++) {
                    cancelDraw |= !(access.get(i).onPreDraw());
                }
            } finally {
                listeners.end();
            }
        }
        return cancelDraw;
    }

    public void addOnPreDrawListener(OnPreDrawListener listener) {
        checkIsAlive();

        if (mOnPreDrawListeners == null) {
            mOnPreDrawListeners = new CopyOnWriteArray<OnPreDrawListener>();
        }

        mOnPreDrawListeners.add(listener);
    }

    public void removeOnPreDrawListener(OnPreDrawListener victim) {
        checkIsAlive();
        if (mOnPreDrawListeners == null) {
            return;
        }
        mOnPreDrawListeners.remove(victim);
    }

App可通过addOnPreDrawListener和removeOnPreDrawListener添加或移除OnPreDrawListener

搜索Launcher代码

scss 复制代码
src/com/android/launcher3/Launcher.java
   protected void onCreate(Bundle savedInstanceState) {
        if (!mModel.addCallbacksAndLoad(this)) {
            if (!internalStateHandled) {
                Log.d(BAD_STATE, "Launcher onCreate not binding sync, prevent drawing");
                // If we are not binding synchronously, pause drawing until initial bind complete,
                // so that the system could continue to show the device loading prompt
                mOnInitialBindListener = Boolean.FALSE::booleanValue;
            }
        }
...
        if (mOnInitialBindListener != null) {
            getRootView().getViewTreeObserver().addOnPreDrawListener(mOnInitialBindListener);
        }
    }

    public void onInitialBindComplete(IntSet boundPages, RunnableList pendingTasks) {
...
        if (mOnInitialBindListener != null) {
            getRootView().getViewTreeObserver().removeOnPreDrawListener(mOnInitialBindListener);
            mOnInitialBindListener = null;
        }
        executor.onLoadAnimationCompleted();
        executor.attachTo(this);
..
    }

原来Launcher在onCreate注册了OnPreDrawListener并返回false阻止绘制,为等待数据加载完成后才开始绘制,数据加载完成后移除OnPreDrawListener

问题解决

出现问题的Android版本是13,通过对比其他平台和Android版本对应的代码,发现Android15在Launcher onDestory时有对应的代码补丁

src/com/android/launcher3/Launcher.java

scss 复制代码
    @Override
    public void onDestroy() {
        super.onDestroy();
        // if Launcher activity is recreated, {@link Window} including {@link ViewTreeObserver}
        // could be preserved in {@link ActivityThread#scheduleRelaunchActivity(IBinder)} if the
        // previous activity has not stopped, which could happen when wallpaper detects a color
        // changes while launcher is still loading.
        getRootView().getViewTreeObserver().removeOnPreDrawListener(mOnInitialBindListener);
    }

加上这个补丁后,重新验证后问题解决

进一步分析

按照补丁的意思,此路径会导致Launcher Activity的重建recreate

3.1 当r.stopped为false,preserveWindow为true

3.2 r.mPreserveWindow为true

3.3 发消息RELAUNCH_ACTIVITY

typescript 复制代码
frameworks/base/core/java/android/app/Activity.java
   public void recreate() {
        if (mParent != null) {
            throw new IllegalStateException("Can only be called on top-level activity");
        }
        if (Looper.myLooper() != mMainThread.getLooper()) {
            throw new IllegalStateException("Must be called from main thread");
        }
        mMainThread.scheduleRelaunchActivity(mToken);
    }

frameworks/base/core/java/android/app/ActivityThread.java
    void scheduleRelaunchActivity(IBinder token) {
        final ActivityClientRecord r = mActivities.get(token);
        if (r != null) {
            Log.i(TAG, "Schedule relaunch activity: " + r.activityInfo.name);
            // 3.1
            scheduleRelaunchActivityIfPossible(r, !r.stopped /* preserveWindow */);
        }
    }
    private void scheduleRelaunchActivityIfPossible(@NonNull ActivityClientRecord r,
            boolean preserveWindow) {
        if ((r.activity != null && r.activity.mFinished) || r.token instanceof Binder) {
            // Do not schedule relaunch if the activity is finishing or is a local object (e.g.
            // created by ActivtiyGroup that server side doesn't recognize it).
            return;
        }
        if (preserveWindow && r.window != null) {
            // 3.2
            r.mPreserveWindow = true;
        }
        // 3.3
        mH.removeMessages(H.RELAUNCH_ACTIVITY, r.token);
        sendMessage(H.RELAUNCH_ACTIVITY, r.token);
    }
    public void recreate() {
        if (mParent != null) {
            throw new IllegalStateException("Can only be called on top-level activity");
        }
        if (Looper.myLooper() != mMainThread.getLooper()) {
            throw new IllegalStateException("Must be called from main thread");
        }
        mMainThread.scheduleRelaunchActivity(mToken);
    }

RELAUNCH_ACTIVITY消息处理

java 复制代码
frameworks/base/core/java/android/app/ActivityThread.java
   class H extends Handler {
        public void handleMessage(Message msg) {
                case RELAUNCH_ACTIVITY:
                    handleRelaunchActivityLocally((IBinder) msg.obj);
                    break;

    public void handleRelaunchActivityLocally(IBinder token) {
        final ActivityClientRecord r = mActivities.get(token);
        ...
        final ActivityRelaunchItem activityRelaunchItem = ActivityRelaunchItem.obtain(
                null /* pendingResults /, null / pendingIntents /, 0 / configChanges */,
                mergedConfiguration, r.mPreserveWindow);
        ...
        final ClientTransaction transaction = ClientTransaction.obtain(this.mAppThread, r.token);
        transaction.addCallback(activityRelaunchItem);
        transaction.setLifecycleStateRequest(lifecycleRequest);
        executeTransaction(transaction);
    }

frameworks/base/core/java/android/app/servertransaction/ActivityRelaunchItem.java
    public void execute(ClientTransactionHandler client, ActivityClientRecord r,
            PendingTransactionActions pendingActions) {
        client.handleRelaunchActivity(mActivityClientRecord, pendingActions);
    }

frameworks/base/core/java/android/app/ActivityThread.java
   @Override
    public void handleRelaunchActivity(ActivityClientRecord tmp,
            PendingTransactionActions pendingActions) {
        handleRelaunchActivityInner(r, configChanges, tmp.pendingResults, tmp.pendingIntents,
                pendingActions, tmp.startsNotResumed, tmp.overrideConfig, "handleRelaunchActivity");
    }

    private void handleRelaunchActivityInner(ActivityClientRecord r, int configChanges,
            List<ResultInfo> pendingResults, List<ReferrerIntent> pendingIntents,
            PendingTransactionActions pendingActions, boolean startsNotResumed,
            Configuration overrideConfig, String reason) {
        handleDestroyActivity(r, false, configChanges, true, reason);
        handleLaunchActivity(r, pendingActions, mLastReportedDeviceId, customIntent);
    }

3.4 mPreserveWindow为true保留上一次准备移除的PhoneWindow

scss 复制代码
frameworks/base/core/java/android/app/ActivityThread.java
    @Override
    public void handleDestroyActivity(ActivityClientRecord r, boolean finishing, int configChanges,
            boolean getNonConfigInstance, String reason) {
        performDestroyActivity(r, finishing, configChanges, getNonConfigInstance, reason);
        cleanUpPendingRemoveWindows(r, finishing);
        WindowManager wm = r.activity.getWindowManager();
        View v = r.activity.mDecor;
        if (v != null) {
            if (r.activity.mVisibleFromServer) {
                mNumVisibleActivities--;
            }
            IBinder wtoken = v.getWindowToken();
            if (r.activity.mWindowAdded) {
                if (r.mPreserveWindow) {
                    // Hold off on removing this until the new activity's window is being added.
                    // 3.4
                    r.mPendingRemoveWindow = r.window;
                    r.mPendingRemoveWindowManager = wm;
                    // We can only keep the part of the view hierarchy that we control,
                    // everything else must be removed, because it might not be able to
                    // behave properly when activity is relaunching.
                    r.window.clearContentView();
    }

3.5 拿出上一个destroy Activity的PhoneWindow

3.6 给新的Activity使用

typescript 复制代码
frameworks/base/core/java/android/app/ActivityThread.java
    public Activity handleLaunchActivity(ActivityClientRecord r,
            PendingTransactionActions pendingActions, int deviceId, Intent customIntent) {
        final Activity a = performLaunchActivity(r, customIntent);
    }

    private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
                if (r.mPendingRemoveWindow != null && r.mPreserveWindow) {
                    // 3.5
                    window = r.mPendingRemoveWindow;
                    r.mPendingRemoveWindow = null;
                    r.mPendingRemoveWindowManager = null;
                }
                // 3.6
                activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config,
                        r.referrer, r.voiceInteractor, window, r.activityConfigCallback,
                        r.assistToken, r.shareableActivityToken);
    }

3.7 重用上一个PhoneWindow的DecorView和token,ViewRootImpl

3.8 mWindowAdded为true

3.9 不需要重新addView

scss 复制代码
frameworks/base/core/java/android/app/Activity.java
    final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken,
            IBinder shareableActivityToken) {
        mWindow = new PhoneWindow(this, window, activityConfigCallback);

frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java
    public PhoneWindow(@UiContext Context context, Window preservedWindow,
            ActivityConfigCallback activityConfigCallback) {
        this(context);
        if (preservedWindow != null) {// 3.7
            mDecor = (DecorView) preservedWindow.getDecorView();
            mElevation = preservedWindow.getElevation();
            mLoadElevation = false;
            mForceDecorInstall = true;
            // If we're preserving window, carry over the app token from the preserved
            // window, as we'll be skipping the addView in handleResumeActivity(), and
            // the token will not be updated as for a new window.
            getAttributes().token = preservedWindow.getAttributes().token;
            final ViewRootImpl viewRoot = mDecor.getViewRootImpl();
            if (viewRoot != null) {
                // Clear the old callbacks and attach to the new window.
                viewRoot.getOnBackInvokedDispatcher().clear();
                onViewRootImplSet(viewRoot);
            }
        }

frameworks/base/core/java/android/app/ActivityThread.java
    public void handleResumeActivity(ActivityClientRecord r, boolean finalStateRequest,
            boolean isForward, boolean shouldSendCompatFakeFocus, String reason) {
        if (r.window == null && !a.mFinished && willBeVisible) {
            if (r.mPreserveWindow) {// 3.8
                a.mWindowAdded = true;
                r.mPreserveWindow = false;
                // Normally the ViewRoot sets up callbacks with the Activity
                // in addView->ViewRootImpl#setView. If we are instead reusing
                // the decor view we have to notify the view root that the
                // callbacks may have changed.
                ViewRootImpl impl = decor.getViewRootImpl();
                if (impl != null) {
                    impl.notifyChildRebuilt();
                }
            }
            if (a.mVisibleFromClient) {
                if (!a.mWindowAdded) {// 3.9
                    a.mWindowAdded = true;
                    wm.addView(decor, l);
                } else {
                    // The activity will get a callback for this {@link LayoutParams} change
                    // earlier. However, at that time the decor will not be set (this is set
                    // in this method), so no action will be taken. This call ensures the
                    // callback occurs with the decor set.
                    a.onWindowAttributesChanged(l);
                }
            }

log 分析

没有看到关键的"ActivityThread: Schedule relaunch activity: com.android.searchlauncher.SearchLauncher",log不连续可能是丢失了

关键点 :

stop的原因是handleRelaunchActivity,注意在此之前并没有wm_on_stop_called也就是ActivityClientRecord.stopped还是false的

而scheduleRelaunchActivity就是这之前调用的,所以后续ActivityClientRecord.mPreserveWindow是true的

less 复制代码
11-28 10:29:15.247 I/wm_on_start_called( 1876): [33688911,com.android.searchlauncher.SearchLauncher,handleStartActivity]
11-28 10:29:15.316 I/wm_on_resume_called( 1876): [33688911,com.android.searchlauncher.SearchLauncher,LIFECYCLER_RESUME_ACTIVITY]
11-28 10:29:15.350 I/wm_on_paused_called( 1876): [33688911,com.android.searchlauncher.SearchLauncher,performPause]
11-28 10:29:15.621 I/wm_stop_activity( 1128): [0,33688911,com.blackview.launcher/com.android.searchlauncher.SearchLauncher]
// 关键点 
11-28 10:29:16.203 I/wm_on_stop_called( 1876): [33688911,com.android.searchlauncher.SearchLauncher,handleRelaunchActivity]
11-28 10:29:16.409 I/wm_on_destroy_called( 1876): [33688911,com.android.searchlauncher.SearchLauncher,performDestroy]
11-28 10:29:17.976 I/wm_on_create_called( 1876): [33688911,com.android.searchlauncher.SearchLauncher,performCreate]
11-28 10:29:17.978 I/wm_on_start_called( 1876): [33688911,com.android.searchlauncher.SearchLauncher,handleStartActivity]
11-28 10:29:18.007 I/wm_on_resume_called( 1876): [33688911,com.android.searchlauncher.SearchLauncher,LIFECYCLER_RESUME_ACTIVITY]
11-28 10:29:18.011 I/wm_on_paused_called( 1876): [33688911,com.android.searchlauncher.SearchLauncher,performPause]
11-28 10:29:18.403 I/wm_on_stop_called( 1876): [33688911,com.android.searchlauncher.SearchLauncher,STOP_ACTIVITY_ITEM]

对应代码

frameworks/base/core/java/android/app/ActivityThread.java

typescript 复制代码
public void handleRelaunchActivity(ActivityClientRecord tmp,
            PendingTransactionActions pendingActions) {
        handleRelaunchActivityInner(r, configChanges, tmp.pendingResults, tmp.pendingIntents,
                pendingActions, tmp.startsNotResumed, tmp.overrideConfig, "handleRelaunchActivity");
    }
    private void handleRelaunchActivityInner(ActivityClientRecord r, int configChanges,
            List<ResultInfo> pendingResults, List<ReferrerIntent> pendingIntents,
            PendingTransactionActions pendingActions, boolean startsNotResumed,
            Configuration overrideConfig, String reason) {
        if (!r.stopped) {
            callActivityOnStop(r, true /* saveState */, reason);
        }

调用栈

1 wallpaper color change 触发recreate,发送消息RELAUNCH_ACTIVITY到主线程ActivityThread.H

2 ActivityThread.H消息处理RELAUNCH_ACTIVITY

结论:

由于上一个Activity还没stop就触发relaunch,mPreserveWindow为true,新的Activity持有的(mWindow)PhoneWindow使用旧(mDecor)DecoreView,DecoreView持有ViewRootImpl,也就是继续使用旧的ViewRootImpl

在onCreate时addOnPreDrawListener(mOnInitialBindListener)后,而onInitialBindComplete还没完成没有移除;relauncher流程会重建Activity,重进onCreate,此时上一个mOnInitialBindListener并没有移除,但新变量mOnInitialBindListener又添加一次addOnPreDrawListener,就算数据加载完成调用onInitialBindComplete移除mOnInitialBindListener,但上一个保留着已经造成内存泄露了,并在ViewRootImpl performTraversals中一直起作用,无法进行正常的绘制

相关推荐
王家视频教程图书馆5 分钟前
请求go web后端接口 java安卓端播放视频
android·java·前端
mmsx1 小时前
android opencv导入进行编译
android·人工智能·opencv
truemi.732 小时前
flutter --no-color pub get 超时解决方法
android·flutter
wwwsmef13 小时前
android源码下载
android
droidHZ4 小时前
Compose Multiplatform 之旅—声明式UI
android·kotlin
无泪真伤4 小时前
Android自定义控件—一个简单的播放暂停播放按钮
android
花生糖@4 小时前
Android XR 是什么?解释它的功能、设备、开发工具等
android·xr
zhangphil4 小时前
Android基于Path的addRoundRect,Canvas剪切clipPath简洁的圆角矩形实现,Kotlin(1)
android·kotlin
二流小码农5 小时前
鸿蒙元服务项目实战:终结篇之备忘录搜索功能实现
android·ios·harmonyos