eaglesakuraの技術ブログ

技術的な話題とか、メモとか。

WindowsのNTFSはtree()が遅い

起きた事象

  • WindowsAndroid StudioのGradle Sync(像のアイコン押すと走るやつ)が異様に遅い
  • 普通に gradlew 実行だけでも遅い
  • MacUbuntuだと早い
  • 具体的にはこのくらい
    • Windows: gradlew 40秒, WSL2も同じくらい
      • NVMe PCIe 4.0接続しているので、ストレージ自体の接続速度に問題は無いと思われる
    • Mac: gradlew 5秒
    • Ubuntu: gradlew 3秒

遅かった理由

  • CIのキャッシュキーにするため、特定ファイル名のハッシュ値を計算していた
  • ハッシュ計算を(やり忘れないように)gradleのビルドスクリプトの中で(常に)起動していた
  • ファイル一覧を列挙するとき、(実装を面倒くさがって)fileTree()によるリポジトリの全探索を行った
  • 結果として、NTFSの上でファイルツリーの単純全検索が走ることとなった
  • 特に build/ 配下の検索が走ってしまったのが痛かったと思われる

改善

  • ハッシュ計算処理を改善したところ圧倒的改善を行えた

反省点

  • 開発環境にWindowsを使うならファイル全検索はやめよう
  • Linuxファイルシステムは早いので気づかないかもしれない
    • それでも全検索はモノグサしすぎだったかもしれない

結論

Protocol BuffersのAndroid向け実装としてSquare Wireを採用した所感

Wire

square.github.io

Protocol Buffersを採用した理由

  • 大量のデータを扱う処理で、画面外の情報を一時的にファイルやDBに退避したい
    • ついでにIntentで渡したりするのも簡単にやりたい
  • モデルの定義を簡素化したい
  • 銀の弾丸を求めて色々検証した結果、Protocol Buffers+Wireプラグインに落ち着いた

Protocol Buffers公式プラグイン(javaliteとか)の採用を見送った理由

  • AndroidのBundleやIntentにモデルを直接putできない
    • 自分でByteArrayにエンコード/デコードしなければならない
    • やってやれないことはないけどそこまで手間をかけたくない
  • javalite実装が3.0.0(2016年頃)で止まっている
    • Firestoreとかでまだ使われているから、何かあればメンテするとは思われるが。

Wireプラグインを採用した理由

  • Proto3に対応している
  • Android Parcelizeをimplしたモデルを出力してくれる
    • 実装を見ると、ByteArrayにエンコード(Protocol Buffers形式として)してputしている
    • kotlin-parcelizeを使うことで簡単にIntentに乗せられる
  • コミット履歴からみて、アクティブにメンテされている

Wireプラグインの問題

  • Moshi Adapterを提供してる(JSONシリアライズもできる)ように見えるが、必要なClassが出力されずに例外を投げた

追記: Wire プラグインの不具合

AndroidアプリプロジェクトのJDK11対応

AndroidJDK

  • Android Studioに付属しているJDKはJava8相当
  • なので、公式にはJava8でビルドするのが良いと思われる

なぜJava11対応が必要になったのか

  • CircleCIが用意しているAndroidビルド用Docker Imageが突然Java11になった
  • ああもうしゃーないなぁとか思いながらも、変更することにした
    • RobolectricがAPI29以降Java9以上必須となっていたので、まあいい機会だと

JDKインストール

  • 好きな方法でインストールする
  • 以下、Windows環境
  • sdkman でinstallし、currentを切り替えた
  • JAVA_HOMEは変更なし

Android Studioの設定変更

  • Project Structureから使用するJDKを切り替える
  • デフォルトはAndroid Studio付属になっている
  • build.gradleの構成?によってはProject Structureが開かない不具合があるので、その場合はDefault Project Structureで切り替え、.ideaディレクトリを削除して再度プロジェクトファイルを生成させれば良い

