Only fullscreen opaque activities can request orientation

June 26, 2019

问题

最近同事把 targetSdkVersion26 升到 28 后,原本以为没有兼容性问题,没真机检查就发测试版。结果内部试用 Android 8.0Android 8.1 手机打开 Activity 崩溃。

上报错误具体如下:

1
java.lang.RuntimeException:Unable to start activity ComponentInfo{com.xx.xx/com.xx.xxmessage.chat.ui.RoomActivity}: java.lang.IllegalStateException: Only fullscreen activities can request orientation

其他开发者也遇到相同问题:

状况

一直都在 Activity 基类的 onCreate() 中用代码锁定屏幕方向

1
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT

除了用代码指定会出现问题,在 AndroidMenifest 指定该参数也不能幸免:

1
android:screenOrientation="portrait"

这个问题并不仅因单个条件引起,因为界面要做右滑退出,所以Window背景必须设置为透明

1
<item name="android:windowIsTranslucent">true</item>

结果就碰到官方系统做的检查,出现标题的异常

排查

读AOSP的提交:Prevent non-fullscreen activities from influencing orientation,抽出代码如下:

1
2
3
4
5
if (ActivityInfo.isFixedOrientation(requestedOrientation)
        && !fullscreen
        && appInfo.targetSdkVersion >= O) {
    throw new IllegalStateException("Only fullscreen activities can request orientation");
}

fullscreen 有多个条件控制

1
2
3
4
Entry ent = AttributeCache.instance().get(packageName,
                realTheme, com.android.internal.R.styleable.Window, userId);

fullscreen = ent != null && !ActivityInfo.isTranslucentOrFloating(ent.array);

展开 isTranslucentOrFloating()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Determines whether the {@link Activity} is considered translucent or floating.
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;
}

只要满足 isFloatingisTranslucent!windowIsTranslucent && isSwipeToDismiss 条件其中之一都不算 fullscreen,目的是不让 非全屏透明 的界面决定手机界面的朝向,因为透明的界面能透视背景界面。

解决方案

解决这个问题就是打破联合条件之一:

  • 可移除已经制定的透明或半透明属性;
  • 或移除显式指定屏幕方向的代码;
  • targetSdkVersion 不超过 26

看法

从个人角度来看,官方在这个问题上的处理手段极为粗暴。正常来说,检查全屏和屏幕方向条件后, 应该先警告开发者,且忽略已经指定的设置,保证应用运行时兼容性。

结果现在非要粗暴抛出异常,非常不厚道。如果测试没有覆盖 Android 8.0 - Android 8.1,只验证 Android9.0,或者依赖第三方SDK引起问题,导致后果非常严重