Android 8.0 只有全屏不透明活动可以请求方向问题

Android 8.0 只有全屏不透明活动可以请求方向问题

1 背景

  • Android 8.0,即 sdk26 时,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 = falsewindowSwipeToDismiss = true
  • 3)Activity 屏幕方向固定,windowIsFloating = true

3 解决思路

  • 1)[不推荐] 暴力回退 sdk 版本,即 sdk <= 26
  • 2)[不推荐] 去除主题中的透明属性,需求允许的话:
<item name="android:windowIsTranslucent">false</item>

  • 3)[不推荐] 指定除 8.0 以外的系统固定屏幕方向,去掉清单文件中 screenOrientation 属性,activityonCreate 中执行屏幕方向固定的代码:
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开发的各个知识点。
img
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。

欢迎大家一键三连支持,若需要文中资料,直接扫描文末CSDN官方认证微信卡片免费领取↓↓↓(文末还有ChatGPT机器人小福利哦,大家千万不要错过)

PS:群里还设有ChatGPT机器人,可以解答大家在工作上或者是技术上的问题
图片

  • 23
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值