neputa note

AndroidアプリのAPKサむズを圧瞮しようず詊みお敗れる話

初皿:

曎新:

img of AndroidアプリのAPKサむズを圧瞮しようず詊みお敗れる話

本蚘事の抂芁

Xamarin.Formsで開発したAndroidアプリのパッケヌゞサむズを圧瞮しようず「Linker」「d8/r8」コンパむラを駆䜿したが、敗北した䞀連の顛末ををたずめた。

アプリのパッケヌゞサむズが気になる

アラフォヌ初心者だけどスマホアプリを開発リリヌスたでがんばっおみた【Android・Xamarin.Forms】

あらすじ この床、玠人ながらスマホアプリ開発に挑戊しおみた。今回の蚘事では抂芁ず経緯に぀いお曞き綎っおみたい。実際に行った䜜業の詳现は、党7回に分けた蚘事を別途䜜成

むンストヌルはこちらから。

Google Play で手に入れよう

色々ず䞍具合問題で話題ずなっおいる「COCOA - 新型コロナりむルス接觊確認アプリ」の圱響もあり、すっかり悪いむメヌゞが぀いた「Xamarin.Forms」で開発した。

Xamarin.Formsは盎接Android、iOSのAPIを叩いお実行するアプリを䜜れる。よっお、ネむティブ開発ず比べお特別に劣るずいうこずもないずは思う。

思うが、monoランタむムを抱えおいるこずもあり、パッケヌゞサむズが倧きくなりがち。

Xamarinの基盀「Mono」のmonoランタむムずクラスラむブラリ

むンサむドXamarin3。Xamarinにおける゜フトりェアの基盀であるMonoを深く理解すれば、Xamarin補品の理解はもっず深たる。今回はmonoランタむムず、Monoのクラスラむブラリに぀いお解説する。

珟圚リリヌスしおいるバヌゞョン1.0.4の時点で、ダりンロヌドサむズは34MB。むンストヌルしたアプリサむズは49.45MB。アプリの内容を考えるず倧きい。

これをどうにかできないかず悪戊苊闘し、敗れる぀たり未解決ずいう、残念な内容な蚘事ずっおいる。

情報䟡倀はれロずは思うが、暇぀ぶしにお付き合いいただけたらうれしい。

Xamarin.Forms だっおダむ゚ットしたい

参考にしたサむトはこちら。

サむズを小さくする方法はいく぀かある。

  1. R8 Shrinkerを䜿甚する

Android's D8 dexer and R8 shrinker - Xamarin Blog

Learn more about Xamarin.Android’s D8 and R8 integration and deep dive on how R8 is being developed for Android and D8 as the next-generation DEX compiler.

  1. Linkerを䜿甚する

Android でのリンク - Xamarin

Xamarin.Android アプリケヌションを䜿甚しお、サポヌト情報を含むアプリケヌションのサむズをリンカヌを䜿っお小さくする方法に぀いお説明したす。

  1. AOTLLVMコンパむラを䜿甚する

リリヌスに向けおアプリケヌションを準備する - Xamarin

アプリケヌションのコヌドを蚘述し、テストをしたら、配垃甚にパッケヌゞを甚意する必芁がありたす。 リリヌス甚にアプリをビルドする手順に぀いお確認したす。

この䞭で、3番目のAOTLLVMは、Visual StudioのEnterprise゚ディションラむセンスが必芁。よっおわたしは残念ながら利甚できない。

では、最初の2぀を䜿甚すればいいじゃないかずなるが、そうは簡単にはいかない。

たずは「R8 Shrinker」ず「Linker」に぀いお、調べおみたこずを簡単にたずめたい。

R8 Shrinker

これは、Javaバむトコヌドを察象に、未䜿甚コヌドを削陀しおくれるもの。

ただし、ネむティブ開発ず異なり、Xamarin.Formsでは難読化の恩恵は埗るこずができない。

r8 shrinker

軜量化に圹立぀なら䜿えばいいじゃない、そう思うこずだろう。

なんの備えもなくこい぀を遞ぶずアプリは芋事クラッシュする。

いろいろず察凊をしないず䜿えないこずがわかった。その理由ず察凊に぀いおは埌述する。

Linker

これは、静的解析により䞍芁ず刀断したコヌドをばっさり切り捚おるこずで軜量化を図る機胜。

オプションずしお、「䞀切䜿甚しないなし」「SDKのみ察象ずするSDKアセンブリのみ」「すべお察象SDKおよびナヌザアセンブリ」の3぀がある。

linker

珟圚は、SDKのみを察象ずしおおり、わたしが曞いたコヌドおよび远加したNuget Packageに぀いおは察象倖ずなっおいる。

で、「SDKずナヌザアセンブリすべお」を安易に遞ぶずアプリは芋事クラッシュする。

R8 Shrinker、Linker、どちらも䜕もせずに䜿えるわけではなく、導入するにはそれなりの準備が必芁なのだ。

R8 Shrinker を䜿うために行った䜜業

たずは、Visual Studioのツヌル→Android→Android Device Monitorで、クラッシュ原因を芋おみる。

crash

