Android 8.0崩潰信息:Only fullscreen activities can request orientationjava
緣由很簡單,大概是谷歌爸爸在安卓8.0版本時爲了支持全面屏,增長了一個限制:若是是透明的Activity,則不能固定它的方向,由於它的方向實際上是依賴其父Activity的(由於透明)。然而這個bug只有在8.0中有,8.1中已經修復。具體crash有兩種:android
1.Activity的風格爲透明,在manifest文件中指定了一個方向,則在onCreate中crash程序員
2.Activity的風格爲透明,若是調用setRequestedOrientation方法固定方向,則crash先看onCreate中的代碼:數組
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"); } }
這個targetVersion有點騷,若是指定android26,還沒問題。ide
具體什麼是透明,看代碼: ui
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; }
大意就是,有上面三種風格就是透明。this
那麼什麼是固定呢?再看: code
public boolean isFixedOrientation() { return isFixedOrientationLandscape() || isFixedOrientationPortrait() || screenOrientation == SCREEN_ORIENTATION_LOCKED; }
其實就是橫豎屏或者鎖定就是固定。ip
如今思路其實很明確,咱們只要修補一個版。若是進onCreate的時候,若是判斷是透明窗口風格,直接把屏幕朝向改成未指定類型即SCREEN_ORIENTATION_UNSPECIFIED就能夠了,由於Activity是透明的,因此其方向依賴於父Activity,因此這個改動對結果不會產生任何影響。get
至於不少透明Activity的代碼中調用setRequestedOrientation,更好處理,項目不是有BaseActivity嗎,咱們直接判斷若是是androidO版本,咱們不調用它便可,結果也是等效的。
網上千篇一概的不是說把Activity改成不透明或者把方向省掉的,還有說不升級targetVersion的,這些方案是在是不太好,由於本人項目中有大量的Theme文件,依賴錯綜複雜,想理清哪一個Activity是透明的,還真不是件容易的事。
首先要作的就是獲取當前Activity是否是透明的,上面都有了代碼了,只是好多東西都是隱藏的,咱們用反射好了。
安卓O開始,好多類都被谷歌爸爸移動到黑名單,還有好多淺灰、深灰名單的類以及方法,不少調不了,不過沒關係,咱們此次只是解決26版本這一個版本,由於咱們須要反射的類都尚未被禁止,因此還能幫谷歌爸爸擦屁股,若是後面谷歌爸爸再犯2出這種bug,想擦都沒得擦。
下面這段代碼是BaseActivity的成員方法,其中稍難的就是如何獲取com.android.internal.R$styleable.Window這個stylable,最開始我R後面寫的是「.」,一直不對,後來才發現stylable實際上是R的內部類,獲取到這個數組,就能夠用反射調用ActivityInfo#isTranslucentOrFloating()這個方法了。
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; }
在onCreate的時候,先判斷,若是透明,直接把方向改成SCREEN_ORIENTATION_UNSPECIFIED:
@Override protected void onCreate(Bundle savedInstanceState) { if (Build.VERSION.SDK_INT == Build.VERSION_CODES.O && isTranslucentOrFloating()) { boolean result = fixOrientation(); XLog.i(XLog.BASE, "onCreate fixOrientation when Oreo, result = " + result); } super.onCreate(savedInstanceState); } 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; }
而後在設置方向的時候若是透明,直接不執行:
@Override public void setRequestedOrientation(int requestedOrientation) { if (Build.VERSION.SDK_INT == Build.VERSION_CODES.O && isTranslucentOrFloating()) { XLog.i(XLog.BASE, "avoid calling setRequestedOrientation when Oreo."); return; } super.setRequestedOrientation(requestedOrientation); }
附上程序員交流和福利發放羣,平時給程序員發發福利:725030150