您当前的位置:首页 > 随笔备忘

Only fullscreen activities can request orientation优雅解决方案

时间:2022-01-28 07:11:28 阅读数:10,759人阅读
版权声明:转载请注明出处,谢谢!
—— 星光不问赶路人,时光不负有心人。

问题

当我们把targetSdkVersion升级到26及以上,buildToolsVersion和相关的support library升级到26.0.1及以上,在Android 8.0(API level 26)上,部分Activity出现了一个莫名其妙的crash,异常信息如下:

		  java.lang.RuntimeException: Unable to start activity ComponentInfo: java.lang.IllegalStateException:
		  	Only fullscreen activities can request orientation
	  

问题原因

这个问题貌似已经被广泛的讨论了,最终我们锁定了April 26的一个commit:

Prevent non-fullscreen activities from influencing orientation · aosp-mirror/platform_frameworks_base@3979159

这个改动中抛出异常有关的代码如下:

	    if (getApplicationInfo().targetSdkVersion >= O && 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");
            }
        }
	  

基本的意思是说,当我们的项目targetSdkVersion在26及以上,当前Activity是isFixedOrientation的并且是isTranslucentOrFloating的则抛出异常。下面,我们看一下isFixedOrientation如何定义的:

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

很明显,上面的意思是如果当前Activity的屏幕方向是锁定的或固定横竖屏的,那么这个Activity就是isFixedOrientation的。继续看isTranslucentOrFloating:

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

根据上面的定义,如果Activity符合下面三个条件之一,那么就是isTranslucentOrFloating的:

1. “windowIsTranslucent”为true;

2. “windowIsTranslucent”为false,但“windowSwipeToDismiss”为true;

3. “windowIsFloating“为true;

问题修复

现在思路其实很明确,从上面可以看出,只要三个条件同时成立,才会抛出Only fullscreen opaque activities can request orientation异常。首先我们不可能去修改项目的targetSdkVersion配置,这个配置是我们项目中做好兼容与适配的SDK版本。其次我们也不能去修改每个Activity的windowIsTranslucent、windowSwipeToDismiss、windowIsFloating的属性,因为这会影响到每个Activity的效果。所以我们只有从isFixedOrientation着手了。我们来看,首先这个bug只有在Android8.0版本上才会出现, 所以我们只要针对Android8.0进行处理即可,即如果当前设备是Android8.0并且当前Activity是isTranslucentOrFloating的,那么就把屏幕方向改为未指定类型即SCREEN_ORIENTATION_UNSPECIFIED,这样isFixedOrientationPortrait条件就不会成立了。

		  @Override
		  protected void onCreate(Bundle savedInstanceState) {
			  if (Build.VERSION.SDK_INT == Build.VERSION_CODES.O && isTranslucentOrFloating()) {
					fixOrientation();
			  }
		  }

		  private boolean isTranslucentOrFloating() {
				ActivityInfo
				boolean isTranslucentOrFloating = false;
				try {
					int[] styleableRes = (int[]) Class.forName("com.android.internal.R$styleable").getField("Window").get(null);
					final TypedArray ta = obtainStyledAttributes(styleableRes);
					Method m = ActivityInfo.class.getMethod("isTranslucentOrFloating", TypedArray.class);
					m.setAccessible(true);
					isTranslucentOrFloating = (boolean) m.invoke(null, ta);
					m.setAccessible(false);
				} catch (Exception e) {
					e.printStackTrace();
				}
				return isTranslucentOrFloating;
			}

			private boolean fixOrientation() {
				try {
					Field field = Activity.class.getDeclaredField("mActivityInfo");
					field.setAccessible(true);
					ActivityInfo o = (ActivityInfo) field.get(this);
					o.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
					field.setAccessible(false);
					return true;
				} catch (Exception e) {
					e.printStackTrace();
				}
				return false;
			}

			@Override
			public void setRequestedOrientation(int requestedOrientation) {
				if (Build.VERSION.SDK_INT == Build.VERSION_CODES.O && isTranslucentOrFloating())
					return;
				super.setRequestedOrientation(requestedOrientation);
			}
	  

------转载请注明出处,感谢您对原创作者的支持------

有偿提供技术支持、Bug修复、项目外包、毕业设计、大小作业

Android学习小站

Q Q:1095817610

微信:jx-helu

邮箱:1095817610@qq.com

添加请备注"Android学习小站"