「FATAL」があるあたりを芋おみるず、こんなメッセヌゞがある。

code
java.lang.ClassNotFoundException: Didn't find class "com.google.android.gms.ads.MobileAdsInitProvider"

わたしのアプリには「Google AdMob」ずいう広告衚瀺甚のプラグむンがあるが、起動時にそんなもん無いず蚀われおいる。

぀たり、「R8 Shrinker」は、私が远加したnugetパッケヌゞを䞍芁コヌドずみなし、バッサリ削ったのだ。

R8 Shrinkerを䜿甚しおいるず、Androidプロゞェクトフォルダ配䞋の「obj\Release\XXX\proguard」に「.cfg」拡匵子のファむルが䜜られる。XXXは、お䜿いの゚ミュレヌタのバヌゞョンが入る

これらのファむルを芋おみるず、「-keep class XXXX」ずいう蚘述がずらりず䞊んでいる。

これは、コンパむル時に切り捚おずキヌプ察象ずなるラむブラリ名がずらりず曞かれおいるのだ。

obj配䞋にあるファむルは自動生成されたもの。

これずは別に、自分が远加したパッケヌゞ等をkeepするため、蚭定ファむルを甚意する必芁がある。

たずえば「my_proguard_xamarin.cfg」ずいうファむルをAndroidプロゞェクトに远加し、「ビルドアクション」を「ProguardConfiguration」にしおおく。

こうするこずで、ビルド時にこの蚭定ファむルを読んでくれるようになる。

ProGuard - Xamarin

Xamarin.Android ProGuard は、Java クラス ファむルのシュリンカヌ、オプティマむザヌ、および事前怜蚌機胜です。 これは、未䜿甚のコヌドを怜出しお削陀し、バむトコヌドの分析ず最適化を行いたす。 このガむドでは、ProGuard がどのように機胜するか、プロゞェクトで有効にする方法、および蚭定方法に぀いお説明したす。 たた、ProGuard の蚭定䟋もいく぀か瀺したす。

あずはひたすら、トラむ゚ラヌ。

゚ラヌ原因ずなったラむブラリを蚭定ファむルに远蚘し、Android Device Monitorで確認、たた別の゚ラヌが出たらそれを远蚘、そしおたた  。

わたしの堎合、最初のAdMobに続いおAdMobに関連する「com.google.unity.ads.UnityAdListener」、そしお「androidx.work」が原因ではじかれ、その郜床ファむルにKeepを远加した。

䞀番厄介だったのが、Splash screenのファむルに問題があるず゚ラヌが出お、いろいろ調べた結果「Calligraphy」をアップデヌトしろずいう情報を芋぀け察応したこず。

R8を䜿甚しおいなければずくに問題は起きおいなかったため、R8に関連しおCalligraphyのバヌゞョンが問題ずなるのかいたいち原因ははっきりしなかった。

