背景
AndroidRuntime: java.lang.RuntimeException: Unable to start activity ComponentInfo
{包名/目标路径Activity}
: java.lang.IllegalStateException: Only fullscreen opaque activities can request orientation
项目的线上Firebase监控和monkey都指向了 在8.0机型上启动XXActivity会crash,无法启动目标Activity,下面会结合源码分析异常抛出原因。
源码分析
异常抛出位置:源码路径,在8.0.0-r4上
源码路径.pngActivityRecord #setRequestedOrientation()
frameworks/base/services/core/java/com/android/server/am/ActivityRecord.java
void setRequestedOrientation(int requestedOrientation) {
if (ActivityInfo.isFixedOrientation(requestedOrientation) //注释1
&& !fullscreen //注释2
&& appInfo.targetSdkVersion > O) { //注释3
throw new IllegalStateException("Only fullscreen activities can request orientation");
}
...
}
同时满足这三个条件就会抛出此异常
-
注释2:非全屏
!fullscreen
-
注释3:targetSdkVersion的设置为大于26
appInfo.targetSdkVersion > O
-
注释1:Activity是否显示固定方向,ActivityInfo.isFixedOrientation(requestedOrientation)
ActivityInfo.isFixedOrientation
frameworks/base/core/java/android/content/pm/ActivityInfo.java
/**
* Returns true if the activity's orientation is fixed.
* @hide
*/
public boolean isFixedOrientation() {
return isFixedOrientationLandscape() || isFixedOrientationPortrait()
|| screenOrientation == SCREEN_ORIENTATION_LOCKED;
}
/**
* Returns true if the specified orientation is considered fixed.
* @hide
*/
static public boolean isFixedOrientation(int orientation) {
return isFixedOrientationLandscape(orientation) || isFixedOrientationPortrait(orientation);
}
/**
* Returns true if the activity's orientation is fixed to landscape.
* @hide
*/
boolean isFixedOrientationLandscape() {
return isFixedOrientationLandscape(screenOrientation);
}
/**
* Returns true if the activity's orientation is fixed to landscape.
* @hide
*/
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;
}
/**
* Returns true if the activity's orientation is fixed to portrait.
* @hide
*/
boolean isFixedOrientationPortrait() {
return isFixedOrientationPortrait(screenOrientation);
}
/**
* Returns true if the activity's orientation is fixed to portrait.
* @hide
*/
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;
}
可见只要手动固定上面这些位置标记,isFixedOrientation都会返回true , 都会被检查
fullscreen
的赋值
- 赋值是在ActivityRecord的构造函数,调用了静态方法 ActivityInfo#isTranslucentOrFloating
- 继续看这个函数 isTranslucentOrFloating()
frameworks/base/core/java/android/content/pm/ActivityInfo.java
/**
* Determines whether the {@link Activity} is considered translucent or floating.
* @hide
*/
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;
}
根据上面的定义,如果一个Activity的Style符合下面三个条件之一,则认为不是“fullscreen”:
-
“windowIsTranslucent”为true;
-
“windowIsTranslucent”为false,但“windowSwipeToDismiss”为true;
-
“windowIsFloating“为true;
解决方案
由于谷歌官方已经在后续版本删除这个逻辑修复了,且Android 8占比已经很少,那么我们就按照这个触发条件,手动去规避。
-
检查Activity的style是否可以不设置成透明,以至于不触发fullscreen
-
移除orientation显示固定标记
1.在manifest中移除
android:screenOrientation="portrait"
2.Activity#oncreate()中动态设置
if (Build.VERSION.SDK_INT != Build.VERSION_CODES.O) {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
}
-
全局hook反射,设置屏幕不固定 ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
网友评论