Android mobile

Android Gradle Pluginのバージョンをv7.0.4からv8.2に上げるために8ヶ月でやったこと

Android mobile

minne事業部プロダクト開発チームのtepiです。お恥ずかしい話ではあるのですが、昨年6月に私が入社した時点でminneで利用されているAndroid Gradle Plugin(以下、AGP)のバージョンがv7.0.4だったため、これは・・・と思いコツコツ積み重ねてきた結果、先日AGPのv8.2にすることができましたのでその変遷をお伝えしていきたいと思います。
※最新はv8.3ですが、v8.2へアップデートする作業を進めていた段階でアップデートされたため、取り急ぎv8.2まで済ませる形としています。

  1. スタート時点の状況
  2. トラブルと解決方法の依存関係の整理
  3. トラブルの解消 その1:Jetpack Composeへの変更
  4. トラブルの解決 その2:Deprecatedになったライブラリの更新
    1. FirebaseInstanceIdの解決
    2. INSTANCE_ID_EVENTの解決
    3. その他
  5. トラブルの解消 その3:AGPのバージョンアップに伴う更新
    1. non-transitiveの対応
    2. BuildConfigの自動生成の解決
    3. proguardの変更の解決
  6. 完成!
  7. まとめ

スタート時点の状況

昨年時点でv7.0.4だったのは、主にライブラリのアップデートが間に合っていないことが理由でした。また、そのライブラリの更新ができない理由がアプリのクラッシュに起因するものであったりしたため、クラッシュが発生するUIの作り直しなどが必要ですぐ直せるものではなく放置されてきていました。

トラブルと解決方法の依存関係の整理

まずは、「何が解決されれば、どのトラブルが解消でき、その結果どのライブラリが更新できて、最終的にAGPが更新できるのか」を整理しました。

例えば、

  1. あるUIがEpoxyというライブラリを利用しており、それとappcompatライブラリの相性が原因でクラッシュしている。
  2. 結果、appcompatライブラリの更新ができない
  3. 結果、AGPも更新ができない

といった具合です。

ある程度の予測がついたところで、末端の問題から着手していきあとはただひたすらに出てくる問題に対応していきました。

トラブルの解消 その1:Jetpack Composeへの変更

minneでは古くから開発されていることもあり、XMLのViewや上記にも記載があるEpoxyライブラリを利用したViewなどが混ざっています。また、例に漏れずEpoxyのライブラリが更新されておらず、全体への影響を考えると更新しづらい部分もありました。そこにきて上記の例で記載しているように、appcompatライブラリの更新を行うとクラッシュしてしまうという事象が発生していました。

幸い、

  • AndroidではJetpack Composeにしていくという潮流である
  • minneとしても当然やっていきたい
  • 当該の問題が発生しているのは1画面だけである

ということから、当該のUIはJetpack Composeへ置き換えて、問題を解決しました。

結果、Jetpack Composeへの置き換えができたのはもちろんのこと、他の画面のJetpack Composeへの移行例(例えば工数や難易度など)としてチーム内でもノウハウを共有できました。実際にやってみることで、意外と簡単である、ここは大変だ、などやって気づくことがたくさんありますので、なかなか重たい腰ではあるとは思いますが何か理由をつけて始めてみることをオススメします。

トラブルの解決 その2:Deprecatedになったライブラリの更新

minneでは主にFirebaseがこの問題に直面していました。対応を開始した当初はFirebaseのBOMのバージョンがv27.1.0で最新はv32.8.0と大きな開きがあり、最新にするには以下の問題がありました。

  • FirebaseInstanceIdがdeprecatedになった。
  • <action android:name="com.google.firebase.INSTANCE_ID_EVENT" />を記載する手法がdeprecatedになった。
  • Firebase App Indexingがサービス終了した。

Firebase App Indexingに関しては、サービスが終了しているかつ代替となるAndroid アプリリンクへの移行が済んでいたため、削除するだけですみましたが、残りはそうはいきませんでした。

FirebaseInstanceIdの解決

