共享元素動(dòng)畫在Android10上異常的解決方案(全文唯一僅此一份)

問(wèn)題描述

最近在項(xiàng)目中遇到一個(gè)關(guān)于共享元素動(dòng)畫失效的問(wèn)題,在Activity跳轉(zhuǎn)時(shí)使用ActivityOptionsCompat.makeSceneTransitionAnimation做轉(zhuǎn)場(chǎng)動(dòng)畫,
對(duì)于Activity共享元素動(dòng)畫的使用不懂的童鞋自行谷歌,網(wǎng)上資料太多,這里就不做描述了。

一般情況下使用ActivityOptionsCompat.makeSceneTransitionAnimation做轉(zhuǎn)場(chǎng)動(dòng)畫時(shí),當(dāng)Activity返回時(shí)也會(huì)伴隨返回的動(dòng)畫。但是就是這樣的一個(gè)簡(jiǎn)單的API,
居然在Android 10上測(cè)試出現(xiàn)了異常:

如果使用了ActivityOptionsCompat.makeSceneTransitionAnimation跳轉(zhuǎn)的Activity沒(méi)有再次跳轉(zhuǎn)到別的Activity的話,返回時(shí)系統(tǒng)是帶上響應(yīng)的共享元素動(dòng)畫的,
但是如果跳轉(zhuǎn)的Activity再次跳轉(zhuǎn)了其他的Activity,然后再返回的話共享元素的返回動(dòng)畫就失效了(包括按了Home鍵退回桌面,然后再次進(jìn)入也會(huì)失效)。

筆者的測(cè)試手機(jī)是華為nova 5i,系統(tǒng)是Android 10

問(wèn)題追蹤

1、 大神方案

當(dāng)筆者看到這個(gè)bug的時(shí)候,首先想到的可能是我打開(kāi)的方式不對(duì),于是谷歌百度了一波,結(jié)果確實(shí)無(wú)功而返。

于是筆者第一想到的就是是不是自己的使用方式不對(duì),于是看看大神是怎么使用的,然后參考了一下Android 大神郭霖開(kāi)源的一個(gè)開(kāi)源項(xiàng)目giffun
發(fā)現(xiàn)giffun也存在同樣的異常...

為此我專門給giffun提了一個(gè)issue:

https://github.com/guolindev/giffun/issues/67

絕望了。。。。。。。

2、 競(jìng)品對(duì)比

既然查找不到相關(guān)的資料,那么就看看競(jìng)品是否也有這樣的問(wèn)題的,如果競(jìng)品也有這樣的問(wèn)題話就可以拿著競(jìng)品去忽悠產(chǎn)品了,心里美滋滋。

于是筆者對(duì)比了一下競(jìng)品的產(chǎn)品是否也存在這么的一個(gè)bug,發(fā)現(xiàn)競(jìng)品沒(méi)有一樣的bug,它們的共享元素動(dòng)畫一切正常。
后面通過(guò)研究了它們的APK才發(fā)現(xiàn)它們的共享元素動(dòng)畫是使用Fragment做的,所以盡量使用Fragment的一個(gè)好處又體現(xiàn)出來(lái)了。

但是筆者今天要說(shuō)的將Activity的轉(zhuǎn)場(chǎng)動(dòng)畫改為Fragment這樣就完事了,今天將帶大家一步一步分析共享元素動(dòng)畫是如何失效的,如何修復(fù)這樣的一個(gè)bug。

3、 源碼先行

既然網(wǎng)上沒(méi)有這樣的資料,那就自己動(dòng)手,豐衣足食了。首先我們初步看看Activity的源碼,發(fā)現(xiàn)如果是返回帶執(zhí)行共享元素動(dòng)畫的話執(zhí)行的方法是finishAfterTransition。
finishAfterTransition里面調(diào)用了ActivityTransitionStatestartExitBackTransition方法。

