一開始是提交app 到小米市場,一直提示app閃退不穩定,公司測試機很少,後面在https://www.testin.cn找到閃退原因
後面看了以下文章才真正解決問題:
原文地址:https://blog.csdn.net/starry_eve/article/details/82777160
適配到安卓O,適配了Service、通知等等,天真的以爲一切都結束了,換菊花廠手機試APP,直接crash,這簡直是何等的臥槽。錯誤提示如下(還有一種和這個差不多,就差一個單詞,一個是onCreate時候,另一個是設置方向之後):
Only fullscreen activities can request orientation
先參考了一下網上各位大佬的文章,以下面的爲例:
https://zhuanlan.zhihu.com/p/32190223
原因很簡單,大概是谷歌爸爸在安卓8.0版本時爲了支持全面屏,增加了一個限制:如果是透明的Activity,則不能固定它的方向,因爲它的方向其實是依賴其父Activity的(因爲透明)。然而這個bug只有在8.0中有,8.1中已經修復。具體crash有兩種:
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,還沒問題。
具體什麼是透明,看代碼:
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;
}
大意就是,有上面三種風格就是透明。
那麼什麼是固定呢?再看:
public boolean isFixedOrientation() {
return isFixedOrientationLandscape() || isFixedOrientationPortrait()
|| screenOrientation == SCREEN_ORIENTATION_LOCKED;
}
其實就是橫豎屏或者鎖定就是固定。
現在思路其實很明確,我們只要修補一個版。如果進onCreate的時候,如果判斷是透明窗口風格,直接把屏幕朝向改爲未指定類型即SCREEN_ORIENTATION_UNSPECIFIED就可以了,因爲Activity是透明的,所以其方向依賴於父Activity,所以這個改動對結果不會產生任何影響。
至於很多透明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);
}