FirebaseInstanceIdは、Firebaseで発行されるプッシュ通知のトークンを取得するためのクラスで、FirebaseInstanceId.getInstance().tokenのように利用していましたがクラス自体がDeprecatedとなり、FirebaseMessaging.getInstance().tokenを利用するよう変更になりました。
書き換えるだけと言えばそれまでなのですが、前述のとおりプッシュ通知のトークンとして利用されているためFirebase内で必要であることに加えて、各種プッシュ通知を送信する外部サービスなどでも必要になっているため、影響が出ないような修正をしていく必要がありました。

INSTANCE_ID_EVENTの解決

INSTANCE_ID_EVENTも厄介で、最初に修正を実施した際はプッシュ通知が届かなくなってしまいました。

INSTANCE_ID_EVENTがdeprecatedとなりMESSAGING_EVENTへ移行せよということで、修正としてはINSTANCE_ID_EVENTで起動していたサービスをMESSAGING_EVENTで起動するように修正しました。ただ、ドキュメントでは明示的に触れられてないのですが、以前からMESSAGING_EVENTで別のサービス(プッシュ通知を受け取るためのサービス)を起動しており、記載のとおり修正したことでMESSAGING_EVENTで起動するサービスが設定上は2つになってしまったので、プッシュ通知を取得するサービスだけが起動しなくなってしまったことが原因でした。
最終的には当該の2つのサービスをマージし1つにすることで解決しました。

その他

上記以外にも、Google AdsのSDKや外部サービスのSDKのアップデートが滞っていたり、すでに利用されていない外部サービスのSDKが削除されていなかったりして、AGPのアップデートの妨げになっていたため、問題を解消しながらライブラリのアップデート作業を進めました。

トラブルの解消 その3:AGPのバージョンアップに伴う更新

上記までの対応で、AGPのv7.0.4からv7.4.2(v7系の最新)までアップデートすることができました。ただ、AGPのv7系からv8系はビルドに関する部分がいろいろと変わっており、以下のような対応を事前に進める必要がありました。

  • リソースがnon-transitiveになった。
  • BuildConfigが自動生成されなくなった。
  • proguardに変更が必要になった。

non-transitiveの対応

これまでリソースは上位のモジュール内にあるリソースも自身のリソースとして利用できたものが、できなくなったことをnon-transitiveと呼んでいます。

これは、同じリソースが複数箇所に配置されるのを抑制したり、AGPが各モジュールのリソースのみを把握することでビルドスピードの向上につながったりするので良いことばかりではありますが、そんなことを気にせずリソース定義してきたアプリには一苦労です。 Googleさんも親切なのでAndroid Studioの機能としてマイグレーションする機能を準備してくれてはいますが、それだけでは対応できないケースもあります。

例えばminneの場合だと、XML内にRクラスのimport文が記載されており、Data Binding時に利用するようなコードは非対応でした。

// 例
<layout>
  <data>
    <variable
      name="isOk"
      type="boolean" />
    <import type="package.name.R" />
  </data>

  <FrameLayout>
    <TextView
      android:text="@{isOk ? context.getString(R.string.yes) : context.getString(R.string.no)}"
    />
  </FrameLayout>
</layout>

更にminneの場合はリソースがさまざまなモジュールに点在していたり、廃止する予定のモジュールの中に配置されているものもあったため、今後重複した作業を行わずに済むよう同時にリソースの整理も行うことにしました。

対応箇所を探すという作業はほとんど必要なく、AGPの7系でもgradle.propertiesandroid.nonTransitiveRClass=trueを追加するとnon-transitiveな設定となり、ビルドすれば参照できないリソースはエラーとなるのでそれを1つ1つ潰しました。また、整理したいリソースに関してもリソースから削除してしまえば参照できない箇所が同様に見つかるので、適切な場所に整理しました。

ここで気をつけないといけないのは、並行してい進んでいる開発でも正しくリソースを利用しておかないと、あとあと同じ対応を変更箇所に対してしなくてはならない点です。開発者が気をつけること、またPRで重点的に見ておけばある程度はコントロールできますが、minneでは私含めて2名での開発だったからできたとも言えそうです。人数が多い組織なのであればgradle.propertiesの設定をした上で手分けしてせーのでやりきってしまう方が良いかもしれません。

