Studyplus Engineering Blog

スタディプラスの開発者が発信するブログ

透明Activityの罠

初めまして、Studyplus開発部でAndroidアプリの開発をしております中島です。

今回のブログでは、我々 Studyplus Androidチームが踏んでしまった、深い深い落とし穴について少しお話しさせていただければと思います。

透明Activityとは?

ここで 透明Activity と言っているのは、以下の指定を持ったThemeを用いることで背景を透過させたActivityのことです。

(Android SDK内、theme.xmlより抜粋)

<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:colorBackgroundCacheHint">@null</item>
<item name="android:windowIsTranslucent">true</item>

弊社アプリでは、例えば以下のような箇所で使用しています。

  • 初めて使う機能の際に、チュートリアル画像を出して簡単に説明する
  • ホーム画面を開いた際、ユーザーが設定したイベント(模試などが多いでしょうか)までの日にちをカウントダウン表示する

透明Activity の罠とは?

では、この透明Activityにどんな罠があったかということについて、順を追って説明いたします。

Overture<発端>

弊社アプリは、今年の10/10のアップデートをもってTargetSDKを 26 から 27 に更新いたしました。 これ自体は特に問題ありません、むしろ素晴らしいことであると思います。(Androidチームとしては28まで上げたかったのは確かですが、まぁ一歩ずつ)

手元のテストでも特に問題はなくリリースしたのですが、その日の夕方から悪夢が始まりました…

Stampede<殺到>

Fatal Exception: java.lang.RuntimeException: Unable to start activity ComponentInfo{~~Activity}: java.lang.IllegalStateException:
Only fullscreen opaque activities can request orientation

複数のActivityで上記のクラッシュレポートが殺到しました。一晩で 10k を超えるクラッシュが報告され、はっきり言って異常事態でした。

Investigate<調査>

正直この時点で精神を落ち着かせることなど不可能ですが、頭だけでも落ち着かせて調査を進めなくてはいけません… Crashlyticsの確認と、ソースコードの確認で以下のようなことがわかりました。

  • クラッシュは Android 8.0 のみで起こっていること
  • クラッシュしているActivityは全て 透明Activity であること

また、エラーメッセージで検索したところ StackOverFlowの投稿 が見つかりました。 これに加えAOSPのActivity.java内部を8.08.1で比較してみましたところ、8.0で以下の処理が追加され、8.1で削除されていることがわかりました。

Activity.java(8.0)

protected void onCreate(@Nullable Bundle savedInstanceState) {

    ~~~

    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");
        }
    }

    ~~~

以下は、この変更がAOSPに追加された時(66fa94d42f30771c3e7b249756aed656d38aed08)のコミットメッセージです。

This changelist enforces that activities targeting O and beyond can only specify an orientation if they are fullscreen. The change ignores the orientation on the server side and throws an exception when the client has an orientation set in onCreate or invokes Activity#setRequestedOrientation.

O以上において、透明Activity などのfullscreenではないActivityにはOrientationの指定を行えないようにすることが目的として明言されています。 こうすることで、直前のActivityのOrientationに依存させることが目的だと思われます。

また、その後に Oには指定を許す といった旨のコミットメッセージと共に コミット(d1ac18c7c9eca1b07120be598dc6859b188baeb3)が追加され、上記のコードの形になったようです。

Allow for SDK 26 Activities to specify orientation when not fullscreen.

Conclusion<結果>

調査の結果、以下の条件を全て満たした場合に、 Activity#onCreateで必ず例外をスローしてクラッシュする ようになっていることが発見されました。

  • アプリの指定する TargetSDKが 27(Android 8.1) 以上
  • 動作端末 が Android 8.0 (SDK 26)
  • ActivityのThemeに <item name="android:windowIsTranslucent">true</item> または <item name="android:windowIsFloating">true</item> と指定している
  • Activityに android:screenOrientationportraitlandscape を指定している

なお弊社アプリは縦画面固定アプリのため、アプリ内の全Activityに対し android:screenOrientation="portrait" が指定がされています。 そのため、TargetSDKを27に上げたことが、罠の最後のトリガーだったのでした…

Fix<対応>

対応としては、Orientationの指定を消せば解決する問題ではあります。 しかし、メッセージでは8.0 以上に限定しているため、ただOrientation指定を消すのみでは 8.0 未満の端末に影響する可能性があります。 以上の理由から、該当Activityにおいて以下のような修正を行ないました。

  • AndroidManifestからOrientation指定を削除
  • 8.0 未満の場合のみ、 Activity#onCreate でOrientation指定を改めて追加
super.onCreate(savedInstanceState);

// super#onCreate の後に改めて Orientation 指定
if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
    setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
}

この対応により事態は収束しましたが、次に私を待っていたのは障害報告書の作成任務であったことは想像にかたくありません。

まとめ

今回は、TargetSDKを上げたことにより顕在化した不具合についてお話しさせていただきました。

ただ、今回の一件も、元はと言えばTargetSDKを最新に近づけようという意志の下に生まれたものと言えます。 StudyplusにはFail Forward(失敗から学ぶことに重点を置き、失敗そのものを批判することはしない)という標語があります。 我々Studyplus Androidチームはこの一件から学んだことを糧にして、今後もモダンなAndroid開発を目指していければと思っております。(Kotlin Coroutinesの導入も始まっています!) もちろん同じ轍は踏まぬよう、十二分に注意いたします。

Studyplusでの開発に興味がおありの方はぜひ ご応募ください