Android 8.0 只有全屏不透明活動可以請求方向問題
1 背景
-
Android 8.0,即 sdk 為 26 時,Android 為了支持全面屏系統(tǒng)增加了一個限制,如果是透明的 Activity,則不能固定它的方向,因?yàn)樗姆较蚱鋵?shí)是依賴其父 Activity 的(因?yàn)橥该鳎?/li>
- 因此產(chǎn)生了一個系統(tǒng)級別的
Bug,當(dāng)以下四個條件同時滿足時會發(fā)生的崩潰:
- 1)使用的是
Android 8.0 操作系統(tǒng)的設(shè)備;
- 2)
targetSdkVersion 設(shè)置為 27 以上;
- 3)將背景設(shè)置為透明主題;
- 4)固定屏幕方向,
screenOrientation 的值為 portrait 或者 landscape(代碼或者清單文件);
- 崩潰信息如下所示:
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.SupportActivity.onCreate(SupportActivity.java:66)
at android.support.v4.app.FragmentActivity.onCreate(FragmentActivity.java:297)
at android.support.v7.app.AppCompatActivity.onCreate(AppCompatActivity.java:84)
at xxx.xxx.xxx.ui.XxxActivity.onCreate(XxxActivity.java:43)
at android.app.Activity.performCreate(Activity.java:7372)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1218)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3147)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3302)
at android.app.ActivityThread.-wrap12(Unknown Source:0)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1891)
at android.os.Handler.dispatchMessage(Handler.java:108)
at android.os.Looper.loop(Looper.java:166)
2 分析
- 通過
Only fullscreen opaque activities can request orientation 報錯信息可知,這是 Android 8.0 Activit 的源碼所拋出的異常錯誤信息,源碼如下所示:
public class Activity {
protected void onCreate(@Nullable Bundle savedInstanceState) {
// ...
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");
}
}
// ...
}
}
public class ActivityInfo {
public boolean isFixedOrientation() {
return isFixedOrientationLandscape() || isFixedOrientationPortrait()
|| screenOrientation == SCREEN_ORIENTATION_LOCKED;
}
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;
}
}
- 通過閱讀上述的源碼可知,觸發(fā)
IllegalStateException 異常的條件有:
- 1)
Activity 屏幕方向固定,windowIsTranslucent = true;
- 2)
Activity 屏幕方向固定,windowIsTranslucent = false, windowSwipeToDismiss = true;
- 3)
Activity 屏幕方向固定,windowIsFloating = true。
3 解決思路
- 1)[不推薦] 暴力回退
sdk 版本,即 sdk <= 26;
- 2)[不推薦] 去除主題中的透明屬性,需求允許的話:
<item name="android:windowIsTranslucent">false</item>
- 3)[不推薦] 指定除
8.0 以外的系統(tǒng)固定屏幕方向,去掉清單文件中 screenOrientation 屬性,activity 中 onCreate 中執(zhí)行屏幕方向固定的代碼:
if (android.os.Build.VERSION.SDK_INT != android.os.Build.VERSION_CODES.O) {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
// setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
}
- 4)[不推薦] 如果你前一個頁面和需要透明主題的界面屏幕方向一致,我們只需要在清單文件中配置
android:screenOrientation="behind",behind 的意思就是和之前頁面的屏幕方向保持一致;
- 5)[推薦] 通過反射,讓系統(tǒng)繞過屏幕方向的檢測,設(shè)置屏幕不固定:
open class FixOreoOrientationActivity: AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
// 必須在 Activity#onCreate() 中 super 之前調(diào)用
fixOrientationBugForAndroidO()
super.onCreate(savedInstanceState)
}
/**
* 針對 Android 8.0 版本,如果 Activity 是透明或浮動的,則嘗試修復(fù)屏幕方向設(shè)置的問題
* 異常日志如下:
* 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.SupportActivity.onCreate(SupportActivity.java:66)
* at android.support.v4.app.FragmentActivity.onCreate(FragmentActivity.java:297)
* at android.support.v7.app.AppCompatActivity.onCreate(AppCompatActivity.java:84)
* at xxx.xxx.xxx.ui.XxxActivity.onCreate(XxxActivity.java:43)
* at android.app.Activity.performCreate(Activity.java:7372)
* at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1218)
* at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3147)
* at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3302)
* at android.app.ActivityThread.-wrap12(Unknown Source:0)
* at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1891)
* at android.os.Handler.dispatchMessage(Handler.java:108)
* at android.os.Looper.loop(Looper.java:166)
*/
private fun fixOrientationBugForAndroidO() {
if (Build.VERSION_CODES.O == Build.VERSION.SDK_INT && isTranslucentOrFloating()) {
fixOrientation()
}
}
/**
* 通過反射獲取 ActivityInfo,并嘗試修改 screenOrientation 屬性以避免崩潰
* @return Boolean 是否成功修復(fù) [true:成功 false:失敗]
*/
@SuppressLint("DiscouragedPrivateApi")
private fun fixOrientation(): Boolean {
return try {
val field: Field = Activity::class.java.getDeclaredField("mActivityInfo")
field.isAccessible = true
val activityInfo: ActivityInfo = field.get(this) as ActivityInfo
// 設(shè)置屏幕方向不固定
activityInfo.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
field.isAccessible = false
true
} catch (e: Exception) {
e.printStackTrace()
false
}
}
/**
* 通過反射檢查當(dāng)前 Activity 是否為透明或浮動
* @return Boolean 是否為透明或浮動 [true:是透明或浮動 false:不是透明或浮動]
*/
@SuppressLint("PrivateApi")
private fun isTranslucentOrFloating(): Boolean {
return try {
val styleableClass: Class<*> = Class.forName("com.android.internal.R\$styleable")
val windowField: Field = styleableClass.getField("Window")
val styleableAttrs: IntArray = windowField.get(null) as IntArray
val typedArray: TypedArray = obtainStyledAttributes(styleableAttrs)
val activityInfoClass: Class<*> = ActivityInfo::class.java
val method: Method = activityInfoClass.getMethod("isTranslucentOrFloating", TypedArray::class.java)
method.isAccessible = true
val isTranslucentOrFloating: Boolean = method.invoke(null, typedArray) as Boolean
method.isAccessible = false
typedArray.recycle()
isTranslucentOrFloating
} catch (e: Exception) {
e.printStackTrace()
false
}
}
/**
* 重寫設(shè)置屏幕方向的方法
* 以便在 Android 8.0 且 Activity 為透明或浮動時,阻止設(shè)置屏幕方向
* @param orientation 屏幕方向
*/
override fun setRequestedOrientation(orientation: Int) {
if (Build.VERSION_CODES.O != Build.VERSION.SDK_INT || ! isTranslucentOrFloating()) {
super.setRequestedOrientation(orientation)
}
}
}
-
注意:
- 上述的
FixOreoOrientationActivity 代碼為反編譯懂車帝 Android 代碼中獲取,由此可見在應(yīng)用市場已得到有效驗(yàn)證;
- 其次因?yàn)樯洗a涉及到反射,這些
API 在未來的 Android 版本中可能會改變或不再可訪問,這可能導(dǎo)致你的應(yīng)用在未來的 Android 版本上出現(xiàn)問題,所以需要關(guān)注 Android 版本升級相關(guān)變更信息,從而持續(xù)維護(hù)該方法。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。