在适配完Android9.0,即target升级到28后运行app在8.0设备上,发现crash
Caused by: java.lang.IllegalStateException: Only fullscreen opaque activities can request orientation
at android.app.Activity.onCreate(Activity.java:1081)
at android.support.v4.app.FragmentActivity.onCreate(FragmentActivity.java:278)
at android.support.v7.app.AppCompatActivity.onCreate(AppCompatActivity.java:84)
at android.app.Activity.performCreate(Activity.java:7372)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1218)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3147)
... 9 more
可以看到是Activity启动时,onCreate()方法里产生了一场,并且后Activity展示方向有关
于是我们跟踪一下Android8.0系统的Activity源码
protected void onCreate(@Nullable Bundle savedInstanceState) {
//target>26(8.0),且Activity方向模式为固定方向
if (getApplicationInfo().targetSdkVersion > O && mActivityInfo.isFixedOrientation()) {
final TypedArray ta = obtainStyledAttributes(com.android.internal.R.styleable.Window);
//Activity为Translucent或Floating模式
final boolean isTranslucentOrFloating = ActivityInfo.isTranslucentOrFloating(ta);
ta.recycle();
if (isTranslucentOrFloating) {
throw new IllegalStateException(
"Only fullscreen opaque activities can request orientation");
}
}
...
}
可以看到,如果我们的app适配到了8.0以后(即target>26),如果Activity为固定方向模式且style为某些特定模式时,就会产生这个崩溃
那么什么是固定模式呢?
boolean isFixedOrientation() {
return isFixedOrientationLandscape() || isFixedOrientationPortrait()
|| screenOrientation == SCREEN_ORIENTATION_LOCKED;
}
public static boolean isFixedOrientationLandscape(@ScreenOrientation int orientation) {
return orientation == SCREEN_ORIENTATION_LANDSCAPE
|| orientation == SCREEN_ORIENTATION_SENSOR_LANDSCAPE
|| orientation == SCREEN_ORIENTATION_REVERSE_LANDSCAPE
|| orientation == SCREEN_ORIENTATION_USER_LANDSCAPE;
}
public static boolean isFixedOrientationPortrait(@ScreenOrientation int orientation) {
return orientation == SCREEN_ORIENTATION_PORTRAIT
|| orientation == SCREEN_ORIENTATION_SENSOR_PORTRAIT
|| orientation == SCREEN_ORIENTATION_REVERSE_PORTRAIT
|| orientation == SCREEN_ORIENTATION_USER_PORTRAIT;
}
可以看到,固定模式是指LOCKED模式或所有与PORTRAIT和LANDSCAPE相关的模式
那特定模式又包括哪些呢?
public static boolean isTranslucentOrFloating(TypedArray attributes) {
final boolean isTranslucent =
attributes.getBoolean(com.android.internal.R.styleable.Window_windowIsTranslucent,
false);
final boolean isSwipeToDismiss = !attributes.hasValue(
com.android.internal.R.styleable.Window_windowIsTranslucent)
&& attributes.getBoolean(
com.android.internal.R.styleable.Window_windowSwipeToDismiss, false);
final boolean isFloating =
attributes.getBoolean(com.android.internal.R.styleable.Window_windowIsFloating,
false);
return isFloating || isTranslucent || isSwipeToDismiss;
}
可以看到包括了Translucent、Floating、SwipeToDismiss三个模式
然后我们也看了下8.1系统源码,发现了一样的问题
if (getApplicationInfo().targetSdkVersion >= O_MR1 && mActivityInfo.isFixedOrientation()) {
final TypedArray ta = obtainStyledAttributes(com.android.internal.R.styleable.Window);
final boolean isTranslucentOrFloating = ActivityInfo.isTranslucentOrFloating(ta);
ta.recycle();
if (isTranslucentOrFloating) {
throw new IllegalStateException(
"Only fullscreen opaque activities can request orientation");
}
}
但是却发现运行在其他系统版本的设备(包括9.0)都没有问题,这是为什么呢?
我们跟踪了9.0和6.0的系统源码发现,根本没有这段代码。。。
protected void onCreate(@Nullable Bundle savedInstanceState) {
if (DEBUG_LIFECYCLE) Slog.v(TAG, "onCreate " + this + ": " + savedInstanceState);
if (mLastNonConfigurationInstances != null) {
mFragments.restoreLoaderNonConfig(mLastNonConfigurationInstances.loaders);
}
if (mActivityInfo.parentActivityName != null) {
if (mActionBar == null) {
mEnableDefaultActionBarUp = true;
} else {
mActionBar.setDefaultDisplayHomeAsUpEnabled(true);
}
}
...
}
综上所述,结合网上给出解释,我们可以得出结论:
Android在8.0和8.1系统上,规定了当target > 26时,Activity的style如果为几种特定模式下,则他的屏幕方向不能被设置为固定方向相关的模式;事实上,设置为sensor等方向虽然不会有crash,但也无法正常旋转
在Android8.0以前的系统上没有做次限制,且这是一个不科学的设计,于是在9.0上又改了回来
这个问题,官网开发者文档上又没有写。。。。
那么这个坑爹的问题怎么解决呢?
这个方案只是临时方案,因为迟早要适配到26以上,绕不过去这个问题
可以的情况下,让Activity的Translucent、Floating、SwipeToDismiss模式为false
如果Activity的style必须为上述几个模式之一,那么这种情况下,就不能设置Activity的screenOrientation为固定方向相关的模式了,当然,manifest里和代码里requestOrientation()方法都不可以
因此,我们可以在manifest里将Activity的screenOrientation设置删除掉,转而在代码里进行适配设置
public static void fixedOrientation(@NonNull Activity activity) {
if (Build.VERSION.SDK_INT != Build.VERSION_CODES.O && Build.VERSION.SDK_INT != Build.VERSION_CODES.O_MR1) {
activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
}
}
但是这种方案的弊端就是,在8.0和8.1设备上,一些需要非纵向展示的Activity就无法正常展示了,遇到这种情况,还是考虑一下前两种方案吧,着实坑啊。。。
不过经过多次测试,发现部分厂商的定制系统里修改了这个问题,不过还是有大量有问题的机型。