おでーぶでおでーぶ

いろいろ書く。いろいろ。

OSS の公開と保守再開的な何か

最近いくつか OSS を公開したり、保守を再開しました。最近は Ruby 4割弱、BashScript 2割、Kotlin 2割、Golang 1割、YAML 1割、残りJava みたいな生き方をしています。

danger-apkstats

Assertion system である danger/dangerAndroid apk 解析用 plugin です。

github.com

現職のアプリの機能は非常にシンプルで、だからこそむやみに apk サイズを大きくすることは好まれません。

そういうわけで apk サイズの変化を計測したかったこと、また permission や feature についても同様に注意を払うために「2つのapkの差分をレポートする」機能を実装しました。

どうやって前の apk を用意するかについては今後どこかで LT をするような気がします。

dpg

DeployGate API clientのGolang CLIを公開しました。 CLI ですが、API クライアントとしても利用できるようなパッケージ構成になっています。 初 Golang OSS な上に実務 Golang-er と働いたこともないので、パッケージ構成やら何やら非常に Java っぽい。マサカリをください。

github.com

公開した背景

DeployGate ではメンバー管理を行うための API も公開されていますが、アプリ開発者の関心事は「デプロイ(アップロード)」に注視しがちで、あまり他の API について関心を払う人はそう多くないように見受けられます。本来開発版アプリの管理を考えるなら手動による管理や手放しの運用は決して褒められるものではなく、メンバーデータと API を用いた自動管理を前提としたいところです。

公式ドキュメントとして curl サンプルが存在するので API コール自体ができない人はそういないと思いつつも、エラーレスポンスのクオリティは逆立ちしても実用的とは言えず非常に難を強いてしまっているのではないかと思っています。1

そこで、ある程度のバリデーション層を用意し、かつそれらを強い静的型付けの中で行い、CI との親和性や保守性を考えた結果が Golang によるクライアントの実装でした。これの開発中にエラーメッセージが意味不明すぎて、サーバーのコードを読まないとトラブルシューティングできない不便さに本気でブチギレていたことは記憶に新しいです。

公式 dg コマンドとの違い

公式ではすでに dg コマンド、gem名称は deploygate として公開されたものがありますが、これはデプロイ周りのヘルパー CLI であって純粋な API クライアントではありません。2

GitHub - DeployGate/deploygate-cli: A command-line interface for DeployGate

非公式の理由

これは僕個人の感情が多いに影響しています。

公式として提供する場合、会社として精神的な面も含めてメンテナンスの運用体制を整える必要があります。Golang による実装、IssueやPRへの対応といった人的リソースに関してはもちろんのこと、会社として実装するということは「機能の取捨選択を会社目線で行う必要性」が出てきます。

現在すでに webサービス以外に dgate、gradle plugin、android sdk を抱えており、人的リソースについてはお世辞にも「人を割ける」とは言えません。

また dpg はただの API クライアントではなく、いくつかの API を組み合わせて実現する半・全自動化された手続き(現状は配布ページの自動作成・削除のみ)のサポートを視野に入れています。こういった応用について、会社からの例の提示ではなく機能として提供するケースでは多くの意思決定を挟む必要があります。特に破壊的変更によるサポート提供の停止について、公式資産がその意思決定に影響を及ぼすこともあるでしょう。

それが嫌だったという表現ではなく、「現状、それはお互いに不幸である」と思ったので非公式にしています。

remocon

remocon は Remote Config as a Code を目標とし、YAMLベースで Remote Config を管理するための Ruby Gem です。これの保守を行いました。

github.com

そもそもの開発モチベーション

前職では Remote Config をフル活用していたのですが、値に JSON を用いるも Web コンソールからはバリデーションが存在しない、PUBLISH CHANGES 押し忘れなどの問題が存在していました。これらの問題点を解決するため の、そして shibuya.apk の発表に間に合わせるため に開発を行いました。

保守

現職では Remote Config を使っていないことからメンテナンスを放置していたという怠惰な背景があります。

つまりgem 開発のモチベーションは前職に置いてきたのですが、 現職でも Remote Config を利用できるようしておこうかなと思ったことと、最近前職の先輩が CI 連携(未完成品だった)を修正しようとしている悲鳴(CI failure 通知や数十にも及ぶチャレンジコミットなど)が聞こえてきたこともあり、放置していたタスクを再開しようと思い直しました。3

APIの変更

