Sansan Tech Blog

Sansanのものづくりを支えるメンバーの技術やデザイン、プロダクトマネジメントの情報を発信

Android版SansanのtargetSdkVersionを30にしました

こんにちは、Sansan事業部プロダクト開発部に所属しているAndroidアプリエンジニアの荒です。

先日Sansan AndroidアプリのtargetSdkVersionを30に更新しましたので、その際に必要だった変更などについてお話していきます。 play.google.com

targetSdkVersion30化が必須となるのは、既存アプリのアップデートの場合は2021年11月(新規アプリの場合は同8月)とまだ時間の猶予は残されていますが、読んでくださっている皆さんの一助となれば幸いです。*1

はじめに

はじめに、targetSdkVersion及びtargetSdkVersion30について軽く説明します。
Androidでは定期的にOSのアップデートが行われており、そこにはセキュリティやパフォーマンスの向上、新たなAPIの追加、既存のAPIへの変更などが含まれています。
targetSdkVersionとは、そのアプリがどのAndroidのバージョンの機能やAPIを適用しているかを明示的に記述するものであり、今回のtargetSdkVersionを30にするということは、Android 11での更新内容をアプリに含めることを指します。

今回行ったことや書いていくこと

行った対応

  • APIのnullableだった箇所がnon-nullに変更されていたので対応
  • 外部パッケージの情報の取得に制約が追加されたので対応
  • systemUiVisibilityがdeprecatedになったので対応

副次的に対応が必要だったもの

  • sdk30でCoordinatorLayout + TabLayoutのレイアウトが表示崩れを起こしたので対応

今回Sansanでは対応不要だったが多くのケースで対応が必要と思われること

  • Scoped Storage対応

以上のようなことについて今回は書いていきます。
これらのうちで、行った対応の「APIのnullableだった箇所がnon-nullに変更されていたので対応」については、フレームワーク側のAPIのインターフェイスの更新に合わせてアプリ内でそれをoverrideしている箇所や呼び出し箇所のnullable, non-nullを変更しただけのものなので特別の記述は致しません。

対応はAndroid Developersの以下ページを参照しながら行いました。 developer.android.com

単なる感想ではありますが、今回対応を行っている中でAndroid 11ではセキュリティに関する更新が多く為されているように感じました。
またAndroid 11では、感染症が流行している情勢を鑑みて濃厚接触の検知、通知を行う関連システムのみ、例外的に端末の位置情報を必要とせずBluetoothスキャンを実行できるようになるなど興味深い更新もあったようです。

外部パッケージへのアクセス

ここでの「外部パッケージ」とは、コード上でimportしなければならない外部ライブラリ等ではなく、端末にインストールされた別のアプリのことを指します。

今までのAndroidのバージョンだと、PackageManagerを用いたresolveActivity()queryIntentActivities()などで端末にインストールされているアプリの情報を簡単に取得することができました。
既存の手段が、実際にアプリが動作するのに必要な範囲と比べて不必要に広い範囲へのアクセスを可能にしてしまっていた点、またアプリが他のどのようなアプリの情報を必要としているかの説明責任を強化するといった点を理由に、Android 11ではどのようなアプリ、パッケージの情報を取得するかを明示的に示す必要があります*2

と言ってもほとんどの場合でKotlin、またはJavaで書かれた既存のコードを編集する必要はなく、必要なのはManifestファイルの追記に留まると思われます。

例えば、以下のようにIntent.ACTION_VIEWと表示したいデータに対応したアプリを端末から取得するコードがあったとします。

val intent = Intent(Intent.ACTION_VIEW, uri)
intent.resolveActivity(packageManager)

sdk29以下を対象としたアプリだとこのコードのみで端末内のアプリの情報を取得することが可能でした。
しかし、sdk30以上をターゲットにするアプリでは、「Intent.ACTION_VIEWに対応したアプリを端末内から取得する」ということを明示する必要があります。
具体的にはAndroidManifestの<manifest></manifest>内に以下のような記述をします。

<queries>
    <intent>
        <action android:name="android.intent.action.VIEW" />
    </intent>
</queries>

これで、今まで通り外部パッケージの情報を取得することが可能になります。

systemUiVisibilityのdeprecated対応

sdk30では、これまでアプリ内の画面のステータスバーやナビゲーションバーを非表示にした全画面表示の際などに使われていたsystemUiVisibilityがdeprecatedになります。扱いとしてはdeprecatedに止まり、いますぐに使えなくなるわけではありませんが、Sansanではsdk30に合わせて新しいAPIでの実装を行いました。

