问题描述

最近遇到一个共享元素动画失效问题。经过网上查找,参考文献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 != nullmEnterTransitionCoordinator != 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 也没提供访问该方法的途径。