問題描述
最近在項目中遇到一個關于共享元素動畫失效的問題椭坚,在Activity跳轉時使用ActivityOptionsCompat.makeSceneTransitionAnimation
做轉場動畫,
對于Activity共享元素動畫的使用不懂的童鞋自行谷歌搏色,網(wǎng)上資料太多善茎,這里就不做描述了。
一般情況下使用ActivityOptionsCompat.makeSceneTransitionAnimation
做轉場動畫時频轿,當Activity返回時也會伴隨返回的動畫垂涯。但是就是這樣的一個簡單的API,
居然在Android 10上測試出現(xiàn)了異常:
如果使用了
ActivityOptionsCompat.makeSceneTransitionAnimation
跳轉的Activity沒有再次跳轉到別的Activity的話略吨,返回時系統(tǒng)是帶上響應的共享元素動畫的集币,
但是如果跳轉的Activity再次跳轉了其他的Activity,然后再返回的話共享元素的返回動畫就失效了(包括按了Home鍵退回桌面翠忠,然后再次進入也會失效)鞠苟。
筆者的測試手機是華為nova 5i
,系統(tǒng)是Android 10
。
問題追蹤
1秽之、 大神方案
當筆者看到這個bug的時候当娱,首先想到的可能是我打開的方式不對,于是谷歌百度了一波考榨,結果確實無功而返跨细。
于是筆者第一想到的就是是不是自己的使用方式不對,于是看看大神是怎么使用的河质,然后參考了一下Android 大神郭霖開源的一個開源項目giffun
冀惭,
發(fā)現(xiàn)giffun
也存在同樣的異常...
為此我專門給giffun
提了一個issue:
絕望了震叙。。散休。媒楼。。戚丸。划址。
2、 競品對比
既然查找不到相關的資料限府,那么就看看競品是否也有這樣的問題的夺颤,如果競品也有這樣的問題話就可以拿著競品去忽悠產(chǎn)品了,心里美滋滋胁勺。
于是筆者對比了一下競品的產(chǎn)品是否也存在這么的一個bug世澜,發(fā)現(xiàn)競品沒有一樣的bug,它們的共享元素動畫一切正常署穗。
后面通過研究了它們的APK才發(fā)現(xiàn)它們的共享元素動畫是使用Fragment做的宜狐,所以盡量使用Fragment的一個好處又體現(xiàn)出來了。
但是筆者今天要說的將Activity的轉場動畫改為Fragment這樣就完事了蛇捌,今天將帶大家一步一步分析共享元素動畫是如何失效的,如何修復這樣的一個bug咱台。
3络拌、 源碼先行
既然網(wǎng)上沒有這樣的資料,那就自己動手回溺,豐衣足食了春贸。首先我們初步看看Activity
的源碼,發(fā)現(xiàn)如果是返回帶執(zhí)行共享元素動畫的話執(zhí)行的方法是finishAfterTransition
遗遵。
在finishAfterTransition
里面調用了ActivityTransitionState
的startExitBackTransition
方法萍恕。
然而看源碼并沒有看出什么破綻,畢竟大多數(shù)手機是正常的车要,只有在Android 10上才出現(xiàn)了異常允粤。既然這條路走不通,那我們就換一個途徑吧翼岁。
4类垫、 Debug大法
我們使用Android Studio的斷點調試功能,在ActivityTransitionState
的startExitBackTransition
方法里面打斷點調試琅坡,
對比發(fā)現(xiàn)最終返回時沒有執(zhí)行共享元素動畫的原因是startExitBackTransition
方法內的pendingExitNames
變量為空就直接返回了false悉患,也就是不執(zhí)行共享元素動畫。
5榆俺、 萬變不離其宗售躁,再次回到源碼
那么為什么pendingExitNames
變量會變成了空呢坞淮?我們再次回到源碼分析,pendingExitNames
變量是通過
ActivityTransitionState
的startExitBackTransition
的getPendingExitNames
方法獲取的陪捷,方法如下:
if (mPendingExitNames == null && mEnterTransitionCoordinator != null) {
mPendingExitNames = mEnterTransitionCoordinator.getPendingExitSharedElementNames();
}
return mPendingExitNames;
}
很簡單的代碼回窘,如果mEnterTransitionCoordinator
是空的話,那么getPendingExitNames
方法必定會返回空揩局。
那么我們繼續(xù)追蹤一下mEnterTransitionCoordinator
變量是如何賦值的毫玖,跟蹤發(fā)現(xiàn)是在ActivityTransitionState
的enterReady(Activity activity)
方法
中對mEnterTransitionCoordinator
做了賦值操作,然后在ActivityTransitionState
的onStop
方法中被置空了凌盯,而ActivityTransitionState
的onStop
方法
又被Activity
的onStop
方法調用了付枫,至此大概就能解析的解析得通為什么經(jīng)過跳轉后Activity的返回共享元素失效了,原來是被Activity的onStop
生命周期給影響了驰怎。
ActivityTransitionState的enterReady代碼:
public void enterReady(Activity activity) {
if (mEnterActivityOptions == null || mIsEnterTriggered) {
return;
}
mIsEnterTriggered = true;
mHasExited = false;
ArrayList<String> sharedElementNames = mEnterActivityOptions.getSharedElementNames();
ResultReceiver resultReceiver = mEnterActivityOptions.getResultReceiver();
if (mEnterActivityOptions.isReturning()) {
restoreExitedViews();
activity.getWindow().getDecorView().setVisibility(View.VISIBLE);
}
// 在這里對mEnterTransitionCoordinator賦值了
// 在這里對mEnterTransitionCoordinator賦值了
// 在這里對mEnterTransitionCoordinator賦值了
mEnterTransitionCoordinator = new EnterTransitionCoordinator(activity,
resultReceiver, sharedElementNames, mEnterActivityOptions.isReturning(),
mEnterActivityOptions.isCrossTask());
if (mEnterActivityOptions.isCrossTask()) {
mExitingFrom = new ArrayList<>(mEnterActivityOptions.getSharedElementNames());
mExitingTo = new ArrayList<>(mEnterActivityOptions.getSharedElementNames());
}
if (!mIsEnterPostponed) {
startEnter();
}
}
ActivityTransitionState的onStop代碼:
public void onStop() {
restoreExitedViews();
if (mEnterTransitionCoordinator != null) {
mEnterTransitionCoordinator.stop();
mEnterTransitionCoordinator = null;
}
if (mReturnExitCoordinator != null) {
mReturnExitCoordinator.stop();
mReturnExitCoordinator = null;
}
}
在這里給同學們留一個問題阐滩,既然是被Activity的onStop方法影響了,那么為什么有些系統(tǒng)可以县忌,但是在Android 10系統(tǒng)上就不行了呢掂榔?這個問題留給大家去解答,因為最近項目非常忙症杏,
筆者也沒時間去深究這個問題了装获。
如何解決
既然找到了問題那么就好解決了,我們在Activity的onResume方法中調用一下ActivityTransitionState
的enterReady
方法厉颤,再次給mEnterTransitionCoordinator
賦值不久完事了嗎穴豫?
但是ActivityTransitionState
類是系統(tǒng)的私有類,開發(fā)者是不能直接調用的逼友,這時候我們就想到了Java的反射大法精肃,是的筆者就是通過反射調用的。
在編寫反射調用ActivityTransitionState
的enterReady
方法時候AS提示targetSdkVersion
是28以上的會得到反射異常帜乞。這是因為Android 9以上對反射做了限制司抱,這時候我們只需要將targetSdkVersion
設置成28以下的即可。
主要代碼:
/**
* Android10 Activity的onStop方法可能會導致共享元素動畫失效黎烈,通過反射注入恢復共享元素動畫
* @param activity
*/
public static void updateResume(Activity activity){
try {
Field activityTransitionStateField = Activity.class.getDeclaredField("mActivityTransitionState");
activityTransitionStateField.setAccessible(true);
Object mActivityTransitionState = activityTransitionStateField.get(activity);
Class clazz = Class.forName("android.app.ActivityTransitionState");
Method enterReady = clazz.getDeclaredMethod("enterReady",Activity.class);
enterReady.setAccessible(true);
enterReady.invoke(mActivityTransitionState,activity);
} catch (Exception e) {
e.printStackTrace();
}
}
然后在Activity的onResume方法中調用一下反射方法即可习柠。實測返回是共享元素動畫正常,但是會導致什么未知bug目前還不可知怨喘,歡迎大家深究討論津畸。。必怜。
關注我肉拓,一起學習,不止于技術J崆臁E尽卑惜!