背景
AndroidRuntime: java.lang.RuntimeException: Unable to start activity ComponentInfo
{包名/目標(biāo)路徑Activity}
: java.lang.IllegalStateException: Only fullscreen opaque activities can request orientation
項(xiàng)目的線上Firebase監(jiān)控和monkey都指向了 在8.0機(jī)型上啟動(dòng)XXActivity會(huì)crash,無(wú)法啟動(dòng)目標(biāo)Activity,下面會(huì)結(jié)合源碼分析異常拋出原因。
源碼分析
異常拋出位置:源碼路徑,在8.0.0-r4上

ActivityRecord #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");
}
...
}
同時(shí)滿足這三個(gè)條件就會(huì)拋出此異常
注釋2:非全屏
!fullscreen注釋3:targetSdkVersion的設(shè)置為大于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;
}
可見只要手動(dòng)固定上面這些位置標(biāo)記,isFixedOrientation都會(huì)返回true , 都會(huì)被檢查
fullscreen的賦值
- 賦值是在ActivityRecord的構(gòu)造函數(shù),調(diào)用了靜態(tài)方法 ActivityInfo#isTranslucentOrFloating

- 繼續(xù)看這個(gè)函數(shù) 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;
}
根據(jù)上面的定義,如果一個(gè)Activity的Style符合下面三個(gè)條件之一,則認(rèn)為不是“fullscreen”:
“windowIsTranslucent”為true;
“windowIsTranslucent”為false,但“windowSwipeToDismiss”為true;
“windowIsFloating“為true;
解決方案
由于谷歌官方已經(jīng)在后續(xù)版本刪除這個(gè)邏輯修復(fù)了,且Android 8占比已經(jīng)很少,那么我們就按照這個(gè)觸發(fā)條件,手動(dòng)去規(guī)避。
-
檢查Activity的style是否可以不設(shè)置成透明,以至于不觸發(fā)fullscreen
-
移除orientation顯示固定標(biāo)記
1.在manifest中移除
android:screenOrientation="portrait"
2.Activity#oncreate()中動(dòng)態(tài)設(shè)置
if (Build.VERSION.SDK_INT != Build.VERSION_CODES.O) {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
}
-
全局hook反射,設(shè)置屏幕不固定 ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;