_JAVA_OPTIONSの設定

  • 初めて知ったが、 JAVA_OPTIONS_JAVA_OPTIONS は明示的に異なり、通常ユーザー向けのRuntimeは前者を、開発者向けのJDKは後者をロードする
  • _JAVA_OPTIONSに -Djavax.net.ssl.trustStoreType=JKS を追加する

robolectricの設定

  • sdk=28 とかで固定を入れている場合、sdk=29 に変更して実行環境を調整

CircleCI設定

  • circleci/android:api-30 とか切り替えちゃっていいんじゃないかな

dokka設定

  • ドキュメントをdokka出力しているなら、バージョンを変更する必要がある
  • JDKのClass名が変わったりとかしたので
// /build.gradle
classpath "org.jetbrains.dokka:dokka-gradle-plugin:1.4.0-rc"
dokkaHtml {
    outputFormat = 'javadoc'
    outputDirectory = "$buildDir/dokka"
    noStdlibLink = true
    dokkaSourceSets {
        noAndroidSdkLink = true
        configureEach {
            noStdlibLink = true
            noJdkLink = true
            noAndroidSdkLink = true
        }
    }
}
}

メイン開発PCのパーツ交換をした

メイン開発マシンのスペックアップのため、一部のパーツを入れ替えたので今後のためにメモ。

メイン開発マシンのスペック(2017~)

基本スペック

表OS / Windows 10 / ストレージ512GB割当

  • Android(Kotlin) / Flutter / Go Serverあたりの開発
  • Adobe製品
  • Steamとかのゲーム(リモートプレイの母艦)

裏OS / Ubuntu 20.04 / ストレージ512GB割当

  • Android(Kotlin) / Flutter / Go Serverあたりの開発に使用
  • 特にAndroid Studioで顕著だが、Windowsでの動作が緩慢なため切り替える場合が多い

不満だった点

アップグレード計画

  • シングルスレッド性能の向上と、スペックダウンしないという要件を満たすため、Ryzen 3950X以上のスペックが必要
  • 第3世代Ryzen TRは非常に高額になっている

乗り換え先選定

  • 2017年購入時と同じく、サイコム の水冷BTOが第一候補
  • 予算的にはまあギリいけるか、的な範囲
  • 筐体がかっこ悪くなっている
    • 追加購入して、デスクトップ2台分の接地面積を取ることも難しい
    • 中古で売るにしても面倒だけが多い
  • 筐体を変えない(Fractal DesignDefine R5)ため、パーツ交換を行うことにする

交換内容

  • Ryzen 3950Xを導入することにした
    • TRを使うと、「長く使わないともったいない」ってなってしまう
    • Ryzen進化速度早いので、まあ2~3年もしたらRyzen9系に追い抜かれるだろう
    • シングルスレッド性能も3950Xと3960Xはほとんど変わらない
  • ソケットが異なるため、自動的にマザーボードも交換することになる
  • 元々の水冷クーラーがAM4非対応(オプション別売り、終売)のため、水冷クーラーも交換することになる
  • 最大メモリスロットが8スロット -> 4スロットに半減するため、メモリも交換することになる
  • PCIe 4が使えることと、Windowsの動作速度向上のためストレージも買い換えることになる
  • 後々グラボを載せ替えるのも面倒なので、グラフィックボードも交換しておく
    • Lightroomのプレビューやスクロールの遅さも気になってたしね
    • せっかくならレイトレーシング対応がいいね
    • せっかくならゲームももう少し品質向上できるやつがいいね
  • という理由でドミノ倒しのように殆どの機材が入れ替えとなる

嘆き

  • タイムセールで買っておきゃよかったぁ!!!!

最終的な構成変更

  • Ryzen 1950X -> Ryzen 3950X
  • 水冷クーラー Corsairの昔の -> Corsair h100i Pro RGB
  • メモリ 2400MHz 8GB×8 -> 3600MHz 16×4
  • マザーボード X399 Taichi -> X570 Taichi
  • ストレージ NVMe 512GB×2(PCIe 3.0) -> NVMe 512×2(PCIe 4.0)
  • グラボ GTX 1060 -> RTX 2080 Super