ちょっと見ない間に API レスポンスの仕様が変わっていたり、validation のみを行う API コールが増えていたりと、色々と対応した結果、0.1 から現在は 0.4.1 まで上げました。

実装にあたり API ドキュメントに誤りがあったので、フィードバックを送っています。とはいえクリティカルな部分でもなんでもなかったですし、全体的にドキュメントが丁寧で最高でした。

新しいコマンドや機構の実装

Firebase Remote Config は短命の OAuth2 トークンを使って認証を行います。サンプルがあまり見当たらなかったことと発表に間に合わなかったので python で代替していたのですが、今回トークン発行のコマンドを Ruby で実装しておきました。YAML 管理を行わなくとも、remocon を使えばトークン発行、configの取得・更新を利用できるようになったという形です。

また以前は YAML にある定義をヒューリスティックに更新する機構がなかったため、web コンソールから変更を行ってしまったのち、それらの変更を YAML に反映させるためには手動で行う必要がありました。現在はこの問題点に対し、シンプルな差分を計算し、ある程度は YAML を自動更新できるようにしています。

Remocon Starter Kit

gem だけあっても使えないんだけどというクールな DM を貰ったので、一理あるなあと思って CircleCI 上でメンテナンスを開始するためのスターターキットも作成しました。

このリポジトリには CircleCI で remocon を使った Firebase Remote Config の管理を始めるにあたり、必要なジョブ(.circleci/config.yml)やスクリプトを埋め込んであります。

github.com

これのテストをセキュアにやるにはどうやればいいのか思いつかなかったので、今は手動でやっています。とてもつらいです。何かあったら Issue 立ててください。

今後保守するもの

Vector Drawable Previewer

VectorDrawable をプレビューする Chrome Extension です。

が、バグを放置したまま保守をしていませんでした。現職のアイコン類を全て VectorDrawable にする必要があるんですが確認がつらすぎるので、実は今一番必要に迫られています。

github.com

import-android-icons

public にしてるんですが公開の体裁を整えていません。また jar とシェル芸とnode という継ぎ接ぎなので、全部 jar にして提供し直そうと思います。

github.com


  1. 現在エラーメッセージの修正を行っています。

  2. dgate と書いていましたが 正しくは deploygate でした。

  3. 前職には業務委託として別件をお手伝いさせてもらっています

java.lang.RuntimeException: Failed to parse data binding compiler options. Params: だけ出てビルドできない

multiple module構成のprojectで、とあるmoduleに汎用BindingAdapterを寄せていたら発生した。 なんのoptionがパースできないんだ・・・と思ったら

android {
  ...
  dataBinding {
    enabled = true
  }
  ...
}

が抜けていただけだった。エラーメッセージ・・・という感じ。

Retrofit2でmultipart × multiple images をuploadする

久しぶりにやったらドチャクソハマったのでメモ

interface Service {
    @Multipart
    @POST("upload")
    Call<Response> upload(
            @Part(/* images[] */) MultipartBody.Part[] images,
            @Part("message")
                    RequestBody message);
}
Service service = getService();

List<MultipartBody.Part> parts = new ArrayList<>();

for (String imageUrl: imageUrls) {
    Uri uri = Uri.parse(uriString);
    String mimetype = context.getContentResolver().getType(uri);
    
    if (mimetype == null) continue;

    File tempFile = createTempFile(uri);
    parts.add(MultipartBody.Part.createFormData("images[]", tempFile.getName(), RequestBody.create(MediaType.parse(mimetype), tempFile)));
} 

RequestBody messagePart = RequestBody.create(MultipartBody.FORM, message);

service.upload(parts.toArray(new MultipartBody.Part[0]), messagePart);

DroidKaigi2018を致命傷で終えて

DroidKaigi2018を終えて

スタッフとしては今年で3回目の参加です。

  • 2015年の初回は一般参加
  • 2016年は東工大で開催だったので地の利(当時在学中)を活かして当日スタッフ
  • 2017年は発表者兼通年スタッフ
  • 2018年は通年スタッフ

という感じ。 今回はセッション公募・採択・タイムテーブル周りのリーダー、また部屋担当(司会や補佐の割り振り等)のリーダーをやっていました。

セッションでは連絡係もやっていたので、松田 and/or Jumpei と名乗っていたのは僕です。
とあるスピーカーの人も同じ苗字だったので、松田(jmatsu) という意味不明な名乗り方をしたこともありました。

何か至らない点がありましたらフィードバックをお願いします!!!

セッション公募・採択・タイムテーブル

アサイン方法