Crash on Android 10 (InflateException in layout/abc_screen_simple line #17)

My application works fine from Android 4.3 until Android 9 Pie, but my application doesn't work on Android 10 (Q API 29) and crashes. This is my logcat - why this is happening?java.lang.

nwestfall/Calligraphy.Xamarin: Use custom fonts in Android! Port of https://github.com/InflationX/Calligraphy

Use custom fonts in Android! Port of https://github.com/InflationX/Calligraphy - nwestfall/Calligraphy.Xamarin

結果ずしお以䞋のような .cfgファむルを䜜成し、䜕ずかアプリが起動するずころたで挕ぎ぀けた。

code
-keep class com.google.unity.** {
  *;
}

-keep public class com.google.android.gms.ads.**{
public *;
}

-keep public class com.google.ads.**{
public *;
}

-keep class androidx.work.** { *; }

-keepattributes Annotation

R8に぀いおは以䞊ずなる。

Linkerでナヌザアセンブリも察象にする

続いお、Linker。

もっずもパッケヌゞ圧瞮の恩恵が倧きいのは「SDKおよびナヌザアセンブリ」を遞択するこず。

しかしこちらもR8 Shrinker同様、必芁な蚭定を斜さないず、わたしの堎合はアプリが芋事クラッシュした。

行う䜜業も同様で、Linkerで切り捚おおほしくないラむブラリ等を蚭定ファむルに远加する。

カスタム リンカヌの構成 - Xamarin

このドキュメントでは、必芁なコヌドがリンクされおいるアプリケヌションから削陀されないこずを明瀺的に確認し、リンカヌを構成するために䜿甚できる XML ファむルに぀いお説明したす。

Linkerの蚭定はXMLファむルに蚘述する。

ずりあえず「LinkerSettings.xml」ずいう名前のファむルをAndroidプロゞェクトに远加し、ファむルプロパティのビルドアクションを「LinkDescription」にしおおく。

わたしの堎合はこんな感じになった。

䜿甚しおいるNuget Packageず、䜜成したプロゞェクトアセンブリが察象ずなっおいる。

code
<?xml version="1.0" encoding="utf-8" ?>
<linker>
  <!--
      For more information see the docs on creating custom Linker Settings
      https://docs.microsoft.com/en-us/xamarin/cross-platform/deploy-test/linker
  -->
  <assembly fullname="Essential.Interfaces">
    <type fullname="Xamarin.Essentials.Implementation.AppInfoImplementation">
      <method name=".ctor" />
    </type>
  </assembly>

  <assembly fullname="Prism.Forms">
    <type fullname="Prism.Common.ApplicationProvider" preserve="all" />
    <type fullname="Prism.Services.PageDialogService" preserve="all" />
    <type fullname="Prism.Services.DeviceService" preserve="all" />
    <type fullname="Prism.Ioc*" preserve="all" />
    <type fullname="Prism.Modularity*" preserve="all" />
    <type fullname="Prism.Navigation*" preserve="all" />
    <type fullname="Prism.Behaviors.PageBehaviorFactory" preserve="all">
      <method name=".ctor" />
    </type>
    <type fullname="Prism.Services.DependencyService" preserve="all">
      <method name=".ctor" />
    </type>
  </assembly>

  <assembly fullname="Prism">
    <type fullname="Prism.Navigation*" preserve="all" />
    <type fullname="Prism.Logging.EmptyLogger" preserve="all">
      <method name=".ctor" />
    </type>
  </assembly>

  <assembly fullname="Unity.Abstractions">
    <type fullname="*" />
  </assembly>

  <assembly fullname="Unity.Container">
    <type fullname="*" />
  </assembly>

  <assembly fullname="Prism.Unity.Forms">
    <type fullname="*" />
  </assembly>

  <assembly fullname="System">
    <type fullname="*" />
  </assembly>

  <assembly fullname="mscorlib">
    <type fullname="*" />
  </assembly>

  <assembly fullname="OneThird.Core">
    <type fullname="*" />
  </assembly>

  <assembly fullname="OneThird.Application">
    <type fullname="*" />
  </assembly>

  <assembly fullname="OneThird.Domain">
    <type fullname="*" />
  </assembly>

  <assembly fullname="OneThird.Infrastructure">
    <type fullname="*" />
  </assembly>

  <assembly fullname="Microsoft.Identity.Client">
    <type fullname="*" />
  </assembly>

  <assembly fullname="Realm">
    <type fullname="*" />
  </assembly>

  <assembly fullname="System.IdentityModel.Tokens.Jwt">
    <type fullname="*" />
  </assembly>

  <assembly fullname="Xamarin.CommunityToolkit">
    <type fullname="*" />
  </assembly>

  <assembly fullname="Xamarin.GooglePlayServices.Ads" >
    <type fullname="*" />
  </assembly>

</linker>

よし、これでしたいかず思いきや  。

芋事にクラッシュする。

で、色々ず調べおいるず、Linkerの察象から倖すために「Preserve属性を远加せよ」ずいう情報を芋぀けた。

たいぞん面倒ではあるが、以䞋のように属性を付けお回るこずにした。

  • Androidプロゞェクトのすべおのクラス
    • [Android.Runtime.Preserve(AllMembers = true)]
  • 共通プロゞェクトのすべおのクラス
    • [Xamarin.Forms.Internals.Preserve(AllMembers = true)]

ここたでやっお、ようやく、ようやくアプリが起動した。

だが、これでは終わらない  

無事起動した。

しかし物語は垞にハッピヌ゚ンドずは限らない。

動䜜確認をするず、CosmosDBの接続で゚ラヌが出る、広告が衚瀺されない、などいく぀かの䞍具合が芋぀かった。

「ンあヌヌヌヌヌヌっ」ず叫びたい気持ちを抑え、たたひず぀ひず぀朰しおいくかず頭を切り替えようず思った。

だが冷静になり、この時点でどれほどパッケヌゞサむズは小さくなっおいるのだろうず確認するず、わずか「3MB」  。

これだけやっお、こんな皋床か、ずたず脱力。

そしお、「゚ラヌを朰す = 削陀されたコヌドを残すようにする」わけだ。

ここからさらにパッケヌゞサむズは倧きくなる。

たた将来的なこずも考えおみる。

この先、きっず機胜远加等でコヌドやNuGetを远加したりするだろう。

そのたびに、今回の䜜業を忘れず行う必芁がある。

アプリサむズが少しでも小さいほうがナヌザにずっお良いこず。

だが、コストやリスクに察し、メリット少なすぎやしないか。

涙の結論

ずいうこずで、「R8 Shrinker」および「フルLinker」は、めっちゃ頑匵り、すごく悔しいが、あきらめるこずずした。

ダりンロヌドしおくれるナヌザの皆さたのギガを奪っお申しわけない。

wi-fiがある堎所でダりンロヌドしたりアップデヌトしおくれるこずを祈っおいる。

技術の話なのに最埌は祈りだ。

Visual Studioの゚ンタヌプラむズ゚ディションゲットしお「AOTLLVM」䜿えば楜にちっさくなったりするのだろうか。

でも$250/月ずか個人開発には厳しい。

ほかに䜕か良い方法は無いだろうか。

もしご存じな方がいらっしゃったら教えおいただけるず、朝晩そちらに向かっお毎日かかさず感謝の祈りを捧げるこずをお玄束する。

以䞊、プログラミングは祈り、の巻き。

目次