Android 8.0 只有全屏不透明活动可以请求方向问题
1 背景
Android 8.0
,即sdk
为26
时,Android
为了支持全面屏系统增加了一个限制,如果是透明的Activity
,则不能固定它的方向,因为它的方向其实是依赖其父Activity
的(因为透明);- 因此产生了一个系统级别的
Bug
,当以下四个条件同时满足时会发生的崩溃: - 1)使用的是
Android 8.0
操作系统的设备; - 2)
targetSdkVersion
设置为27
以上; - 3)将背景设置为透明主题;
- 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;
}
}
- 通过阅读上述的源码可知,触发
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
以外的系统固定屏幕方向,去掉清单文件中screenOrientation
属性,activity
中onCreate
中执行屏幕方向固定的代码:
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)[推荐] 通过反射,让系统绕过屏幕方向的检测,设置屏幕不固定:
open class FixOreoOrientationActivity: AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
// 必须在 Activity#onCreate() 中 super 之前调用
fixOrientationBugForAndroidO()
super.onCreate(savedInstanceState)
}
/**
* 针对 Android 8.0 版本,如果 Activity 是透明或浮动的,则尝试修复屏幕方向设置的问题
* 异常日志如下:
* 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 是否成功修复 [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
// 设置屏幕方向不固定
activityInfo.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
field.isAccessible = false
true
} catch (e: Exception) {
e.printStackTrace()
false
}
}
/**
* 通过反射检查当前 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
}
}
/**
* 重写设置屏幕方向的方法
* 以便在 Android 8.0 且 Activity 为透明或浮动时,阻止设置屏幕方向
* @param orientation 屏幕方向
*/
override fun setRequestedOrientation(orientation: Int) {
if (Build.VERSION_CODES.O != Build.VERSION.SDK_INT || ! isTranslucentOrFloating()) {
super.setRequestedOrientation(orientation)
}
}
}
- 注意:
- 上述的
FixOreoOrientationActivity
代码为反编译懂车帝Android
代码中获取,由此可见在应用市场已得到有效验证; - 其次因为上代码涉及到反射,这些
API
在未来的Android
版本中可能会改变或不再可访问,这可能导致你的应用在未来的Android
版本上出现问题,所以需要关注Android
版本升级相关变更信息,从而持续维护该方法。
最后
如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。
如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。
欢迎大家一键三连支持,若需要文中资料,直接扫描文末CSDN官方认证微信卡片免费领取↓↓↓(文末还有ChatGPT机器人小福利哦,大家千万不要错过)
PS:群里还设有ChatGPT机器人,可以解答大家在工作上或者是技术上的问题