然而看源碼并沒(méi)有看出什么破綻,畢竟大多數(shù)手機(jī)是正常的,只有在Android 10上才出現(xiàn)了異常。既然這條路走不通,那我們就換一個(gè)途徑吧。

4、 Debug大法

我們使用Android Studio的斷點(diǎn)調(diào)試功能,在ActivityTransitionStatestartExitBackTransition方法里面打斷點(diǎn)調(diào)試,
對(duì)比發(fā)現(xiàn)最終返回時(shí)沒(méi)有執(zhí)行共享元素動(dòng)畫的原因是startExitBackTransition方法內(nèi)的pendingExitNames變量為空就直接返回了false,也就是不執(zhí)行共享元素動(dòng)畫。

5、 萬(wàn)變不離其宗,再次回到源碼

那么為什么pendingExitNames變量會(huì)變成了空呢?我們?cè)俅位氐皆创a分析,pendingExitNames變量是通過(guò)
ActivityTransitionStatestartExitBackTransitiongetPendingExitNames方法獲取的,方法如下:

    if (mPendingExitNames == null && mEnterTransitionCoordinator != null) {
            mPendingExitNames = mEnterTransitionCoordinator.getPendingExitSharedElementNames();
        }
        return mPendingExitNames;
    }

很簡(jiǎn)單的代碼,如果mEnterTransitionCoordinator是空的話,那么getPendingExitNames方法必定會(huì)返回空。

那么我們繼續(xù)追蹤一下mEnterTransitionCoordinator變量是如何賦值的,跟蹤發(fā)現(xiàn)是在ActivityTransitionStateenterReady(Activity activity)方法
中對(duì)mEnterTransitionCoordinator做了賦值操作,然后在ActivityTransitionStateonStop方法中被置空了,而ActivityTransitionStateonStop方法
又被ActivityonStop方法調(diào)用了,至此大概就能解析的解析得通為什么經(jīng)過(guò)跳轉(zhuǎn)后Activity的返回共享元素失效了,原來(lái)是被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);
        }

        // 在這里對(duì)mEnterTransitionCoordinator賦值了
        // 在這里對(duì)mEnterTransitionCoordinator賦值了
        // 在這里對(duì)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;
        }
    }

在這里給同學(xué)們留一個(gè)問(wèn)題,既然是被Activity的onStop方法影響了,那么為什么有些系統(tǒng)可以,但是在Android 10系統(tǒng)上就不行了呢?這個(gè)問(wèn)題留給大家去解答,因?yàn)樽罱?xiàng)目非常忙,
筆者也沒(méi)時(shí)間去深究這個(gè)問(wèn)題了。

如何解決

既然找到了問(wèn)題那么就好解決了,我們?cè)贏ctivity的onResume方法中調(diào)用一下ActivityTransitionStateenterReady方法,再次給mEnterTransitionCoordinator賦值不久完事了嗎?

但是ActivityTransitionState類是系統(tǒng)的私有類,開(kāi)發(fā)者是不能直接調(diào)用的,這時(shí)候我們就想到了Java的反射大法,是的筆者就是通過(guò)反射調(diào)用的。

在編寫反射調(diào)用ActivityTransitionStateenterReady方法時(shí)候AS提示targetSdkVersion是28以上的會(huì)得到反射異常。這是因?yàn)锳ndroid 9以上對(duì)反射做了限制,這時(shí)候我們只需要將targetSdkVersion設(shè)置成28以下的即可。

主要代碼:


 /**
     * Android10 Activity的onStop方法可能會(huì)導(dǎo)致共享元素動(dòng)畫失效,通過(guò)反射注入恢復(fù)共享元素動(dòng)畫
     * @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方法中調(diào)用一下反射方法即可。實(shí)測(cè)返回是共享元素動(dòng)畫正常,但是會(huì)導(dǎo)致什么未知bug目前還不可知,歡迎大家深究討論。。。

關(guān)注我,一起學(xué)習(xí),不止于技術(shù)!?。?/p>

image
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容