交換後の感触

  • ゲームのフレームレート向上した!!
    • BIOHAZARD RE2のオプションもりもりがだいぶできるようになってきた
    • ライザのアトリエ 4Kで起動できるようになった
    • オプションもりもりでもFF15がそこそこ動くようになった
  • Windows10 2004がインストールできるようになった
    • Ryzen 1950X+X399Taichiにはインストール不可能だった
    • WSL2サポートされた
    • 超快適
  • Cinebenchスコアが6000 -> 9000に向上
  • Androidプロジェクトはまだ触ってない。快適だと嬉しい。

Mac版IntellijでGradle Syncが出来なくなった場合の対処

発生した問題

  • 最近購入したMac Book Pro 13inch / 2020 / RAM32GBでIntellijをインストール
  • gradle syncが行えない
    • 必ず失敗し、ログも表示されない
  • IntelliJ自体のログを見ると、 Java home is different と言われてDaemon接続に失敗しているらしい

解決

  • Androidアプリビルドのために、 Java Zulu 1.8.0_252 をsdkmanを通して利用している
  • 最近のバージョンから、JAVA_HOMEが変更になった
    • 以前はインストール直下がJAVA_HOMEとして機能した
    • 新しいバージョンでは、インストール直下に zulu-8.jdk というディレクトリが使われて、そっちがJAVA_HOMEとして機能している
  • 間違ったJAVA_HOMEを指定していたので、正常にgradle syncできなかった
    • zulu-8.jdkIntellijに読ませることで解決

まじかよ

  • 何じゃこりゃ
  • 半日潰してしまった

Intellij Idea 日本語EAP版が配信されているので試した結果、アンインストールした

日本語化の利点

  • 日本語話者なので、自然に使えるでしょう
  • 初学者に勧めやすい
    • これは重要。超重要
    • 日本語Visual Studioから、英語Eclipseに移った当時(10年以上前)、必死に日本語パックの初期設定とか探した

EAP版の問題点

  • フィードバック済み
  • Run... とか、たまに使う(けどショートカットを覚えるほど使ってない)アクションの検索(Ctrl+Shift+Aに割り当ててる)も日本語化されてしまう
    • Run... ではヒットせず、 実行... と検索しなければならない
    • 翻訳されたキーワードを知らなければ使えないし、日本語・英語を検索時に切り替えるのはちょっと面倒
  • 日本語化が完全でないため、英語メニューと日本語メニューが混在
    • Build はビルドではなくBuildである
    • が、メニューには ビルド もあるので、どっちのBuildかビルドか選ぼう
  • せめて英語メニューと日本語メニューを同時に検索してほしい

結論

  • まだIntellijを触ったことがない人で、英語だとちょっと。。。みたいな感触であれば入れてあげるがよし

ChildFragmentのViewが一部条件で表示されない場合のワークアラウンド

問題点

  • MotionLayoutの中にChildFragmentでConstraintLayoutの中にFragmentの中にカメラプレビューを置いた
    • ユーザーの操作にあわせてアニメーションしたり、Viewを入替えたりする
  • サムスンHuaweiの端末(もしくはAndroid 8以上?)でChildFragment.replace()すると、カメラが表示されない 場合がある

原因

  • onCreateView後、なぜかViewのmeasure, width, heightが全部0になっている
    • Viewがレイアウトされず放置されるらしい
    • Activity.onPause/onResumeを経由(Fragment開いているときに他のActivityからonActivityResultで結果を受け取ったり)すると100%再現
  • どこが原因か、正直不明だった

ワークアラウンド

  • 自動でViewの配置やViewレイアウトの構築をトリガーしてくれないなら、手動トリガーすればいいじゃない
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        // 親FragmentのViewを経由して再度レイアウトさせる. this.viewをやっても無駄
        requireParentFragment().requireView().requestLayout()
    }

どこが原因だ?

  • MotionLayoutか?
  • ChildFragment 2段階入れ子だからか?
  • ConstraintLayout in MotionLayoutだからか?
  • わからないが、コレで逃げられることはわかった