最近遭遇したクラッシュについて、久しぶりにQiita記事を書いてみようと思います
クラッシュについて
端的に書くと、
- Android 8.0の端末である
- targetSdkVersionを27以上にしている
- 背景を透過にしている
- 画面の向きを固定している
という4つの条件を満たすと、以下のようなクラッシュが発生します。
java.lang.IllegalStateException: Only fullscreen opaque activities can request orientation
Android 8.1以降では発生しないようです。すなわち、targetSdkVersionを27にしたら、Android 8.0(=APIレベル26)のみでクラッシュするということです。
バージョン | APIレベル |
---|---|
Android 8.0 | 26 |
Android 8.1 | 27 |
コードで書くと以下のような感じです。
targetSdkVersionを27以上にしている
android {
defaultConfig {
targetSdkVersion 27
}
}
背景を透過にしている
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Translucent" parent="AppTheme">
<item name="android:windowIsTranslucent">true</item>
</style>
</resources>
画面の向きを固定している
<?xml version="1.0" encoding="utf-8"?>
<manifest>
<application>
<activity
android:name=".TranslucentActivity"
android:configChanges="orientation"
android:screenOrientation="portrait"
android:theme="@style/Translucent" />
</application>
</manifest>
上記のTranslucentActivityをAndroid 8.0の端末で開くとクラッシュします。
原因
Android 8.0のActivity#onCreate
には、以下のような処理が入っているため、上記条件を満たすとクラッシュするようになっています。
public class Activity {
protected void onCreate(@Nullable Bundle savedInstanceState) {
// ...
if (getApplicationInfo().targetSdkVersion >= O_MR1 && 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 8.1では上述の処理が消されています。
Activity.java
のcommit logを見てみると、だいたい以下のような時系列で修正が入っていそうです。
commit / tag | 説明 |
---|---|
android-7.1.2_r36 | Android 7.1.2 |
3979159 d1ac18c |
該当箇所の追加 |
android-8.0.0_r1 android-8.0.0_r36 |
Android 8.0 |
a89b183 e83f34c d4ecffa |
該当箇所の削除 |
android-8.1.0_r1 | Android 8.1 |
a4ceea0 | (同様に削除) |
APIレベルとタグの関係は以下のページを参考にしました
https://source.android.com/setup/start/build-numbers
バグなのか?仕様なのか?
完全に予想ですが、うしろのActivityが見えているようなActivityの場合、orientationを指定するとうしろのActivityも同じorientationにならなければならず、その時にうしろのActivityはorientationを固定していても画面回転が起こってしまうというのを防ぎたかったのかなと思います。
とはいえ、Android 8.1では削除されている事実からすると、少なくともクラッシュするという挙動はAndroid開発者たちには受け入れられなかったようです。以下で話し合われているのはActivity#onCreate
の部分ではないですが、そのようなやりとりがあります。
https://issuetracker.google.com/issues/68454482
解決方法
当たり前ですが、クラッシュしてしまう4つの条件のうち、いずれかを満たさないようにすれば解決できます。とはいえ、Android 8.0を使わせないとか、一生targetSdkVersionを27以上にしないとかはできないと思うので、以下のような解決策になると思います。
画面回転に対応する
本来は画面回転に対応するのがベストだと思います!とはいえこの問題に遭遇する人は画面回転に対応していないアプリを開発している気がします
orientationをbehindする
遷移元のActivityが自分のアプリに限定されるのであれば、遷移元のActivityの向きを固定することができます。そうした場合、orientationをbehindにすると、うしろに透けているActivityと同じ向きになるため、そのまま固定されます。
(透明なActivityの場合、orientation未指定でもbehind指定時と同じ挙動になるようですが、behindを指定しておくと明示できて良い気がします)
targetSdkVersionを一瞬下げる
完全に力技です
適当に触った感じだと正常に動作していそうですが、動作は保証できないです
class TranslucentActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.O) {
val targetSdkVersion = applicationInfo.targetSdkVersion
applicationInfo.targetSdkVersion = Build.VERSION_CODES.O
super.onCreate(savedInstanceState)
applicationInfo.targetSdkVersion = targetSdkVersion
} else {
super.onCreate(savedInstanceState)
}
setContentView(R.layout.activity_translucent)
}
}
まとめ
targetSdkVersionを27に上げた場合、APIレベル27(Android8.1)で動作確認すれば大丈夫かと思いきや、そうでもないようですね
とはいえ今回の場合はActivityを起動した瞬間にクラッシュするので、自動テストを充実させて防ぎたいものです
何かご意見ご感想、間違い指摘などがあればコメントください