Android 8.0/8.1 screenOrientation适配

Android 8.0/8.1 screenOrientation适配

  • 一.问题
  • 二.原因
    • 1.Android 8.0
    • 2.Android 8.1
    • 3.Android8.0以下和Android9.0
    • 4.总结
  • 三.解决
    • 1.设置targetSdkVersion为26及以下
    • 2.修改Activity的style
    • 3.修改Activity的screenOrientation

一.问题

在适配完Android9.0,即target升级到28后运行app在8.0设备上,发现crash

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.FragmentActivity.onCreate(FragmentActivity.java:278)
	at android.support.v7.app.AppCompatActivity.onCreate(AppCompatActivity.java:84)
	at android.app.Activity.performCreate(Activity.java:7372)
	at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1218)
	at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3147)
	... 9 more

可以看到是Activity启动时,onCreate()方法里产生了一场,并且后Activity展示方向有关

二.原因

1.Android 8.0

于是我们跟踪一下Android8.0系统的Activity源码

protected void onCreate(@Nullable Bundle savedInstanceState) {
  //target>26(8.0),且Activity方向模式为固定方向
  if (getApplicationInfo().targetSdkVersion > O && mActivityInfo.isFixedOrientation()) {
    final TypedArray ta = obtainStyledAttributes(com.android.internal.R.styleable.Window);
    //Activity为Translucent或Floating模式
    final boolean isTranslucentOrFloating = ActivityInfo.isTranslucentOrFloating(ta);
    ta.recycle();
    if (isTranslucentOrFloating) {
      throw new IllegalStateException(
        "Only fullscreen opaque activities can request orientation");
    }
  }
  ...
}

可以看到,如果我们的app适配到了8.0以后(即target>26),如果Activity为固定方向模式且style为某些特定模式时,就会产生这个崩溃

那么什么是固定模式呢?

boolean isFixedOrientation() {
  return isFixedOrientationLandscape() || isFixedOrientationPortrait()
    || screenOrientation == SCREEN_ORIENTATION_LOCKED;
}

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;
}

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;
}

可以看到,固定模式是指LOCKED模式或所有与PORTRAIT和LANDSCAPE相关的模式

那特定模式又包括哪些呢?

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;
}

可以看到包括了Translucent、Floating、SwipeToDismiss三个模式

2.Android 8.1

然后我们也看了下8.1系统源码,发现了一样的问题

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");
  }
}

3.Android8.0以下和Android9.0

但是却发现运行在其他系统版本的设备(包括9.0)都没有问题,这是为什么呢?

我们跟踪了9.0和6.0的系统源码发现,根本没有这段代码。。。

protected void onCreate(@Nullable Bundle savedInstanceState) {
  if (DEBUG_LIFECYCLE) Slog.v(TAG, "onCreate " + this + ": " + savedInstanceState);

  if (mLastNonConfigurationInstances != null) {
    mFragments.restoreLoaderNonConfig(mLastNonConfigurationInstances.loaders);
  }
  if (mActivityInfo.parentActivityName != null) {
    if (mActionBar == null) {
      mEnableDefaultActionBarUp = true;
    } else {
      mActionBar.setDefaultDisplayHomeAsUpEnabled(true);
    }
  }
  ...
}

4.总结

综上所述,结合网上给出解释,我们可以得出结论:

  1. Android在8.0和8.1系统上,规定了当target > 26时,Activity的style如果为几种特定模式下,则他的屏幕方向不能被设置为固定方向相关的模式;事实上,设置为sensor等方向虽然不会有crash,但也无法正常旋转

  2. 在Android8.0以前的系统上没有做次限制,且这是一个不科学的设计,于是在9.0上又改了回来

  3. 这个问题,官网开发者文档上又没有写。。。。

三.解决

那么这个坑爹的问题怎么解决呢?

1.设置targetSdkVersion为26及以下

这个方案只是临时方案,因为迟早要适配到26以上,绕不过去这个问题

2.修改Activity的style

可以的情况下,让Activity的Translucent、Floating、SwipeToDismiss模式为false

3.修改Activity的screenOrientation

如果Activity的style必须为上述几个模式之一,那么这种情况下,就不能设置Activity的screenOrientation为固定方向相关的模式了,当然,manifest里和代码里requestOrientation()方法都不可以

因此,我们可以在manifest里将Activity的screenOrientation设置删除掉,转而在代码里进行适配设置

public static void fixedOrientation(@NonNull Activity activity) {
  if (Build.VERSION.SDK_INT != Build.VERSION_CODES.O && Build.VERSION.SDK_INT != Build.VERSION_CODES.O_MR1) {
    activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
  }
}

但是这种方案的弊端就是,在8.0和8.1设备上,一些需要非纵向展示的Activity就无法正常展示了,遇到这种情况,还是考虑一下前两种方案吧,着实坑啊。。。

不过经过多次测试,发现部分厂商的定制系统里修改了这个问题,不过还是有大量有问题的机型。

你可能感兴趣的:(疑难杂症,原理解析,android相关)