【操作步骤】 在谷歌商店下载安装三方壁纸(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中一直起作用,无法进行正常的绘制