Android Only fullscreen opaque activities can request orientation

原因

错误是由全屏透明 Activity 引起的

Caused by: java.lang.IllegalStateException: Only fullscreen opaque activities can request orientation
在这里插入图片描述手机是 华为荣耀8 Android 8.0
在这里插入图片描述

当时,为了解决不显示引导页,直接进入应用时,会闪屏的问题,引入了透明 Activity 样式

<style name="GuideActivityStyle" parent="ImmersionStatusBar">
	<item name="android:windowIsTranslucent">true</item>
	<item name="android:windowBackground">@android:color/transparent</item> 
</style>

这是 Google 出于安全的考虑,对 Android8.0 版本做的处理

当一个 Activity 固定方向并且是透明的,在8.0以后的版本中就会抛出异常。源码如下

Entry ent = AttributeCache.instance().get(packageName,realTheme, com.android.internal.R.styleable.Window, userId);
final boolean translucent = ent != null && (ent.array.getBoolean(com.android.internal.R.styleable.Window_windowIsTranslucent, false)|| (!ent.array.hasValue(
                com.android.internal.R.styleable.Window_windowIsTranslucent) &&  ent.array.getBoolean(com.android.internal.R.styleable.Window_windowSwipeToDismiss,false)));
fullscreen = ent != null && !ent.array.getBoolean(com.android.internal.R.styleable.Window_windowIsFloating, false) && !translucent;
fullscreen = ent != null && !ActivityInfo.isTranslucentOrFloating(ent.array);
noDisplay = ent != null && ent.array.getBoolean(com.android.internal.R.styleable.Window_windowNoDisplay, false);

if (ActivityInfo.isFixedOrientation(requestedOrientation) && !fullscreen && appInfo.targetSdkVersion > O) {
	throw new IllegalStateException("Only fullscreen activities can request orientation");
}

可以看出当 三个条件同时满足的时候,系统会抛出"Only fullscreen activities can request orientation"异常。

  1. ActivityInfo.isFixedOrientation(requestedOrientation)
    表示判断当前的 Activity是否固定了方向,true为固定了方向。
  2. fullscreen 表示Activity是否是透明的或者是否悬浮在Activity上,是透明的或者悬浮在Activity上fullscreen就等于false。以下三种情况认为不是“fullscreen“:
    • “windowIsTranslucent” 为 true;
    • “windowIsTranslucent” 为 false,但“windowSwipeToDismiss” 为 true;
    • “windowIsFloating“ 为 true;
  3. appInfo.targetSdkVersion > O 表示编译版本号大于26

所幸的是在 Android 9.0 的时候,已经删掉了这个限制了

解决方案

  1. windowSwipeToDismiss 禁止预览
    当启动一个activity或者应用的时候,系统会先加载一个window preview的UI来增加过渡效果,但是有时候这种效果体验并不好,比如用户自定义的白色界面,然后预览的黑色界面,这样就会出现所谓的闪屏
<style name="GuideActivityStyle" parent="ImmersionStatusBar">
    <item name="android:windowIsTranslucent">false</item>
    <item name="android:windowDisablePreview">true</item>
    <item name="android:windowBackground">@null</item>
</style>
  1. 使用占位图片
 <style name="SplashTheme" parent="AppTheme">
    <item name="android:windowBackground">@drawable/shape_launch</item>
	<item name="android:windowFullscreen">true</item>
</style>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
    android:opacity="opaque">
    <item android:drawable="@android:color/white"/>
    <item>
        <bitmap
            android:src="@mipmap/main_splash_bg"
            android:gravity="fill" />
    </item>
</layer-list>

2020-05-06 补充

问题

用了上面的方案,能解决部分手机兼容性问题。但上线之后,依旧有客户反馈,使用微信登录会导致闪退

2020-05-06 03:25:18 A/UncaughtException: **************************************************
Manufacturer: vivo, Model: vivo X9s L, API 27
PackageName: com.xxx.yyy.zzz
VersionName: 3.4.3, VersionCode: 91
java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
	at d.l.a.h.i(FragmentManagerImpl.java:3)
	at d.l.a.h.c(FragmentManagerImpl.java:36)
	at d.l.a.h.b(FragmentManagerImpl.java:38)
	at d.l.a.a.c(BackStackRecord.java:6)
	at com.xxx.yyy.zzz.activity.MainActivity.a(MainActivity.java:7)
	at g.l.a.a.a.b.run(Unknown Source:4)
	at android.os.Handler.handleCallback(Handler.java:790)
	at android.os.Handler.dispatchMessage(Handler.java:99)
	at android.os.Looper.loop(Looper.java:192)
	at android.app.ActivityThread.main(ActivityThread.java:6866)
	at java.lang.reflect.Method.invoke(Native Method)
	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:549)
	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:817)
**************************************************

原因

谷歌在安卓8.0版本时为了支持全面屏,增加了一个限制:如果是透明的Activity,则不能固定它的方向,因为它的方向其实是依赖其父Activity的(因为透明)。

这个bug只有在8.0中有,8.1中已经修复。具体crash有两种:

  1. Activity的风格为透明,在manifest文件中指定了一个方向,则在onCreate中crash
  2. Activity的风格为透明,如果调用setRequestedOrientation方法固定方向,则crash

解决方案

如果进onCreate的时候,如果判断是透明窗口风格,直接把屏幕朝向改为未指定类型即SCREEN_ORIENTATION_UNSPECIFIED就可以了,因为Activity是透明的,所以其方向依赖于父Activity,所以这个改动对结果不会产生任何影响。

至于很多透明Activity的代码中调用setRequestedOrientation,更好处理,项目不是有BaseActivity吗,我们直接判断如果是androidO版本,我们不调用它即可,结果也是等效的。

  1. 获取 com.android.internal.R$styleable.Window 这个stylable,stylable 是 R 的内部类,因此要获取到这个数组,就只能用反射调用。然后再通过反射 ActivityInfo#isTranslucentOrFloating() 这个方法,来判断 Activity 是否是透明或者浮动的。
  2. 利用反射,修改mActivityInfo中的变量screenOrientation,设置成 SCREEN_ORIENTATION_UNSPECIFIED
  3. override setRequestedOrientation 方法,直接return

代码如下

public class BaseActivity extends FragmentActivity {

    private static final String TAG = "BaseActivity";
    
	@Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        if(needCheckOrientation()) {
            boolean fixOrientation = fixOrientation();
            Log.i(TAG, "onCreate fixOrientation when Oreo, result = " + fixOrientation);

        }
        super.onCreate(savedInstanceState);
    }
    
	private boolean needCheckOrientation() {
	    return Build.VERSION.SDK_INT == Build.VERSION_CODES.O && isTranslucentOrFloating();
	}
	
	@Override
	public void setRequestedOrientation(int requestedOrientation) {
	    if(needCheckOrientation()) {
	        Log.i(TAG, "setRequestedOrientation avoid calling setRequestedOrientation when Oreo");
	        return;
	    }
	    super.setRequestedOrientation(requestedOrientation);
	}
	
	private boolean isTranslucentOrFloating(){
	    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 = -1;
	        field.setAccessible(false);
	        return true;
	    } catch (Exception e) {
	        e.printStackTrace();
	    }
	    return false;
	}
}

然后微信的 WXEntryActivity 再集成 BaseActivity,最终修复 BUG

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值