前言

不得不说 Google 的适配是真的烦。真的是一不小心各种坑,市面上的 Android 版本也很多,各种各样的屏幕也很多。最近做的项目在自己手上测试的都还行,直接丢给客户测试的时候,客户手机是 Android 8.0 的,直接崩溃了。异常如下

1
Only fullscreen activities can request orientation

在源码中看看是为什么报的异常吧

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
  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");
            }
        }

从上述可以看到条件,大于了 Android O 也就是 target 26 , 加上如果是固定了屏幕就会报出异常

一共有3个条件

  • targetSdkVersion
  • isFixedOrientation ()
  • ActivityInfo.isTranslucentOrFloating(ta)

再看看决定的条件是什么

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
  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 透明

1
2
3
4
public boolean isFixedOrientation() {
        return isFixedOrientationLandscape() || isFixedOrientationPortrait()
                || screenOrientation == SCREEN_ORIENTATION_LOCKED;
    }

是否是设置了横屏和竖屏或者锁定了 screenOrientation == SCREEN_ORIENTATION_LOCKED

好了,结论已经出来了,target 这个先放弃了,因为现在大部分的开发者都适配高版本了。然后要么就是不设置 Activity 透明。要么就是不设置屏幕方法,或者让 screenOrientation 不等于 SCREEN_ORIENTATION_LOCKED

关于这个异常 Google 已经在 8.1 中解决了,但是市面上还是有很多 8.0 的手机的,也还是要解决。而我的项目中透明这个是要的。也就只能修改 screenOrientation == SCREEN_ORIENTATION_LOCKED 这个条件了

解决方案

修改 screenOrientation == SCREEN_ORIENTATION_UNSPECIFIED 由于我项目采用的是组件化 theme 过多,错综复杂,不太好手动在清单文件中修改,所以我采用了反射的方式,根据版本去反射对应的 API

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
public class ActivityHook {


    /**
     * java.lang.IllegalStateException: Only fullscreen opaque activities can request orientation
     * <p>
     * 修复android 8.0存在的问题
     * <p>
     * 在Activity中onCreate()中super之前调用
     *
     * @param activity
     */
    public static void hookOrientation(Activity activity) {
        //目标版本8.0及其以上
        if (activity.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.O) {
            if (isTranslucentOrFloating(activity)) {
                fixOrientation(activity);
            }
        }
    }

    /**
     * 设置屏幕不固定,绕过检查
     *
     * @param activity
     */
    private static void fixOrientation(Activity activity) {
        try {
            Class<Activity> activityClass = Activity.class;
            Field mActivityInfoField = activityClass.getDeclaredField("mActivityInfo");
            mActivityInfoField.setAccessible(true);
            ActivityInfo activityInfo = (ActivityInfo) mActivityInfoField.get(activity);
            //设置屏幕不固定
            activityInfo.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 检查屏幕 横竖屏或者锁定就是固定
     *
     * @param activity
     * @return
     */
    private static boolean isTranslucentOrFloating(Activity activity) {
        boolean isTranslucentOrFloating = false;
        try {
            Class<?> styleableClass = Class.forName("com.android.internal.R$styleable");
            Field WindowField = styleableClass.getDeclaredField("Window");
            WindowField.setAccessible(true);
            int[] styleableRes = (int[]) WindowField.get(null);
            //先获取到TypedArray
            final TypedArray typedArray = activity.obtainStyledAttributes(styleableRes);
            Class<?> ActivityInfoClass = ActivityInfo.class;
            //调用检查是否屏幕旋转
            Method isTranslucentOrFloatingMethod = ActivityInfoClass.getDeclaredMethod("isTranslucentOrFloating", TypedArray.class);
            isTranslucentOrFloatingMethod.setAccessible(true);
            isTranslucentOrFloating = (boolean) isTranslucentOrFloatingMethod.invoke(null, typedArray);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return isTranslucentOrFloating;
    }
}

### 在需要的Activity中使用

1
2
3
4
5
6
7
8
@Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
         
         ActivityHook.hookOrientation(this);//hook,绕过检查
         
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_xxxx);
    }

参考博客:

https://www.jianshu.com/p/8328a586f9de

https://blog.csdn.net/starry_eve/article/details/82777160