DroidKaigi 2018やるぞ〜〜〜ってなってミーティングが開かれ、遅刻してのこのこ到着したときに、ひつじさんから「どう?」って言われました。 今にして思えば、トイレいってからミーティング参加すればよかった。

ニッチというトピック

今回は「ニッチ」というトピックを入れてみました。ひつじさん(代表)が。僕は「いいっすね!」って何も考えずに承認欲求ボタンを押しただけです。

どうなるのかなー・・・と不安になったんですが、結構期待度や評判もよく、 確かに例年よりもセッションの幅が広がっていたような印象を受けました。

もっと褒めてくれても良いんですよ。ひつじさんを。

CfP システム

今回は sessionize.com というサービスを利用しました。このサービスは KotlinConf 2017, 2018 でも使われています。

去年も応募してくださった方の中には覚えている方もいると思うのですが、 Google Form を使って応募を集めていました。
そのシートをmasterとし、スクリプトでメールを送っていたわけです。セッション一覧用のjsonも我らがWebサイト班渾身のスクリプト芸で生成してました。

そういう面もあって、今年は CfP 系サービスを利用しよう!という流れになったわけですね。apiもあるし。

結論からいうと「すごい助かった」し、「致命傷で済んでよかった」という感じです。

バグやら機能要望やら何やらで大量のフィードバックを投げ、リアルタイムで sessionize のメンバーから「直ったよ」と連絡をもらい、直らないものはワークアラウンドで手動修正を行い・・・などなどの手間が発生したことは否めないんですが、メールの一斉送信やタイムテーブルAPIなどは非常に楽でした。

タイムテーブル

賛否両論あるかと思います。このセッションたちが聞きたかったのに同じスロットにある!、自分の発表スロットに聞きたいセッションがある!、といった例が多そうですね。

わかる。

司会や補佐

最初のホールセッションなどでいくつか司会をしていました。イベント途中からちょっと指示出しの必要が多くなって、結局他のスタッフにシフト交代をお願いすることも多かったです。皆さま本当にありがとうございました。

ウェルカムトークの謎の司会

ウェルカムトークの司会って実は決まっていなかったというか、そんな打ち合わせは(僕の知る限り)なかったんですよね。

開始が10分遅れの時点で割と「どうすっかなー」とぐるぐる思考を巡らせていたんですが、オープニング動画を始めてもらうようお願いし、舞台が暗転したところでひつじさんが急に「なんか良い感じによろしく」って言った時は「おっ、今日は泣いて帰ろう」ってちょっと思いました。

しかも日英どっちでもいわないとダメなんじゃないの?って思ったらもうどうでもよくなりました。正直何言ったか覚えてないです。

ホール第一スロットが終わったときのアナウンス

日英両方めっちゃ巻きで話しました。それでも長くてごめんな・・・って気持ちで胸がいっぱいでした。

一応原稿は作っていったんですが、巻くためにガン無視して日英アドリブでアナウンスした結果、汚い英語で大変申し訳ありませんでした。

名札をつけてくださいと言いましたが、実は僕がつけていなかったのは内緒です。

Day1パーティー会場への誘導

大変申し訳ありませんでした。完全に輸送能力を超えるという凡ミスでした。
参加者の方々や業者の方々の多大な理解と協力があってこそ、最終的に移動が叶ったと思っています。本当にありがとうございました。

来年

英語で発表とかできたら、いいな・・・

おまけ

名札とスタッフパーカーかわいい

f:id:jmatsu:20180211152810j:plain

みーちゃんの葛藤 #musani

SHIROBAKO Advent Calendar 4日目 (https://adventar.org/calendars/2092) です。 3Dクリエイターのみーちゃんこと藤堂美沙(とうどう みさ)を取り上げます。 主人公人5人の中で唯一のアホ毛キャラです。ちなみにこのアホ毛は重力に逆らうことのできるタイプのアホ毛です。

f:id:jmatsu:20171204005626j:plain

出典: http://shirobako-anime.com/character-04.html

SHIROBAKO では主人公宮森あおいを含めたアニメーション同好会の5人がそれぞれ夢について悩み、葛藤し、そして乗り越える様が表現されます。

さて、みーちゃんは唯一転職という道を選び、葛藤を乗り越え、チャンスを得るに至ったキャラです。現職では自分のやりたいアニメには関われない・・・さあどうする?という立ち位置の葛藤が描かれます。

5人それぞれの葛藤

アニメーション同好会の5人はそれぞれ違う葛藤を抱えています。

制作 宮森

  • アニメーション会社で制作という立場におり、すでにアニメを作っているが、将来の目標がない

作画 絵麻

  • 著名な原画担当のようになりたいという目標もあり、すでにアニメに携わっているが、自分の実力を卑下して悩む

声優 ずかちゃん

  • 声優の卵になったものの仕事が来ず、この先どうなるか分からない

作家志望 りーちゃん

  • 職人的な立ち位置でもなく、どうアニメの道に進めばいいのか分からない

3Dクリエイター みーちゃん

  • 現職が自分の作りたいアニメに続いているか分からない

アニメーション会社に入った宮森と絵麻、声優プロダクションに所属するずかちゃん、まだ道の拓けている学生のりーちゃん、これら4人と異なり、すでに「ある程度の道筋に乗ってしまっている」ことそのものが悩みの根幹になります。

5人で飲んでるときも一人だけ「このまま仕事を続けていいんだろうか」という悩みをぶちまけ、仕事がとれないというずかちゃんとびっみょーに気まずい空気になる場面も・・・

みーちゃんの葛藤をもうちょい見てみる

最初、みーちゃんは3DCG制作会社に入社(1年目)しているところからスタートします。
業界では高待遇で比較的安定した会社に入社できたようで、武蔵野アニメーションの3DCG監督は「あんないいところやめちゃうの?」と表現するくらいです。

ただ会社が受ける案件の都合上、ホイールとタイヤの3Dモデリングを行なう仕事しかやっていません。アニメを作りたいという思いがありながらも、です。

f:id:jmatsu:20171204020942j:plain

出典 : http://anicobin.ldblog.jp/archives/42240273.html

今の仕事を続けるべきか、他の職場を探すべきか、答えが出せないままタイヤとホイールのモデリングがどんどん上手になっていきます。
もうこれについてはめちゃくちゃ分かりますね。今 Android 開発をやっていますが、Android OS ってこれからどれだけ続いていくんだろう・・・とか思うわけです。それで身につくのは後方互換を維持したワークアラウンドtipsで、Android以外では使えないものばかりなんですよね・・・いやなんでもないです

f:id:jmatsu:20171204005712j:plain

出典 : http://anicobin.ldblog.jp/archives/42240273.html

見てくださいこの顔、タイヤをモデリングしてるとは思えない、まるでゴミを見るときのような顔です。グッときますね!僕もこんな顔して Android 開発したい、というかこんな顔で見られながら開発したい。

まあとりあえず、現職の仕事内容と自分のやりたいこととのギャップに悩むわけです。でも実はやりたいことが具体的でないことを現職の社長に指摘され、ぐぬぬとなるシーンも・・・

f:id:jmatsu:20171204121510j:plain

出典 : http://anicobin.ldblog.jp/archives/42240273.html

転職という選択

これは消去法に見えますが、そのまま続けるという選択肢もあったと思うんですよね。

みーちゃんの最初の職場の社長の言葉に「ディレクターの土橋くんが褒めてたよ。最初は雑だったけど、ポリゴンの流れが綺麗になったって」という褒め言葉があるんですね。これ「ホイールやタイヤのモデリングがうまくなったね」ではなくて、3Dモデリングの基礎技術の向上を褒めてるんですよ。少なくとも「今転職をしなくとも基礎技術を固めることはできて、そのあとからでも転職は遅くないと思うよ」的な社長の隠れた言葉が見えるんですよ(多分)。

とはいっても転職を選ぶわけですが、ちゃんとこのタイヤモデリングが報われる日が来ます。

f:id:jmatsu:20171204120158j:plain 出典: http://anicobin.ldblog.jp/archives/43240658.html

前職でひたすらモデリングしたタイヤとホイールが自分の得意分野として認識され、新しい職場で評価されます。自分の目指すアニメとは違うと思っていた前職のスキルが、夢に近づいた新職場で役に立つ。みーちゃんもここで前の職場の社長の思いを理解したんだと思います(多分)。 Androidはまあアレですけど。

この新職場で第三少女飛行隊に関わり、5人でアニメを作るという目標を達成することができます。かっこいい。

最後に

正直 Android への雑念が溢れて収集がつかなくなってきたので、とりあえず葛藤を乗り越える前のみーちゃんとその後のみーちゃんを見ましょう。

f:id:jmatsu:20171204011847j:plain

出典 : http://anicobin.ldblog.jp/archives/42240273.html

前職で新しくホイールの仕事をもらった時の顔です。かわいい。ではなくて、嫌そうな感じが顔に出ていますね。

f:id:jmatsu:20171204012107j:plain

出典 : http://anicobin.ldblog.jp/archives/43988621.html

新職場で急遽「戦闘機の型を変更してくれ」と言われたときの顔です。どう考えてもブラックすぎてヤバイんですがすごくやる気に満ちた顔です。

まとめ

転職しなかった世界線も見たいなあ。

f:id:jmatsu:20171204125812j:plain

出典: http://anicobin.ldblog.jp/archives/41644190.html

かわいい。

Apple IDの確認コード on macOS が👎という話

複数デバイス間で Apple ID を共有していると、以下の手順を利用してログインする必要がある。

  1. Apple id とパスワードを入力
  2. 他デバイスで承認、確認コードを表示
  3. 確認コードを入力

例えば webログインを行なうと、手順3で新たに確認コード入力画面が表示される。

で、Marvericks (OS X 10.9.5) で App Store に入ろうとしたら先の手順を要求された。

f:id:jmatsu:20170527174121j:plain

↑ が手順1 を終えた段階。ここでもう1回サインインを押せば確認コード入力画面が出るのかなー、と思ったら出ない。 それどころかパスワードが間違ってるとしてロックされる。
正解は パスワード + 確認コードをパスワード欄に入力するという話。

よくよく読めば確かにそういってるんだけれど、確認コード入力画面の存在を知っていると普通にハマる。
結局ドハマリして、5回パスワードリセットをしてツラい。

もっとクソなのは iCould アカウント追加画面で、そっちに至っては確認コード入力画面も出ないし、パスワード入力画面も閉じてしまう。どうすればいいんですかね・・・

ref: https://discussionsjapan.apple.com/thread/10172876?start=0&tstart=0

Android Architecture Components の Lifecycle モジュールの APT を追う

Google I/O 2017 で Android Architecture Components が公開されました。

developer.android.com

簡単に Lifecycle の Observer を作成できます。どうやって実現しているのでしょうか?

試しに簡単な LifecycleObserver を作ってみましょう。

class MyActivity extends LifecycleActivity {
  static class MyLifecycleObserver implements LifecycleObserver {
    private static final String TAG = MyLifecycleObserver.class.getSimpleName();
    @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
    void onResume() {
      Log.d(TAG, "onResume");
    }
  }

  void onCreate() {
    getLifecycle().addObserver(new MyLifecycleObserver());
  }
}

APT で生成された実装は以下です。

// 同一パッケージ
public class MyActivity_MyLifecycleObserver_LifecycleAdapter implements GenericLifecycleObserver {
  final MyActivity.MyLifecycleObserver mReceiver;

  MyActivity_MyLifecycleObserver_LifecycleAdapter(MyActivity.MyLifecycleObserver receiver) {
    this.mReceiver = receiver;
  }

  @Override
  public void onStateChanged(LifecycleOwner owner, Lifecycle.Event event) {
    if (event == Lifecycle.Event.ON_RESUME) {
      mReceiver.onResume();
    }
    if (event == Lifecycle.Event.ON_CREATE) {
      mReceiver.onCreate();
    }
  }

  public Object getReceiver() {
    return mReceiver;
  }
}

上記はとてもシンプルですね。分かりやすいです。では LifecycleRegistry#addObserver を追っていきます。

// in : LifecycleRegistry
@Override
public void addObserver(LifecycleObserver observer) {
  ObserverWithState observerWithState = new ObserverWithState(observer);
  mObserverSet.putIfAbsent(observer, observerWithState);
  observerWithState.sync();
}

なるほどなるほど。では ObserverWithState の中へ。

// in : LifecycleRegistry
class ObserverWithState {
  private State mObserverCurrentState = INITIALIZED;
  private GenericLifecycleObserver mCallback;

  ObserverWithState(LifecycleObserver observer) {
    mCallback = Lifecycling.getCallback(observer);
  }
}

GenericLifecycleObserver は自分では実装していませんね。APT で作成したクラスから取るのでしょう。では Lifecycling#getCallback へ。

// in : LifecycleRegistry
@NonNull
static GenericLifecycleObserver getCallback(Object object) {
  if (object instanceof GenericLifecycleObserver) {
    return (GenericLifecycleObserver) object;
  }
  ...

違いますね。次です。

// in : LifecycleRegistry
//noinspection TryWithIdenticalCatches
try {
  final Class<?> klass = object.getClass();
  Constructor<? extends GenericLifecycleObserver> cachedConstructor = sCallbackCache.get(
  klass);
  if (cachedConstructor != null) {
    return cachedConstructor.newInstance(object);
  }
  cachedConstructor = getGeneratedAdapterConstructor(klass);
  if (cachedConstructor != null) {
    sCallbackCache.put(klass, cachedConstructor);
    if (!cachedConstructor.isAccessible()) {
      cachedConstructor.setAccessible(true);
    }
    return cachedConstructor.newInstance(object);
  } else {
    sCallbackCache.put(klass, sREFLECTIVE);
  }
  return new ReflectiveGenericLifecycleObserver(object);
} catch (IllegalAccessException e) {
  throw new RuntimeException(e);
} catch (InstantiationException e) {
  throw new RuntimeException(e);
} catch (InvocationTargetException e) {
  throw new RuntimeException(e);
}

コンストラクタをキャッシュして早くしてるだけなので、一回キャッシュ周りの処理を消しましょう。

// in : LifecycleRegistry
  final Class<?> klass = object.getClass();
  cachedConstructor = getGeneratedAdapterConstructor(klass);
  if (cachedConstructor != null) {
    if (!cachedConstructor.isAccessible()) {
      cachedConstructor.setAccessible(true);
    }
    return cachedConstructor.newInstance(object);
  }
  return new ReflectiveGenericLifecycleObserver(object);

つまり getGeneratedAdapterConstructor を見ればいいわけですね。

// in : LifecycleRegistry
@Nullable
private static Constructor<? extends GenericLifecycleObserver> getGeneratedAdapterConstructor(
        Class<?> klass) {
    final String fullPackage = klass.getPackage().getName();

    String name = klass.getCanonicalName();
    // anonymous class bug:35073837
    if (name == null) {
        return null;
    }
    final String adapterName = getAdapterName(fullPackage.isEmpty() ? name :
            name.substring(fullPackage.length() + 1));
    try {
        @SuppressWarnings("unchecked")
        final Class<? extends GenericLifecycleObserver> aClass =
                (Class<? extends GenericLifecycleObserver>) Class.forName(
                        fullPackage.isEmpty() ? adapterName : fullPackage + "." + adapterName);
        return aClass.getDeclaredConstructor(klass);
    } catch (ClassNotFoundException e) {
        final Class<?> superclass = klass.getSuperclass();
        if (superclass != null) {
            return getGeneratedAdapterConstructor(superclass);
        }
    } catch (NoSuchMethodException e) {
        // this should not happen
        throw new RuntimeException(e);
    }
    return null;
}

static String getAdapterName(String className) {
    return className.replace(".", "_") + "_LifecycleAdapter";
}

bug とか書いてあって不穏ですが、canonical name は匿名クラスだと null になります。問題ありません。さて canonical name がある場合、パッケージ名等から LifecycleAdapter 名を作成します。最初の APT で生成されたクラスを作り、そのコンストラクタを返します。

// in : LifecycleRegistry
  final Class<?> klass = object.getClass();
  cachedConstructor = getGeneratedAdapterConstructor(klass);
  if (cachedConstructor != null) {
    if (!cachedConstructor.isAccessible()) {
      cachedConstructor.setAccessible(true);
    }
    return cachedConstructor.newInstance(object);
  }
  return new ReflectiveGenericLifecycleObserver(object);

コンストラクタがあれば、最初に渡した Observer を引数として GenericLifecycleObserver を、今回は MyActivity_MyLifecycleObserver_LifecycleAdapter を作成します。非常にシンプルですね。

canonical name がなかった場合は getGeneratedAdapterConstructor が null を返していましたね。つまり ReflectiveGenericLifecycleObserver なるものにたどり着きます。

f:id:jmatsu:20170522141928j:plain

その中身は黒魔術の塊・・・といいたいんですが、Reflection ではクラスを作れないので、本来は APT でクラスにしておく部分を Runtime で処理しています。

ということで、匿名クラスで LifecycleObserver を作成してしまうと初回のみコストが高いことになります。コストが高いといっても感はありますが。 それに一度生成すればオンメモリキャッシュする機構もありますし、問題はなさそうです。というか、匿名クラスで作る必要を感じないんですがそれは・・・

とりあえず定義した LifecycleObserver を wrap するクラスがあり、それを動的に生成しているようです。またその wrapper は Lifecycle State を監視していて、時が来たら LifecycleObserverの該当メソッドを叩くという非常にシンプルな構造でした。とさ。