一、背景:
使用 Android 8.0 手机测试应用自升级功能,安装包下载完成之后应用崩溃了...
震惊之余,赶紧去查看崩溃堆栈。
下面是异常堆栈:
java.lang.RuntimeException: Unable to start activity ComponentInfo:
java.lang.IllegalStateException: Only fullscreen activities can request orientation
Activity 打开异常,这也太心累了。当务之急,还是先查清原因吧。
二、当前项目状况:
1. 项目中 BaseActivity 的 onCreate() 方法通过代码锁定了手机屏幕方向:
public class BaseActivity extends AppCompatActivity {
@CallSuper
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
//默认设置为竖屏
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
}
2. 项目的 targetSdkVersion 为 28 > 26:
android = [compileSdkVersion : 28,
...
targetSdkVersion : 28,
...
"android.support.test.runner.AndroidJUnitRunner"]
3. InstallApkActivity 的主题样式设置了 Window 窗口半透明。
<style name="BaseTranslucentTheme" parent="Theme.AppCompat.Light.NoActionBar">
<item name="android:windowBackground">#00000000</item>
<item name="android:windowIsTranslucent">true</item>//设置窗口半透明
</style>
种种条件叠加起来,导致遇到官方系统做的检查,抛出以下异常:
java.lang.IllegalStateException: Only fullscreen activities can request orientation
意思是说:只有不透明的全屏 Activity 可以自主设置界面方向。
三、分析:
1. 通过查看 AOSP 的提交:Prevent non-fullscreen activities from influencing orientation,这是 Activity 的源码,可以看到抛出该异常的地方:
@MainThread
@CallSuper
protected void onCreate(@Nullable Bundle savedInstanceState) {
...
if (getApplicationInfo().targetSdkVersion > O && 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");
}
}
...
}
即当屏幕方向锁定了并且不为全屏,而且应用的 targetSdkVersion 大于 Android O 的话,就会抛出这个异常。
2. fullscreen 有多个条件控制
Entry ent = AttributeCache.instance().get(packageName,
realTheme, com.android.internal.R.styleable.Window, userId);
fullscreen = ent != null && !ActivityInfo.isTranslucentOrFloating(ent.array);
3. 展开 isTranslucentOrFloating() 方法
// Determines whether the {@link Activity} is considered translucent or floating.public static boolean isTranslucentOrFloating(TypedArray attributes) {
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;
}
即只要满足 isFloating、isTranslucent、isSwipeToDismiss 条件其中之一都不算 fullscreen,目的是不让非全屏或透明的界面决定手机屏幕的朝向,因为透明的界面能透视背景界面。
4. 测试的时候使用 Android 7.1 / 9.0 / 10.0 的手机没有触发这个问题,是因为这代码是在 8.0 版本引入的,而且问题已经在 8.1 版本修复了。具体可以查看这笔提交:DO NOT MERGE Remove orientation restriction to only fullscreen activities.
四、解决方法:
最直接的解决方法就是打破联合条件之一:
-
降级 targetSdkVersion 使其不超过 26(不推荐)
-
移除 Activity 的 screenOrientation 属性以及移除显式指定屏幕方向的代码
-
设置 Activity 主题样式里的 windowIsTranslucent / windowSwipeToDismiss / windowIsFloating 属性为 false
<item name="android:windowIsTranslucent">false</item>
<item name="android:windowSwipeToDismiss">false</item>
<item name="android:windowIsFloating">false</item>
- 在 Activity 的 onCreate 方法中使用 try - catch 捕获异常
protected void onCreate(Bundle savedInstanceState) {
try {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
} catch (Exception e) {
Log.e(TAG, e);
}
super.onCreate(savedInstanceState);
}
我们应该根据自己的业务需要来选择和制定方案。
五、总结:
官方做这个改动的目的是想阻止非全屏的 Activity 锁定屏幕旋转,因为当前 Activity 是透明或浮动或可滑动取消,是否锁定屏幕朝向应该由全屏的 Activity 决定,而不是没有全部占据屏幕的 Activity 决定。
但个人看来,官方在处理这个问题上过于粗暴了。正常来说,检查全屏和屏幕方向条件后,应该先警告开发者,且忽略已经指定的设置,保证应用运行时兼容性。结果现在二话不说就抛出异常,有点不太厚道了,我想这也是官方在 8.1 就去掉这段代码的原因。因此如果测试没有覆盖 Android 8.0 版本,或者依赖第三方 SDK 引起问题,可能会导致严重后果。
网友评论