目前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實現相關功能.