Target SDK 升级到 29 AnimatorSet 动画不执行了(后续)

795 阅读4分钟

接上一篇文章 Target SDK 升级到 29 AnimatorSet 动画不执行了  问题我们修好了,但是有个奇怪的点不知道大家有没有注意到。

上一篇文章我们知道了 AnimatorSet 没有执行的原因是由于 sDurationScale 反射失败导致的。但是文章最后有一个很奇怪的点,为什么 Application 中设置 sDurationScale 不生效?? Activity 中设置才有效果???不知道大家有没有关注这个点?

上篇文章遗留问题

  1. 为什么 Application 中设置 sDurationScale 无效?
  2. 为什么 Activity 中设置才有效?
  3. 系统 sDurationScale 又是何时设置这个值的呢?省电模式切换对这个值有些什么影响呢?
  4. 还有没有其他的坑点?

带着这些问题我们看下为什么,找寻下其根本原因

分析过程

我们看下 setDurationScale 调用位置

image.png

Android 源码搜索网站上我们可以看到 WindowManagerGlobalWindowManagerService.class 有几处调用位置

WindowManagerGlobal.class

public final class WindowManagerGlobal {
    @UnsupportedAppUsage
    public static void initialize() {
        getWindowManagerService();
    }
    
    @UnsupportedAppUsage
    public static IWindowManager getWindowManagerService() {
        synchronized (WindowManagerGlobal.class) {
            if (sWindowManagerService == null) {
                sWindowManagerService = IWindowManager.Stub.asInterface(
                        ServiceManager.getService("window"));
                try {
                    if (sWindowManagerService != null) {
                        ValueAnimator.setDurationScale(
                                sWindowManagerService.getCurrentAnimatorScale());
                        sUseBLASTAdapter = sWindowManagerService.useBLAST();
                    }
                } catch (RemoteException e) {
                    throw e.rethrowFromSystemServer();
                }
            }
            return sWindowManagerService;
        }
    }


    @UnsupportedAppUsage
    public static IWindowSession getWindowSession() {
        synchronized (WindowManagerGlobal.class) {
            if (sWindowSession == null) {
                try {
                    // Emulate the legacy behavior.  The global instance of InputMethodManager
                    // was instantiated here.
                    // TODO(b/116157766): Remove this hack after cleaning up @UnsupportedAppUsage
                    InputMethodManager.ensureDefaultInstanceForDefaultDisplayIfNecessary();
                    IWindowManager windowManager = getWindowManagerService();
                    sWindowSession = windowManager.openSession(
                            new IWindowSessionCallback.Stub() {
                                @Override
                                public void onAnimatorScaleChanged(float scale) {
                                    ValueAnimator.setDurationScale(scale);
                                }
                            });
                } catch (RemoteException e) {
                    throw e.rethrowFromSystemServer();
                }
            }
            return sWindowSession;
        }
    }
}

WindowManagerService.class

