接上一篇文章 Target SDK 升级到 29 AnimatorSet 动画不执行了 问题我们修好了,但是有个奇怪的点不知道大家有没有注意到。
上一篇文章我们知道了 AnimatorSet 没有执行的原因是由于 sDurationScale 反射失败导致的。但是文章最后有一个很奇怪的点,为什么 Application 中设置 sDurationScale 不生效?? Activity 中设置才有效果???不知道大家有没有关注这个点?
上篇文章遗留问题
- 为什么
Application中设置sDurationScale无效? - 为什么
Activity中设置才有效? - 系统
sDurationScale又是何时设置这个值的呢?省电模式切换对这个值有些什么影响呢? - 还有没有其他的坑点?
带着这些问题我们看下为什么,找寻下其根本原因
分析过程
我们看下 setDurationScale 调用位置
从 Android 源码搜索网站上我们可以看到 WindowManagerGlobal 和 WindowManagerService.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 我把代码都贴了下,总结下具体有以下几个地方
WindowManagerGlobal类调用getWindowManagerService()获取WindowManagerService对象时openSession时设置的回调会影响到windowAddedLocked由于篇幅有限有兴趣的可以去看下这个 Android 源码 图形系统之 WindowState attachonLowPowerModeChanged这个就很明显了低电量的时候会回调
这边我们稍微看下 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 强制打开了动画这个时候是正常的。但是如果用户设置省电模式或者进入了低电量的状态,系统就会回调 WindowManagerService 的 onLowPowerModeChanged 导致 sDurationScale 重新被设置,这个时候用户没有关闭我们的 App 而是边充电边玩,就还是会出现 sDurationScale == 0F 导致动画不执行。
上述这种情况就是一个坑点了!!! 那么怎么去解决这个问题,这个仁者见仁智者见智了,目前个人想到的方案有以下几种
onLowPowerModeChanged尝试监听这个,在这个中重新设置start()时候每次都去设置- 和产品友好交流一波,毕竟这个玩意儿是
google加入灰名单的玩意儿。保不齐xxxx,这种东西咱不懂咱也不好说。
剩下 windowAddedLocked 是时候的影响,这边大家自己看下吧,不细说了
OK, 整篇文章到此结束。我要上分去了。。。。说的不对的自己评论去找大佬去吧。