Posted at

java.lang.IllegalStateException: Only fullscreen opaque activities can request orientation

More than 1 year has passed since last update.

最近遭遇したクラッシュについて、久しぶりにQiita記事を書いてみようと思います :hugging:


クラッシュについて

端的に書くと、


  • 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以上にしている


app/build.gradle

android {

defaultConfig {
targetSdkVersion 27
}
}

背景を透過にしている


styles.xml

<?xml version="1.0" encoding="utf-8"?>

<resources>
<style name="Translucent" parent="AppTheme">
<item name="android:windowIsTranslucent">true</item>
</style>
</resources>

画面の向きを固定している


AndroidManifest.xml

<?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には、以下のような処理が入っているため、上記条件を満たすとクラッシュするようになっています。


Activity.java

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.javacommit 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以上にしないとかはできないと思うので、以下のような解決策になると思います。


画面回転に対応する

本来は画面回転に対応するのがベストだと思います!とはいえこの問題に遭遇する人は画面回転に対応していないアプリを開発している気がします :sweat_smile:


orientationをbehindする

遷移元のActivityが自分のアプリに限定されるのであれば、遷移元のActivityの向きを固定することができます。そうした場合、orientationをbehindにすると、うしろに透けているActivityと同じ向きになるため、そのまま固定されます。

(透明なActivityの場合、orientation未指定でもbehind指定時と同じ挙動になるようですが、behindを指定しておくと明示できて良い気がします)


targetSdkVersionを一瞬下げる

完全に力技です :triumph: :triumph: :triumph:

適当に触った感じだと正常に動作していそうですが、動作は保証できないです :bow:


TranslucentActivity.kt

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)で動作確認すれば大丈夫かと思いきや、そうでもないようですね :sweat_smile:

とはいえ今回の場合はActivityを起動した瞬間にクラッシュするので、自動テストを充実させて防ぎたいものです :muscle:

何かご意見ご感想、間違い指摘などがあればコメントください :dancer: