AndroidアプリプロジェクトのJDK11対応
AndroidのJDK
- Android Studioに付属しているJDKはJava8相当
- なので、公式にはJava8でビルドするのが良いと思われる
なぜJava11対応が必要になったのか
- CircleCIが用意しているAndroidビルド用Docker Imageが突然Java11になった
- ああもうしゃーないなぁとか思いながらも、変更することにした
- RobolectricがAPI29以降Java9以上必須となっていたので、まあいい機会だと
JDKインストール
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~)
基本スペック
- Ryzen 1950X
- メモリ 2400MHz / 8GB×8
- GeForce GTX 1060
- NVMe SSD 512GB×2
表OS / Windows 10 / ストレージ512GB割当
裏OS / Ubuntu 20.04 / ストレージ512GB割当
- Android(Kotlin) / Flutter / Go Serverあたりの開発に使用
- 特にAndroid Studioで顕著だが、Windowsでの動作が緩慢なため切り替える場合が多い
不満だった点
- Windows 10の動作が遅い
- Git For Windows由来のbashを使用していたが、動作が緩慢
- Android Studioのビルドが遅い
- module分割を多用するアーキテクチャなので、 ビルド速度=シングルスレッド性能 の頭打ちが気になる
アップグレード計画
乗り換え先選定
- 2017年購入時と同じく、サイコム の水冷BTOが第一候補
- 予算的にはまあギリいけるか、的な範囲
- 筐体がかっこ悪くなっている
- 追加購入して、デスクトップ2台分の接地面積を取ることも難しい
- 中古で売るにしても面倒だけが多い
- 筐体を変えない(Fractal DesignDefine R5)ため、パーツ交換を行うことにする
交換内容
- Ryzen 3950Xを導入することにした
- TRを使うと、「長く使わないともったいない」ってなってしまう
- Ryzen進化速度早いので、まあ2~3年もしたらRyzen9系に追い抜かれるだろう
- シングルスレッド性能も3950Xと3960Xはほとんど変わらない
- ソケットが異なるため、自動的にマザーボードも交換することになる
- 元々の水冷クーラーがAM4非対応(オプション別売り、終売)のため、水冷クーラーも交換することになる
- 最大メモリスロットが8スロット -> 4スロットに半減するため、メモリも交換することになる
- PCIe 4が使えることと、Windowsの動作速度向上のためストレージも買い換えることになる
- 後々グラボを載せ替えるのも面倒なので、グラフィックボードも交換しておく
- という理由でドミノ倒しのように殆どの機材が入れ替えとなる
嘆き
- タイムセールで買っておきゃよかったぁ!!!!
AmazonのタイムセールでRyzen 3950Xが3万円台になってる
— 川峠@Andriders (@eaglesakura) 2020年6月27日
最終的な構成変更
- 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
交換後の感触
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を指定していたので、正常にgradle syncできなかった
zulu-8.jdk
をIntellijに読ませることで解決
まじかよ
- 何じゃこりゃ
- 半日潰してしまった
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だからか?
- わからないが、コレで逃げられることはわかった
仕事用モバイルノートの要件 2020
新型PCが発売される季節なので、選定の際の要件を定義しておく。 状況を整理するのは良いことだ。
利用シーン
- 会社が運営している施設で作業するのに使う
- 往復で徒歩40〜50分くらいになる
- 基本的に自宅作業なので、気分転換にカフェとかで仕事する
- 平時であれば子供を保育園に送ったあと、眠気覚ましも兼ねてカフェで仕事する
- 30分くらい歩く
- 子供の習い事の待ち時間に、カフェとかで仕事する
- 最近は子供の自宅保育になってしまったので、近所の嫁さん実家で仕事するときに使う
- 映画を見るとき、時間調整でカフェで仕事するときに使う
- 往復で40分くらい歩く
現状のマシンの不満点
- 2019年に購入した
Thinkpad X1 Carbon 2018
- メモリが足りない
- 16GBじゃAndroid Studioだけでカツカツになる
- moduleを細分化して影響範囲を最小化するアーキテクチャを採用してるので、とにかくメモリを喰う
- HDMIで4K/60Hz出力できない
- たまに作業のために4Kモニタに接続するとカクカクして気持ち悪い
必須機能
- ストレージ512GB / OS
- RAM 32GB
- できれば64GB
- Thunderbolt3端子
- ほしい
- HDMIで4K/60Hz出力できるとなお良い
- Type-C PD充電対応
- 必須
- 専用の充電スポットを設けるとか、2020年には考えられない
- Mac Book Pro 15inch 2013を使わなくなった理由が、充電スポット問題
- GPU
- 基本的にiGPUで十分
- 4K出力だけ必須
- Wi-Fi6
- できればほしい
- 重さ
- スペック次第で、1.4〜.5kgくらいまでは許容
- 2kgは腰に辛い
いつ買い換えるか
- すぐにでもほしいが
- 現状不満があるので、決まればすぐにでも買いたい
- すぐじゃなくてもいい
- できるだけ家で仕事するように調整は、まあできる
- 昨今の社会情勢のため、カフェでの作業は最近してない
候補
- XPS 13 32GBモデル
- Mac Book Pro 13inch 2020?
- 期待してる
- Mac Book Pro 16inch
- 腰に悪そう
- 同様の重さのMBP15inchを持ってみたが、重い
- Flutter用に購入したMac miniが無駄になりそう
- mini買う前に発売してくれればなぁ・・・
- メルカリしてしまうか・・・
- Flutter作業するとき、クラムシェルだと爆熱ゴッドフィンガーしそう
- MBP15-2013は爆熱ゴッドフィンガーでずっとふおーんしてた
- スペック的に3〜4年は戦えそう
- 3〜4年後の腰に悪影響ありそう
- ASUS ROG Zephyrus G14
- Ryzenすき
- 発売日未定
- おま国になるかも未定
- ThinkPad E495
- くっそ安い
- 1.7kg
- RAM 2スロットあるので自前で交換して64GBまで拡張できる
Android定型コードをスッキリさせるための Wofkflow Dispatcher ライブラリ 1.0.0を公開
Wofkflow Dispatcherとは
GitHub - eaglesakura/workflow-dispatcher
- Androidアプリ開発でよくある
画面やActivityやプロセスを跨ぐ可能性のある非同期処理
をなるべく簡単に扱うためのライブラリ - Annotation Processorを使って、定形処理を出力する
- startActivityForResult/onActivityResultの分岐を書く必要がなくなる
- Runtime Permissionの処理を書く必要が無くなる
- DialogFragmentのコールバックに気を使う必要が無くなる
一時的な情報をメンバ変数ではなく引数として扱うことができる
- 個人的にコレが重要
- Activity再生成(回転とかLowMemoryとか)しても大丈夫
- 初めてAnnotation Processorを実装した
- 以前は否定的だったけど、十分にマシンスペックが上がったので心の中で解禁
似たライブラリ
使うとどう書けるのか
- 普通に書けるライブラリはいろいろあるが、例えば
onActivityResult
に戻ってきたときに一時変数の値を使いたい場合は結構気を使う - このサンプルでは、ブラウザを開いた時刻を保存しておき、onActivityResultでToast表示に使っている
- AndroidではActivityやFragmentが再生成される(新しいインスタンスが作られる)場合があるため、save/resotreには気を使う
- 別Activityで開く -> Runtime Permissionで権限を得る -> ダイアログでYES/NOを質問するとかを順番にやると一時変数が結構増えていく
- ライブラリが自動的に一時情報を保存・レストアして、コールバック時に渡してくれるのでActivityとかFragmentをクリーンに保てる
Before
class ExampleBeforeActivity : AppCompatActivity() { private var tempDialogStartDate: Date? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) if(savedInstanceState == null) { tempDialogStartDate = startDate startActivityForResult( Intent( Intent.ACTION_VIEW, Uri.parse("https://google.com") ), REQUEST_SHOW_BROWSER ) } } override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) outState.putSerializable("startDate", tempDialogStartDate) } override fun onRestoreInstanceState(savedInstanceState: Bundle) { super.onRestoreInstanceState(savedInstanceState) tempDialogStartDate = savedInstanceState.getSerializable("startDate") as? Date } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { when (requestCode) { REQUEST_SHOW_BROWSER -> { // read temporary data, clear temporary val startDate = tempDialogStartDate!! tempDialogStartDate = null // show toast Toast.makeText(this, "done workflow, startDate='$startDate'", Toast.LENGTH_SHORT) .show() } else -> super.onActivityResult(requestCode, resultCode, data) } } companion object { const val REQUEST_SHOW_BROWSER = 0x0011 } }
After
class ExampleAfterActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) if(savedInstanceState == null) { // show browser // ExampleAfterActivity.showWebsite() is generated by kapt(workflow-dispatcher-processor). // auto save/restore/clear `startDate` state. showWebsite( Intent(Intent.ACTION_VIEW,Uri.parse("https://google.com")), startDate = Date() ) } } @OnActivityResultFlow("showWebsite") fun onShowWebsiteResult(result: ActivityResult, startDate: Date) { // show toast Toast.makeText(this, "startDate='$startDate'", Toast.LENGTH_SHORT).show() } }
どうやって実現しているのか
- startActivityとかするタイミングで、ライブラリが WorkflowProviderFragment をchildFragmentManagerに差し込む
- WorkflowProviderFragmentの中でstartActivityとかpermissionとか処理をして、結果をコールバックしている
- 一時データの保存もWorkflowProviderFragmentの中に持っているViewModelに持たせているので、呼び出し側を汚染しない
保存できるステート
- 生成されるコードを見るとわかるが、内部的にはBundleに突っ込んでいるので、Bundleに保存可能な値であれば
@OnActivityResultFlow
の引数として受け取ることができる - ステートが不要な場合は
result: ActivityResult
とか第1引数だけでメソッドを宣言すれば良い
使い続けられるのか?
- Jetpack Navigation ライブラリが同じ機能をサポートしたらコイツは役目を終える
- 公式、ガンバ・・・