public class WindowManagerService extends IWindowManager.Stub
        implements Watchdog.Monitor, WindowManagerPolicy.WindowManagerFuncs,
                DisplayManagerService.WindowManagerFuncs, DisplayManager.DisplayListener {

   private WindowManagerService(Context context, PowerManagerService pm,
            DisplayManagerService displayManager, InputManagerService inputManager,
            Handler uiHandler,
            boolean haveInputMethods, boolean showBootMsgs, boolean onlyCore) {
        // 省略部分代码
        if (mPowerManagerInternal != null) {
            mPowerManagerInternal.registerLowPowerModeObserver(
                    new PowerManagerInternal.LowPowerModeListener() {
                @Override
                public int getServiceType() {
                    return ServiceType.ANIMATION;
                }

                @Override
                public void onLowPowerModeChanged(PowerSaveState result) {
                    synchronized (mGlobalLock) {
                        final boolean enabled = result.batterySaverEnabled;
                        if (mAnimationsDisabled != enabled && !mAllowAnimationsInLowPowerMode) {
                            mAnimationsDisabled = enabled;
                            dispatchNewAnimatorScaleLocked(null);
                        }
                    }
                }
            });
            mAnimationsDisabled = mPowerManagerInternal
                    .getLowPowerState(ServiceType.ANIMATION).batterySaverEnabled;
        }
        // 省略部分代码
        setAnimatorDurationScale(getAnimatorDurationScaleSetting());
        // 省略部分代码
    }
    
    void windowAddedLocked() {
        // 省略部分代码
        if (mSurfaceSession == null) {
            // 省略部分代码
            if (mLastReportedAnimatorScale != mService.getCurrentAnimatorScale()) {
                mService.dispatchNewAnimatorScaleLocked(this);
            }
        }
        mNumWindow++;
    }
    
    void dispatchNewAnimatorScaleLocked(Session session) {
        mH.obtainMessage(H.NEW_ANIMATOR_SCALE, session).sendToTarget();
    }

    @Override
    public void handleMessage(Message msg) {
        if (DEBUG_WINDOW_TRACE) {
            Slog.v(TAG_WM, "handleMessage: entry what=" + msg.what);
        }
        case NEW_ANIMATOR_SCALE: {
            float scale = getCurrentAnimatorScale();
            ValueAnimator.setDurationScale(scale);
            Session session = (Session)msg.obj;
            if (session != null) {
                try {
                	session.mCallback.onAnimatorScaleChanged(scale);
                } catch (RemoteException e) {
                }
            } else {
                ArrayList<IWindowSessionCallback> callbacks
                        = new ArrayList<IWindowSessionCallback>();
                synchronized (mGlobalLock) {
                    for (int i=0; i<mSessions.size(); i++) {
                        callbacks.add(mSessions.valueAt(i).mCallback);
                    }
                }
                for (int i=0; i<callbacks.size(); i++) {
                    try {
                        callbacks.get(i).onAnimatorScaleChanged(scale);
                    } catch (RemoteException e) {
                    }
                }
            }
            break;
       }
   }
}

这边能能影响到 sDurationScale 我把代码都贴了下,总结下具体有以下几个地方

  1. WindowManagerGlobal 类调用 getWindowManagerService() 获取 WindowManagerService 对象时
  2. openSession 时设置的回调会影响到

这边我们稍微看下 WindowManagerGlobal 可以看到初始化位置在 handleLaunchActivity

public final class ActivityThread extends ClientTransactionHandler
        implements ActivityThreadInternal {

    @Override
    public Activity handleLaunchActivity(ActivityClientRecord r,
            PendingTransactionActions pendingActions, Intent customIntent) {
        // 省略部分代码
        WindowManagerGlobal.initialize();
		// 省略部分代码
        return a;
    }

}

看到这里基础好的应该就能明白为什么 Application 中设置 sDurationScale 无效,而 Activity 设置 sDurationScale 有效了。不太明白的可以去搜索 handleLaunchActivity 调用时机,由于篇幅有限不做过多介绍。这边对启动流程不熟悉的话推荐看下这篇文章 # Android Application创建到Activity启动(launcher启动和startActivity启动)

这边就解释了完了前面两个疑问的,剩下就看看有没有其他的坑点。虽然我们在 Activity 中设置 sDurationScale 修复了动画不播放的问题,但是不知道有没有人记得 openSession 设置的回调。

假设当前有这么个场景:用户开发者模式中设置了关闭动画,而我们在 Activity 强制打开了动画这个时候是正常的。但是如果用户设置省电模式或者进入了低电量的状态,系统就会回调 WindowManagerServiceonLowPowerModeChanged 导致 sDurationScale 重新被设置,这个时候用户没有关闭我们的 App 而是边充电边玩,就还是会出现 sDurationScale == 0F 导致动画不执行。

上述这种情况就是一个坑点了!!! 那么怎么去解决这个问题,这个仁者见仁智者见智了,目前个人想到的方案有以下几种

  1. onLowPowerModeChanged 尝试监听这个,在这个中重新设置
  2. start() 时候每次都去设置
  3. 和产品友好交流一波,毕竟这个玩意儿是 google 加入灰名单的玩意儿。保不齐xxxx,这种东西咱不懂咱也不好说。

剩下 windowAddedLocked 是时候的影响,这边大家自己看下吧,不细说了

OK, 整篇文章到此结束。我要上分去了。。。。说的不对的自己评论去找大佬去吧。