BuildConfigの自動生成の解決

AGPの8系からはBuildConfigが自動で生成されなくなりました。自分ではこうなった理由までは見つけられなかったのですが、恐らくこれもビルド時に生成されるBuildConfigを生成しなくても済むようにすることで、ビルド時間の短縮ができるものと思われます。

解決方法としては、今回はBuildConfigが必要なモジュールでbuild.gradleにBuildConfigを生成する設定を行いました。

android {
  buildFeatures {
    buildConfig = true
  }
}

gradle.propertiesにて設定する方法もありますが、そうするとビルド全体で必ず生成するようになってしまうので、多少でもビルドスピードに寄与できるようあくまで必要なモジュールのみで行うようにしています。

他の解決方法として、Now In Androidのリポジトリを見てみると、基本的にはbuildType、productFlavor毎のディレクトリを作成して、都度必要な実装が利用されるようにしています。 non-transitiveの件と同様に整理してしまいところではあったのですが、特にBuildConfigが使われがちなDebugか否かの実装が至る箇所にあり、まずはAGPの更新を優先するため、このような手法を取りました。今後Jetpack Composeに変更していく中で排除していきたいです。

proguardの変更の解決

上記の対応でやっと更新できると思ったのもつかの間、ビルドしてみると難読化の際に以下のようなエラーが表示されました。

Library class android.content.res.XmlResourceParser implements program class org.xmlpull.v1.XmlPullParser

調べるとこちらのIssue TrackerのIssueにたどり着きました。どうやらクラスが重複しているがために起きているようです。 minneのソース内を調べてみると、XmlResourceParserを利用しているクラスがAndroid OSで利用されているものとRobolectricライブラリ内にあることがわかりました。

Issueにも記載のとおり、本来なら取り除くようトライすることが望ましそうですが、上記にもあるようにAndroid OS内のソースとテストのみで利用されるRobolectricでの問題ということで、今回は以下のようにproguardを追記し解決しました。

-dontwarn org.xmlpull.v1.**
-dontwarn android.content.res.**
-dontnote org.xmlpull.v1.**
-keep class org.xmlpull.** { *; }
-keepclassmembers class org.xmlpull.** { *; }

また、上記以外にも、minneではcom.google.protobufを利用していたため、ビルド時に以下のようなエラーも出ました。

ERROR: Missing classes detected while running R8. Please add the missing classes or apply additional keep rules that are generated in /XXX/missing_rules.txt.

これはエラーにも書いてあるとおり、生成されているmissing_rules.txtに記載の内容をproguardに追加しました。

完成!

上記の対応をもって、AGPをv8.2に更新することができました!

きちんと記録はされていませんが、期間としては8ヶ月、工数としては大体2ヶ月〜3ヶ月といったところでしょうか?
上記以外にも、開始時には問題となっていなかったライブラリのアップデートは実施していたので、もしそれがなければ途中さらにつまずいていたかもしれません。 また、途中ビルドに使うJavaのバージョンで迷走したり、並行して機能の開発も行っていたので、それら次第では多少早く終えられた可能性もありそうです。

まとめ

恐らく読んでいる皆さんはここまでまとめて対応が必要な状態にはなっていないかと思います。

ただ、未来に同じような大変な作業をしなくても済むようにしておくのは、今開発している方々にほかなりません。残念ながら1年かかる作業は1年前に始めなければ終わりません。日々の開発で忙しかったり他の楽しい開発があったりしてなかなか手が出ないこともあるとは思いますが、未来の自分自身のためにも日々積み上げておきたいものです。 また、こういった作業をしなくても済むよう事前に考え開発していくことも忘れずにやっていきたいですね。

まだまだminneでは解決したい課題がたくさんあります。話題としては暗い内容ではありますが、社内ではワイワイ楽しくやっておりますので、ぜひぜひ一緒に課題を解決いただける方、ご応募お待ちしております!