スマートニュースで Android アプリを担当している大橋と申します。

先日、弊社のイベントスペースで Shibuya.apk #19 が開催され、私も登壇させていただきました。 今回はイベントの様子のご報告と、私の発表で時間の都合で省いた部分について補足説明させていただきたいと思います。

Shibuya.apk #19

Shibuya.apk は渋谷を中心に活動する Android アプリ開発者コミュニティです。 様々な場所で不定期に開催されていますが、19 回目となる今回は、スマートニュースのイベントスペースで開催されることになりました。

前半は 15 分程度の発表が 4 つあり、懇親会を挟んで、スタンディングでざっくばらんとした雰囲気の中、各 5 分程度のライトニングトークが 5 つ行われました。 発表は開発環境やデバッグの話から Android の最新機能についての話などバラエティに富み、興味深い内容ばかりでした。 私は Shibuya.apk に参加させていただくのは初めてだったのですが、活気のあるレベルの高いコミュニティだと感じました。

スマートニュースでの開催ということで、私も登壇させていただき、SmartNews アプリの簡単な説明と最近遭遇した問題・その解決方法について発表いたしました。この発表の資料は こちら からご覧いただけます。

スマートニュースの Android アプリの技術的な話が聞けると楽しみにしている参加者がいらっしゃるかもしれないと、勝手にプレッシャーを感じながら緊張しつつ発表に臨みました。

面白い話題を提供できるか不安でしたが、懇親会でかけていただいた言葉や Twitter 上でのリアクション(#shibuya_apk)を拝見する限り、多少なりとも皆さんに興味を持っていただける発表にはできたのかなと思います。

発表の補足

Shibuya.apk ではいくつかの内容を発表させていただきましたが、その中でも時間の都合で詳細な説明を省いた 発表資料の15ページ目 の「WebView を生成していることが density が変わらなくなる条件」の箇所について、なぜそうなるのか補足させていただきたいと思います。

「Display size を変更しても density が以前のまま」と説明させていただきましたが、具体的には画面サイズの変更前後でテキストサイズが変化しない部分があるという現象が起きていました。

そこで TextView が生成される時のテキストサイズの決定方法を糸口に Android OS のソースコードを調査しました(こういうとき、オープンソースだとありがたいですね)。 静的解析の結果、View 生成時に利用される TypedArray クラスが持つ DisplayMetrics がおかしくなった時にこの現象が起き得るという仮説が得られました。

TypedArray はキャッシュとして Resources クラスで5個ほど所有される可能性があります。 そこで試しにリフレクション API を駆使してキャッシュされている TypedArray が持つ DisplayMetrics を dump してみたところ、画面サイズ変更前の density のままになっているものがいくつかあり、仮説が正しいことが証明できました。

density が更新されるものとされないものの差は何かを調べるため、まずは DisplayMetrics の参照関係を特定することにしました。 Resources と TypedArray の実装を解析したところ、下記の関係になっていました。

DisplayMetrics の実体は ResourcesImpl クラスが所有しており、TypedArray はその参照を持っていました。 画面サイズ変更時は「Resouces -> ResourcesImpl -> DisplayMetrics」という流れで density が更新されますが、上記のような関係のままなら TypedArray が参照している DisplayMetrics の density と一致するはずです(DisplayMetrics のインスタンス自体が一致するはずです)。

しかし、現象が起きた後にリフレクション API を使って dump してみると、ResourcesImpl の持つ DisplayMetrics インスタンス と TypedArray が参照しているインスタンスが異なっていることがわかりました。 このような状態になる可能性として、どこかのタイミングで ResoucesImpl インスタンスが入れ替わっていることが考えられます。 実際に Resouces.setImpl() というメソッドがあり、もしそれが使われると下記のような関係になる可能性があります。

この状態になった場合、Resources クラスから参照が外れた ResourcesImpl インスタンス(上記赤枠部分)は所有している DisplayMetrics が更新できず、density は元のまま、ただしキャッシュされている TypedArray にはそのインスタンスを参照しているものが存在することになり、density の不一致が起きます。

Resources.setImpl() の呼び出しが鍵になるところまでわかったので、あとは呼び元がどこかを地道にソースコードを追ってみました。 その中で可能性の一つに WebView の生成がありそうだということにたどり着きました。

WebViewDelegate.addWebViewAssetPath() -> ResourcesManager.appendLibAssetForMainAssetPath() -> Resources.setImpl()

問題が起きていた Activity では WebView を生成していたので、リフレクションを使って生成前後で ResourcesImpl インスタンスが変わるか確認したところ、実際にそうなっていました。 また WebView の生成をやめてみると現象が起きなくなることから、条件の 1 つであることが特定できました。

以上が「WebViewを生成していることが density が変わらなくなる条件」の説明になります。 WebView 生成以外にも density が変わらなくなる場合はあり得ると思いますので、もしその状況に遭遇したら発表させていただいた Workaround を試していただけたらと思います。

終わりに

100 人規模の勉強会への会場提供を行なっております

スマートニュース渋谷オフィス 2F イベントスペースは最大 170 名まで収容でき、アルコールを含む飲食も可能です。所在地等はこちらをご参照ください。

勉強会とその懇親会開催はもちろんのこと、ミートアップイベントの開催もお手伝いしております。 弊社スタッフがお手伝いできるイベントでしたら、IT エンジニア向けのイベントに限らず、会場提供させていただいております。

会場提供をご希望の方は dev-event-space-jp@smartnews.com または弊社のエンジニアまで、ご希望の日時とイベントの概要をお気軽にご連絡ください。

スマートニュースは Android エンジニアを絶賛募集中です

スマートニュースでは Android エンジニアを絶賛募集中です。大橋と一緒に働くことに興味を持ってくださったあなたのご応募をお待ちしております。