其他
Target SDK 升级到 29 AnimatorSet 动画不执行了
本文作者
作者:爱上小懒虫
链接:
https://juejin.cn/post/7258233838370783293
本文由作者授权发布。
fun <T> ValueAnimator.getValueAnimatorAndExecute(call: (T) -> Unit) {
(animatedValue as? T)?.let {
call.invoke(it)
}
}
fun doRedPacketAnimatorSet(view:View){
AnimatorSet().apply {
playTogether(
ValueAnimator.ofFloat(0f, 1.1f, 0.8f, 1f).apply {
duration = 800L
},ValueAnimator.ofFloat(0f, 1f).apply {
duration = 800L
addUpdateListener { animation: ValueAnimator ->
animation.getValueAnimatorAndExecute<Float> {
// 监听更新做一个渐现动画
view.alpha = it
}
}
})
doOnStart {
view.run {
// 动画执行开始,透明度降为 1
isVisible = false
alpha = 0f
}
}
doOnEnd {
}
}
}
为什么 update 不执行?最近也没做改动,只有 Target Sdk 进行升级为啥动画给干没了呢?
分析原因
public final class AnimatorSet extends Animator implements AnimationHandler.AnimationFrameCallback {
@Override
public void start() {
start(false, true);
}
private void start(boolean inReverse, boolean selfPulse) {
// 省略部分代码
boolean isEmptySet = isEmptySet(this);
if (!isEmptySet) {
startAnimation();
}
// 省略部分代码
}
private void startAnimation() {
addAnimationEndListener();
// Register animation callback
addAnimationCallback(0);
// 省略部分代码
}
private void addAnimationCallback(long delay) {
if (!mSelfPulse) {
return;
}
AnimationHandler handler = AnimationHandler.getInstance();
handler.addAnimationFrameCallback(this, delay);
}
}
public class AnimationHandler {
private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
doAnimationFrame(getProvider().getFrameTime());
if (mAnimationCallbacks.size() > 0) {
getProvider().postFrameCallback(this);
}
}
};
public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) {
if (mAnimationCallbacks.size() == 0) {
getProvider().postFrameCallback(mFrameCallback);
}
if (!mAnimationCallbacks.contains(callback)) {
mAnimationCallbacks.add(callback);
}
// 省略部分代码
}
private void doAnimationFrame(long frameTime) {
long currentTime = SystemClock.uptimeMillis();
final int size = mAnimationCallbacks.size();
for (int i = 0; i < size; i++) {
final AnimationFrameCallback callback = mAnimationCallbacks.get(i);
if (callback == null) {
continue;
}
if (isCallbackDue(callback, currentTime)) {
// 最终会回调到 AnimatorSet
callback.doAnimationFrame(frameTime);
if (mCommitCallbacks.contains(callback)) {
getProvider().postCommitCallback(new Runnable() {
@Override
public void run() {
commitAnimationFrame(callback, getProvider().getFrameTime());
}
});
}
}
}
cleanUpList();
}
}
public final class AnimatorSet extends Animator implements AnimationHandler.AnimationFrameCallback {
@Override
public boolean doAnimationFrame(long frameTime) {
float durationScale = ValueAnimator.getDurationScale();
if (durationScale == 0f) {
// Duration scale is 0, end the animation right away.
forceToEnd();
return true;
}
// 省略部分代码
}
}
public class ValueAnimator extends Animator implements AnimationHandler.AnimationFrameCallback {
/**
* 系统范围的动画比例。
*
* 要检查是否启用了 areAnimatorsEnabled()动画,请使用 。
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
private static float sDurationScale = 1.0f;
/**
* 返回基于动画器的动画的系统范围的比例因子。这会影响所有此类动画的开始延迟和持续时间。设置为 0
*
* 将导致动画立即结束。默认值为 1.0f。
*
* 返回:持续时间刻度
*/
@FloatRange(from = 0)
public static float getDurationScale() {
return sDurationScale;
}
/**
* 返回系统范围的动画器当前是否已启用。默认情况下,所有动画器都处于启用状态。如果用户将开发人员选项设置为将动画器持续时间比例设置为 0,或者启用电池保存模式(禁用所有动画),则这可能会更改。
* 开发人员通常不需要调用此方法,但如果应用希望在禁用动画器时显示不同的体验,则可以将此返回值用作要提供的体验的决策程序。
* 返回:
* 布尔值 当前是否启用动画器。默认值为 true。
*/
public static boolean areAnimatorsEnabled() {
return !(sDurationScale == 0);
}
}
UnsupportedAppUsage 注意这个,后面会提到。
这个时候我同事发来一篇文章 targetSdkVersion 29,部分海外机型无法显示动效。
https://github.com/svga/SVGAPlayer-Android/issues/314#top
open class SVGAImageView : ImageView {
fun startAnimation() {
startAnimation(null, false)
}
fun startAnimation(range: SVGARange?, reverse: Boolean = false) {
// 省略部分代码
drawable.videoItem.let {
// 省略部分代码
try {
val animatorClass = Class.forName("android.animation.ValueAnimator")
animatorClass?.let {
it.getDeclaredField("sDurationScale")?.let {
it.isAccessible = true
it.getFloat(animatorClass).let {
durationScale = it.toDouble()
}
if (durationScale == 0.0) {
it.setFloat(animatorClass, 1.0f)
durationScale = 1.0
Log.e("SVGAPlayer", "The animation duration scale has been reset to 1.0x, because you closed it on developer options.")
}
}
}
} catch (e: Exception) {}
// 省略部分代码
}
}
}
Landroid/animation/ValueAnimator;->sDurationScale:F # Use ValueAnimator.areAnimatorsEnabled() (introduced in API 26) to query whether duration scale = 0. Otherwise, it is intended not to expose impl details such as the actual duration scales to devs.
open class SVGAImageView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0)
: ImageView(context, attrs, defStyleAttr) {
fun startAnimation() {
startAnimation(null, false)
}
fun startAnimation(range: SVGARange?, reverse: Boolean = false) {
stopAnimation(false)
play(range, reverse)
}
private fun play(range: SVGARange?, reverse: Boolean) {
// 省略部分代码
animator.duration = ((mEndFrame - mStartFrame + 1) * (1000 / videoItem.FPS) / generateScale()).toLong()
// 省略部分代码
}
@Suppress("UNNECESSARY_SAFE_CALL")
private fun generateScale(): Double {
var scale = 1.0
try {
val animatorClass = Class.forName("android.animation.ValueAnimator") ?: return scale
val getMethod = animatorClass.getDeclaredMethod("getDurationScale") ?: return scale
scale = (getMethod.invoke(animatorClass) as Float).toDouble()
if (scale == 0.0) {
val setMethod = animatorClass.getDeclaredMethod("setDurationScale",Float::class.java) ?: return scale
setMethod.isAccessible = true
setMethod.invoke(animatorClass,1.0f)
scale = 1.0
LogUtils.info(TAG,
"The animation duration scale has been reset to" +
" 1.0x, because you closed it on developer options.")
}
} catch (ignore: Exception) {
ignore.printStackTrace()
}
return scale
}
}
解决问题
fun setAnimatorsEnabled() {
// 代码就不贴了,`SVGAImageView` 文章里面都有,自己拷贝下
}
public abstract class BaseApplication extends Application {
@SuppressLint("MissingSuperCall")
@Override
public void onCreate() {
setAnimatorsEnabled()
}
}
public abstract class BaseActivity extends Application {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
setAnimatorsEnabled()
}
}
为什么 Application 中设置 sDurationScale 无效? 为什么 Activity 中设置才有效? 系统 sDurationScale 又是何时设置这个值的呢?省电模式切换对这个值有些什么影响呢? 还有没有其他的坑点?
带着这些问题我们看下为什么,找寻下其根本原因。
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;
}
}
}
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;
}
}
}
WindowManagerGlobal 类调用 getWindowManagerService() 获取 WindowManagerService 对象时。 openSession 时设置的回调会影响到:
windowAddedLocked 由于篇幅有限有兴趣的可以去看下这个 Android 源码 图形系统之 WindowState attach。
https://blog.csdn.net/tyyj90/article/details/107850439
onLowPowerModeChanged 这个就很明显了低电量的时候会回调。
public final class ActivityThread extends ClientTransactionHandler
implements ActivityThreadInternal {
@Override
public Activity handleLaunchActivity(ActivityClientRecord r,
PendingTransactionActions pendingActions, Intent customIntent) {
// 省略部分代码
WindowManagerGlobal.initialize();
// 省略部分代码
return a;
}
}
这边给大家准备个时序图(网上扒的 Activity 启动时序图)。
https://blog.csdn.net/bobo_zai/article/details/84844178
onLowPowerModeChanged 尝试监听这个,在这个中重新设置。 start() 时候每次都去设置。 和产品友好交流一波,毕竟这个玩意儿是 google 加入灰名单的玩意儿。保不齐xxxx,这种东西咱不懂咱也不好说。
最后推荐一下我做的网站,玩Android: wanandroid.com ,包含详尽的知识体系、好用的工具,还有本公众号文章合集,欢迎体验和收藏!
推荐阅读:
扫一扫 关注我的公众号
如果你想要跟大家分享你的文章,欢迎投稿~
┏(^0^)┛明天见!