關於Android Q平臺的support庫的配置

目前Google已經釋放了Android Q平臺的SDK, 有OEM手機廠商在新機器中已經使用了Android Q的系統, 一些App應用也已經開始適配了Android Q系統.本文檔主要說明在Android Studio工程中,如果target SDK適配的是Android Q系統時, Support library配置需要注意的一個潛在問題.

理論上support library的版本號可以低於target SDK的版本號, 比如target SDK是28(Android P系統), 可以配置support library爲27.0.1,但是最好還是根據Google官方文檔配置指定版本的support library.當target SDK升級到29(Android Q系統)並且support library是27.0.1時, Android Framework將會存在一個潛在的問題, 有可能會導致App產生bug. 這個Android底層的潛在問題是這樣的:

Support library27.0.1中的如下函數(FragmentManager.java):

/**
 * Returns an existing AnimationListener on an Animation or {@code null} if none exists.
 */
private static AnimationListener getAnimationListener(Animation animation) {
    AnimationListener originalListener = null;
    try {
        if (sAnimationListenerField == null) {
            sAnimationListenerField = Animation.class.getDeclaredField("mListener");
            sAnimationListenerField.setAccessible(true);
        }
        originalListener = (AnimationListener) sAnimationListenerField.get(animation);
    } catch (NoSuchFieldException e) {
        Log.e(TAG, "No field with the name mListener is found in Animation class", e);
    } catch (IllegalAccessException e) {
        Log.e(TAG, "Cannot access Animation's mListener field", e);
    }
    return originalListener;
}

運行在Android Q手機上時, 由於類Animation中不再存在mListener成員變量, 因此反射調用結果爲null. 該函數getAnimationListener被如下函數使用(FragmentManager.java):

/**
 * Animates the removal of a fragment with the given animator or animation. After animating,
 * the fragment's view will be removed from the hierarchy.
 *
 * @param fragment The fragment to animate out
 * @param anim The animator or animation to run on the fragment's view
 * @param newState The final state after animating.
 */
private void animateRemoveFragment(@NonNull final Fragment fragment,
        @NonNull AnimationOrAnimator anim, final int newState) {
    final View viewToAnimate = fragment.mView;
    final ViewGroup container = fragment.mContainer;
    container.startViewTransition(viewToAnimate);
    fragment.setStateAfterAnimating(newState);
    if (anim.animation != null) {
        Animation animation = anim.animation;
        fragment.setAnimatingAway(fragment.mView);
        AnimationListener listener = getAnimationListener(animation);
        animation.setAnimationListener(new AnimationListenerWrapper(listener) {
            @Override
            public void onAnimationEnd(Animation animation) {
                super.onAnimationEnd(animation);
                // onAnimationEnd() comes during draw(), so there can still be some
                // draw events happening after this call. We don't want to detach
                // the view until after the onAnimationEnd()
                container.post(new Runnable() {
                    @Override
                    public void run() {
                        container.endViewTransition(viewToAnimate); 
                        if (fragment.getAnimatingAway() != null) {
                            fragment.setAnimatingAway(null);
                            moveToState(fragment, fragment.getStateAfterAnimating(), 0, 0,
                                    false);
                        }
                    }
                });
            }
        });
        setHWLayerAnimListenerIfAlpha(viewToAnimate, anim);
        fragment.mView.startAnimation(animation);
    } else {
        Animator animator = anim.animator;
        fragment.setAnimator(anim.animator);
        animator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator anim) {
                container.endViewTransition(viewToAnimate);
                // If an animator ends immediately, we can just pretend there is no animation.
                // When that happens the the fragment's view won't have been removed yet.
                Animator animator = fragment.getAnimator();
                fragment.setAnimator(null);
                if (animator != null && container.indexOfChild(viewToAnimate) < 0) {
                    moveToState(fragment, fragment.getStateAfterAnimating(), 0, 0, false);
                }
            }
        });
        animator.setTarget(fragment.mView);
        setHWLayerAnimListenerIfAlpha(fragment.mView, anim);
        animator.start();
    }
}

AnimationListenerWrapper通過AnimationListener對象(函數getAnimationListener返回的)進行對象構造, 但是當傳入的這個參數爲null時,AnimationListenerWrapper對象將不會調用其onAnimationEnd方法, 而該方法中會對Fragment的狀態進行更新.Android Framework的這個情況將會導致App中使用Fragment時, 有可能存在功能異常(儘管App中的代碼邏輯沒有任何bug).

結論:
當Android工程的target SDK升級到Android Q系統時, 需要將support library配置爲28.0.0. 或者使用Androidx包. Androidx是support library的超集, support library28.0.0是support library的最後一個版本, 後續將使用Androidx實現相關功能.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章