问题描述
最近遇到一个共享元素动画失效问题。经过网上查找,参考文献1
操作:
首先从 Activity A 利用共享元素通过 ActivityOptionsCompat.makeSceneTransitionAnimation( ) 跳转到 Activity B中,此时动画正常。
然后在 Activity B 中使用 finishAfterTransition() 返回到 Activity A,此时动画也正常。
但是如果在跳转到 Activity B 中后, 将 Activity B 退入后台再回到前台 或者 跳转到第三个 Activity C后返回,此时再使用 finishAfterTransition( ) 尝试返回到 Activity A中式,问题就出现了:
在 Android 10中,动画失效,使用的是系统自己的切换动画;但是在 Android 9 中则仍旧能正常切换。
问题分析
参考文献1 已经做了详细的源码回溯分析,找到了导致问题的点,直接看该文推导即可。根据文献得到结论:
1 mEnterTransitionCoordinator 在 Activity 的 生命周期 onStop() 被置 null
2 mEnterTransitionCoordinator 在 Activity 的 mActivityTransitionState.enterReady( activity) 中被初始化并赋值
因此,参考文献1 给定的初步解决方案是使用反射,在返回前再次调用该方法重新初始化 mEnterTransitionCoordinator。由于 Android 9 以上对反射做了限制,所以该方案也要求限定了SDK版本。并且该文没有继续探究出现差异的原因。
本文就是继续上述分析。
mActivityTransitionState.enterReady( activity) 重新执行mEnterTransitionCoordinator初始化的条件是要能跳过如下判断:
if (mEnterActivityOptions == null || mIsEnterTriggered) {
return;
}
即或者 mEnterActivityOptions 不为 null, 或者 mIsEnterTriggered 为 false。
经过回溯,这两个变量无法通过外部设置。
因为从后台再进入,必定会走 *Activity.onResume()*方法,进而调用 ActivityTransitionState.onResume().其开始代码为:
public void onResume(Activity activity) {
// After orientation change, the onResume can come in before the top Activity has
// left, so if the Activity is not top, wait a second for the top Activity to exit.
if (mEnterTransitionCoordinator == null || activity.isTopOfTask()) {
restoreExitedViews();
restoreReenteringViews();
} else {
...
}
}
经过 debug 可知,这里不论怎么操作,都会走 if 语句内代码。而这两函数内部分别限定 mCalledExitCoordinator != null 和 mEnterTransitionCoordinator != null。
貌似回到原点 (:<。在调试过程中,一直在比对 android-28 和 android-29的代码,未发现有实质差异。
回到原点: Activity.finishAfterTransition()
该方法调用 mActivityTransitionState.startExitBackTransition(this),通过比对 android-28 和 android-29的代码,发现这个函数的一开始就出现了二者的实质差异:
//android-28
public boolean startExitBackTransition(final Activity activity) {
if (mEnteringNames == null || mCalledExitCoordinator != null) {
return false;
} else {
...
}
}
//android-29
public boolean startExitBackTransition(final Activity activity) {
ArrayList<String> pendingExitNames = getPendingExitNames();
if (pendingExitNames == null || mCalledExitCoordinator != null) {
return false;
} else {
...
}
}
对比可知,对于 pendingNames, android-28是利用已有的,而android-29是重新获取。回溯代码可得到,在从 Activity A 跳转到 Activity B 时,同参考文献1 一样,这里会执行 enterReady(), 进而执行 startEnter()。在 startEnter() 中android-28直接使用原有值,这样就不会为 null。而android-29是重新获取,而此时 mEnterTransitonCoordinator 已经是 null了。
所以这里才是问题的核心所在。
问题解决
在 android-29 中,ActivityTransitionState 中有公有方法 readState() 在 mEnterTransitonCoordinator 为 null 时也能读取到保存的 mPendingExitNames 变量,不过 Activity 也没提供访问该方法的途径。