systemUiVisibilityの代わりになるのはWindowInsetsControllerというクラスです。 developer.android.com

必要なコードとしては既存のsystemUiVisibilityとは異なったものになっていますが、個人的にはより直感的になったと感じています。
以下に全画面表示を行う際のコードを示します。

// MyActivity.kt
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
    window.insetsController?.hide(WindowInsets.Type.statusBars() or WindowInsets.Type.navigationBars())
    // 既存のSYSTEM_UI_FLAG_IMMERSIVE_STICKY相当。スワイプで半透明なシステムバーが表示される。
    window.insetsController?.systemBarsBehavior = WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
} else {
    val visibility = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or
        View.SYSTEM_UI_FLAG_FULLSCREEN or
        View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
        View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
        View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
    window.decorView.systemUiVisibility = visibility
}

定数の名前がsystemUiVisibilityに設定するものと比べわかりやすくなり、またシステムバーの表示非表示とbehaviorとの設定導線が別れたりとよりわかりやすくなったと言えるのではないでしょうか?

注意すべき点として、バージョンでの分岐が必須であることが挙げられます。
WindowInsetsControllerはAndroid 11以降で追加されたものであり、バージョン分岐を行わないとAndroid 11未満のアプリはクラッシュしてしまうので注意が必要です。

また、この方法で全画面表示を行っている最中にIntent.createChooser()などでstartActivity()した際に、一時的にステータスバーの透過が解かれてしまうなど細かい差異も確認されました。

CoordinatorLayout + TabLayoutのレイアウトが表示崩れを起こした

これは直接的にsdk30への対応というわけではないのですが、targetSdkVersionを29から30にした際に起こった問題なので記述します。

結論から言えば、詳細な原因はわからず、material-components-androidのライブラリをアップデートすることで解決に至りました。

これまで、Sansanではappcompatのライブラリのバージョン1.1が文字サイズに関するバグを含んでいることが原因で、1.0のバージョンを使用していました。 material-components-androidはappcompatに依存しており、こちらも1.0のバージョンを使用していました。先日appcompatは先んじてバグが修正された1.2への更新を行ったのですが、material-components-androidはまだ1.0のままとなっていました。

この状態でtargetSdkVersionを29から30に更新した際にTabLayoutに指定していないpaddingが上部に付与されてしまうという現象が発生しましたが、material-components-androidを1.2にアップデートすることで表示は今までと同じものとなりました。

まとめると、発生した状況は以下の通りです。

skd29 sdk30
material-components-android 1.0 ×
material-components-android 1.2 未検証

○: 期待通りに表示される。
×: 表示が崩れた。

Scoped Storage対応

Androidでは、端末のストレージにアクセスする仕組みがAndroid 10(sdk29)から新しいものとなっていました。
これも「外部パッケージへのアクセス」と同じく、アプリが実際に必要なストレージへのアクセス権を超えたアクセスが可能であることに対するアップデートでした。

しかし、targetSdkVersionが29のアプリであれば、ManifestファイルにrequestLegacyExternalStorageを設定することで既存の手法で端末のストレージにアクセスすることが可能でしたが、targetSdkVersionを30にするとこのフラグは無視されるので、端末ストレージへのアクセスを行っているアプリは対応が必要となります。

Sansanでは端末内のファイルにアクセスできる機能は、今年8月にリリースした、面会や商談の記録にファイルを添付できる機能のみだったのですが、実装段階でScoped Storageの必須化を考慮し、新しいAPIでの実装を行っていたため今回特別の対応は不要でした。

まとめ

今回のsdkバージョンの更新では、パッケージの取得やストレージとやはりセキュリティに関するアップデートが目立ったように思われます。(一番時間を食ったのはmaterial-componentsの表示崩れですが😅)

また、ストレージについては先を見据えて予めScoped Storageに対応していた恩恵を感じることができました。
WindowInsetsControllerの実装についても、いつか移行が必須となった時に、今回必須化に先んじて対応した恩恵に与ることができるのではないかと考えています。


Sansanでは一緒に働くAndroidアプリエンジニアを探しています!
東京だけでなく大阪で働くAndroidアプリエンジニアも在籍していますので、少しでも興味がある方は是非以下のページをご覧ください!

【Kotlin】Androidアプリエンジニア(Sansan/Eight) | Sansan株式会社

【大阪/Kotlin】Androidアプリエンジニア(Sansan/Eight) | Sansan株式会社

© Sansan, Inc.