Extensive Xamarin @ 技術書典3

こちらではだいぶギリギリの告知となってしまいましたが、10/22(日)の技術書典3で、Xamarin周辺コミュニティの何人かの方と一緒に、Xamaritansの名前で(これいつまで使うんだろ)同人誌の新刊を出します。

techbookfest.org

サークル配置番号は「お-13」、新刊は1000円で頒布する予定です。当日頒布分については、電子版のダウンロードカードもお渡しする予定です。(問題なく作業する余裕があれば…)

書名は"Extensive Xamarin"としました。書籍紹介に、まえがきのために書いたポエムをコピペってあるのですが、前作"Essential Xamarin"とはだいぶ方向性が変わって、実践的な各論を中心として、Xamarinの基本を押さえた「その先にあるもの」がたくさん集まっている1冊になっています。記事の難易度も、入門的でわかりやすいものから難解で読み応えのあるものまで、幅広く集まったと思います。

内容の詳細、プレビューが、新刊情報の公式ページで公開されているので、そちらも見てやってください。

当日は既刊"Essential Xamarin"の同人誌版も販売する予定です。

前回"Essential Xamarin"を新刊として出した技術書典2の頃は、Xamarinの新鮮な情報がほぼ書籍として存在していない状況で、各メンバーが本気で最新情報を大量に盛り込んで来た結果、印刷の都合もあって2冊に分けるほどになり、商業化されて達人出版会などでも販売されるようになりましたが、Xamarin本はここ半年くらいで基本書の類がかなり充実してきました。なので、技術書典3では商業化を目標とするのではなく、もう少し本来の「同人誌らしい」「ポピュラーなニーズを気にせず」「書きたいことを書ける」ものにしたいなあという気持ちがそこそこありました。執筆メンバーが締切を前にのびのびと書けたかはわかりませんが(!?)、結果的に集まってきた原稿を見ていて、概ね目的は達成できたかなと思っています。

ちなみに今回は裏表紙までイラストを描いていただいていたりします。贅沢仕様! (この元ネタが分かる人はほとんどいないと思いますが…)

f:id:atsushieno:20171019120618p:plain:w300:h250

10/22当日ですが、わたしは技術書典本体のスタッフとして行動しているので、残念ながら来てくださる方とお話しする余裕は多分無いんじゃないかと思いますが、サークルのブースでは他の執筆メンバーにお手伝いしていただくことになっています。今回は4Fに休憩スペースも設けてあり、整理券でいつでも再入場可能なので、ひと通り見終わったらそちらに移動して戦利品の見せ合いなどしてもらえればと思います。

今回は技術書典3本体の準備も割と忙しく、周りの何人かの方にお手伝いをお願いしたり、自分のサークル側の作業もだいぶ滞ったりと、なかなかタフな状況なのですが、当日まで何とかやっていきたいと思いますんで、みなさん技術書典3にぜひ遊びに来てやってください。

iOSDC2017に参加してきた

オマエiOSやらないじゃん?というかそもそもモバイルアプリケーションを開発するやつじゃないじゃん?という感じだけど、iOS開発ツールやObjC/Swiftについては調べたり書いたりすることもあるし(今は技術書典3向けの原稿に書くためにいろいろ調べるフェーズ)、AndroidコミュニティはけっこうわかってきたけどiOSコミュニティはほとんど知らないので、飛び込んでいろいろ教えを請うべきだろうと思って参加してみた。

 

iosdc.jp

Ubuntu使いのAndroid使いのアプリケーション開発しないわたしはあらゆる意味でアウェイだったのだけど(あ、それ.NETクラスタでもそうですかね…)、面白いセッションがたくさんあったしLTもうまいのがたくさんあったし、次回もぜひとも参加したいと思えるいいカンファレンスだった。個人的にはなぜか台湾から来ていたグループとランチしていたり(わたしがセッションやったMOPCON2014のTシャツで来ているのがいて声をかけずにはいられなかった。あと残りはKKBoxの面々だったけど、昔なぜか台北に住んでいた時にCEOのChrisの接待トークをやらされたことが…)、懇親会で中国人クラスタと話し込んだりしていて、あんまりiOS感があったわけでもなかったけど、それはそれでいいんじゃなかろうか。何の業だろうな。

 

今回はいろいろ調べて面白かったAutoLayoutの話を書きたい。1日目のキーノート的なセッションだ。

speakerdeck.com

AutoLayoutを実装しているレイアウトエンジンのアルゴリズムについての解説で、内容はAutoLayoutの制約ベースのレイアウトを実現しているCassowaryというアルゴリズムの説明だった。前段から数式だらけだった(そのうちtwitterのiOSDCまとめとか出来るんだろうと思うけど、「あーそういうことね完全に理解した」(画像略)の流れである)のだけど、それは制約ベースのレイアウトというのが本質的に線形計画問題となるからだ。ということだ。

詳しくはセッション資料にわかりやすくまとめられているけど、AutoLayoutは、複数の「制約」を(優先度を考慮しつつ)最大限満たすレイアウトを自動計算するもので、これは線形計画問題として捉えることができる。線形計画法は日本の最初のソフトウェア特許訴訟(カーマーカー特許)というかたちで知財クラスタにはよく知られている。コンピューターの計算量を最大限に活用して数学の問題を解決する技術だ。今回のセッションでは「複雑な計算をしている(だから計算力のあるマシンが求められる)」という反応がよく見られたが、線形計画問題を高い計算力で解決するというのは伝統的なソフトウェア技術の応用だ。

Cassowaryアルゴリズムというのは、レイアウト問題を線形計画問題の応用として解決する解法と考えれば良さそうだ。そして、レイアウトの問題は特定のGUIシステムに固有のものではないので、さまざまな言語・環境向けのCassowaryの実装が存在しているようだ。AndroidにもConstraintLayoutがあるけど、これもCassowaryの実装だそうだ。

www.bignerdranch.com

もっとびっくりしたのは、Gtkにすら存在していて、それがDiaCanvasが使われていた頃から存在していた、ということだ。2001年なんてもしかしてみんな生まれていないんじゃないの…

sourceforge.net

ちなみにGtk向けにはもうちょっとモダンなやつがあるようだ。

github.com

以前から「AutoLayoutもConstraintLayoutもなんか似ているっぽいしクロスプラットフォームでそれっぽいものを実現できるんじゃないの」と思っていたのだけど、どちらもアイディアが同じアルゴリズムで(先にリンクしたページにあるように)実装の違いはあっても(VisualFormatはAutoLayoutだけのDSLだろう)、レイアウトエンジンが共通化できるというのは夢のある話だ。たとえば、Cで共通コードを書いておいて、それをXamarin.Formsにダイレクトに適用できるということだ。

twitterでは何度か言及しているのだけど、XamarinではCSSのFlexBoxのモデルをCで実装したコードが開発されている。コミットログを見れば分かるけどRubyMotionハカー(lrz)がうちに入ってきてから主に作っていたプロジェクトだ。

github.comFacebookにもyogaというプロジェクトがあって、そっちのほうが有名だと思うけど、Cでレイアウトエンジンを実装しておいて、それを全方面で活用するというやり方は、今後もいくつか出てくるかもしれない。Cassowaryも完全に同じ流れでいけるような気がする。P/Invokeで呼び出すプラットフォーム中立なライブラリを作ってから、それをFormsのレイアウトシステムに合わせるのが楽そうだ。

…とまあ、こういう思索がいろいろ出来た刺激的なセッションだった。わたしはGUIより他にやりたいことがありすぎてこっちまでやる時間は無いと思うので、GUIを作り込むのが好きな人がいたらおひとつどうですかね?

 

追記: 更にいろいろあることを教えていただいた:

 QtもAngularもある…! すごい。

D8コンパイラをXamarin.Android(d15-5以降)で使う

今からだいたい1ヶ月前に、Googleが新しいDexコンパイラd8を紹介していました。

android-developers.googleblog.com

d8Android SDKの既存のツールで言えばdxに相当する機能を実装しています。dxはJava bytecode (*.class)をDalvik bytecode (*.dex)に変換するコンパイラですね。通常はclasses.dexをビルドするために使われています(が、大抵の人は意識していないでしょう)。

Android Studio 3.0のbetaでは既にこのd8を有効に出来ます。既に使えるならXamarin.Androidでも使えるようにしたいところです。ただ、リリース当初はd8のソースが見当たらず、しばらく待っていました。その後、d8がr8のリポジトリに含まれていると教えてもらったので、試しに使ってみたら上手くいきました。

というわけで、dxの代わりにr8を使うようなNuGetパッケージを作っておきました。ソース…と言うほどのものは無いのですが…はここにあります。ちなみにxamarin-androidに加えた変更もかなり小さいものです。

github.com

xamarin-android-d8-build をpackages.config に追加するとインストールできます(このパッケージはまだリスティングに載せていないので、パッケージマネージャのUIで検索しても出てきません)。パッケージをインストールするだけでMSBuildプロパティが追加されるだけで、Xamarin.Androidアプリケーション以外で副作用は無いはずです。

このパッケージですが、実のところXamarin.Androidを製品版から使用している人には使えません。xamarin-androidをmasterからビルドしている人は今すぐ使えます。そうでない人はd15-5ブランチがalpha channelに降ってきてから利用できるようになります。ちなみに今はd15-3がstable、d15-4がalphaですね。

これ自体は大したコードの変更を伴っていないのに、なぜこんな根本的な差し替えが可能なのでしょうか? それは、Xamarin.AndroidMSBuildタスクを読み解くことが出来れば誰にでも出来ます(MSBuildタスクはデバッグビルドのサポート固有の部分以外はgithubで公開されています)。dx.jarを呼び出す部分はCompileToDalvikターゲットであり、CompileToDalvikタスクでコマンドラインを生成しています。ここではdx.jarはDxJarPathプロパティ、その引数はDxExtraArgumentsプロパティで指定されています。

今回のNuGetパッケージはこれらのプロパティをオーバーライドして、同梱のr8.jarを使用するように指定しているだけです。ちなみにdx.jarを使う場合は--dex--no-strictというオプションが必要なので、これらはDxExtraArgumentsのデフォルト値となっています。r8を既存のCompileToDalvikタスクで使うにはこれらが邪魔だったので、消す作業をした、というわけです。

DxJarPathはもともとカスタマイズ可能になっていたので、全体的な変更も些少なものです。

…というわけで、ごく簡単なハックですが、MSBuildタスクをどうやってカスタマイズすればいいか、簡単に解説できるネタだったのでまとめてみました。

Essential Xamarinが"技術書典シリーズ"のひとつとして刊行されます

少し前にお知らせしていたことですが、われわれのXamarin同人誌「Essential Xamarin」が、9/1にインプレスR&Dさんから出版されます。

prtimes.jp

商業化したということで、販路が広がって、Kindle版も出ています*1

https://www.amazon.co.jp/dp/B07539YT44/www.amazon.co.jp

1冊になったので、この4体も当初の計画のように1枚絵になりました。

商業化に際しては、ひらのさん(@ailen0ada)によるXamarin.iOSの記事が追加されたり、最新情報に合わせたアップデートが行われたりしています。あとプロの編集さんが手を加えた原稿をgit diffで眺めましたが(編集作業のやり取りもgithub上で行っていました = 編集済みの原稿をcommitしていただいていました)、やっぱりプロの作業は違う…われわれも相互レビューしているのですが、文言レベルでより誤解を招かないようなかたちにする修正などは、経験が出ますねー。

Essential Xamarinは、軽い気持ちで執筆者を募集したら、気づいたらXamarinコミュニティのガチ勢が本気で書いた原稿だらけになって、これは商業化できるレベル…!と思っていたら、実際に商業化の流れになって、いい話になったなという感じでした。

もうひとつ面白いのは、本書は新しく「技術書典シリーズ」というコレクションの2冊めの本として出るということです*2。技術書典はもともと同人誌から技術書界隈を元気にしていこうというノリで始まったものでしたが、同人誌から目立ったものを出版社が拾い上げて商業化する流れがあって、既に何冊か出ています。

技術書典は、そういう意味では「宝の山」なわけです。当然、出展者の数も多いわけです。前回の技術書典2が190件近くあって、10月の技術書典3も会場が同じだしほぼ同数でしょう。そこから拾い上げられた書籍、というある種のクロスボーダーなシリーズということになるわけで、これが通好みのものなのか、完成度が高いものなのか(あるいは全然別の評価が妥当なのか)はわかりませんが、ひとつのブランドとして通っていくと面白いなと思います。

ちなみに、技術書典3には、この同人誌版のEssential Xamarinも売り物として持っていく予定です。同じ会場でインプレスR&Dさんがこの商業版も販売する予定なので(その案内もこちらのブースで出す予定です)、どのように売れるかは見てみたいですねー、という話を出版社の方としていました。

技術書典は、わたし自身は運営にも片足を突っ込みながらサークルも回しているという特殊事例なのですが(それで特別扱いされることは無いです)、サークルの回し方なども初心者なりに適宜広めながら技術書典3に向けて進めていこうと思います。Xamaritans(サークル)のほうは、初稿締切までは3-4週間ですが、まだ執筆者も募集しているので、参加してみたいという方がいたらお気軽にご連絡ください。名前はXamarin系ですが、Xamarinと関係ない記事も書かれる予定ですし、商業化クオリティ…!とか気負ったことは考えずに、同人誌らしいものをのびのびと書いてもらえればと思っています。

*1:まあKDPならわれわれでも出せましたが

*2:ちなみに技術書典シリーズ第1弾は最新JavaScript開発です。

安全なビルドタスクとクリーンタスクを実装するために気をつける点

ビルドシステムを使いこなすのは難しい。この場合「使いこなす」とは、Visual StudioでF5を押してビルドしたり、メニューから「クリーン」を選んで実行する、程度の内容ではなく、ビルドタスクを記述して他のユーザーに自分以外の開発環境でビルドを期待通りに動かす程度には複雑な操作を行うことである。

あるビルドが期待通りに動かない、という問題は、もう少し具体的に挙げるなら、たとえば次のような状態だ:

  1. ビルドが成功していると言われているのに、期待しているアウトプットが無い
  2. ビルドが途中でエラーを起こして終了する
  3. ビルドが完了しない
  4. ビルドが想定外のソースを元に行われて完了する
  5. ビルドが期待通りのアウトプットを生成しているにもかかわらず、失敗と表示される
  6. ビルドは一応期待通りに完了するが、時間がかかり過ぎる
  7. ビルドが期待通りにエラーを出すのだが、そのメッセージが間違っているか、意味が伝わらない

同様に、クリーンアップが期待通りに動かない問題にも、いくつかパターンがある:

  1. クリーンアップで、ビルド出力以外のファイルが削除される
  2. クリーンアップが期待通りにファイルを削除しない
  3. クリーンアップで、期待されている以上にファイルが削除され、次のビルドに時間がかかりすぎる

「ビルドが成功していると言われているのに、期待しているアウトプットが無い」典型例は、ビルド中のあるステップで失敗しているのに、それに気付かずにビルドが続行してしまう場合だ。子プロセスを実行しているなら、その結果を終了コードなどで確認する必要がある。あるいは、コマンドは実行継続中なのに、呼び出し元には処理が戻っていて、その後の処理に依存しているのに、それに気付かずに続行しているのかもしれない。

「ビルドが途中でエラーを起こして終了する」場合や「ビルドが完了しない」場合というのは、ユーザーのカスタムビルドタスクがおかしい場合もあれば、そもそもビルドエンジン(ビルドツール)がおかしい場合もある。ビルドツールもプログラムに過ぎないのだから、そのような問題は当然発生しうるわけで、これらは理解しやすい問題と言える(デバッグの難易度は別論として)。

「ビルドが想定外のソースを元に行われる」というのは、たとえば次のような場合だ:

  • 一旦ソースコードに何かしらの加工を加えた上でコンパイラに解析させるビルドの場合(AOPなどを想定すると良い)、きちんと最新のユーザーソースをもとにAOP処理が行わなれないと、古いソースがコンパイラに渡されることになりかねない、といった例がある。TypeScriptがJavaScriptに正常にコンパイルされていないのに、最後にコンパイルされた時に生成されたJavaScriptファイルをそれ以降のビルドに使って、実行時エラーを起こす、なんてこともあり得る。
  • あるいは、「クリーンアップが期待通りにファイルを削除しない」問題と組み合わさることで、不正に残されたビルドキャッシュから「クリーンなはずの」ソースをビルドしようとして、一貫性のないアウトプットが生成されることがあり得る。

「ビルドが期待通りのアウトプットを生成しているにもかかわらず、失敗と表示される」という状態は、一番分かりやすい例では子プロセスの終了コードのチェックが反転している(0をエラー扱いする)などの場合で、ビルド出力がある以上実質的な問題はないという場合もあるが、続く処理があってもそこで終了してしまうかもしれないという問題がある。

「ビルドは一応期待通りに完了するが、時間がかかり過ぎる」というのは、ビルドそのものが妨げられているとは言えないが、例えば次のような場合に問題になる:

  • ビルドキャッシュをうまく構築できていない。(キャッシュの生成に失敗している、認識できていない、普通にビルドするよりキャッシュの構築のほうが時間がかかる、など)
  • 一回構築するのに長大な時間を要するビルドタスクなのに、クリーンするたびに再実行しなければならない。100MBのダウンロードが、毎回クリーンのたびに実行されるのはまずい。

「ビルドが期待通りにエラーを出すのだが、そのメッセージが間違っているか、意味が伝わらない」というのも、ビルドそのものが妨げられているわけではないが、問題が発生した時に原因の追及が困難になる。

クリーンアップに話を切り替えよう。

そもそも、クリーンアップタスクが難しくなる要因のひとつは、「雑にビルド出力ファイルや中間出力ファイルを消せない」という点にある。ユーザーがデバッグに必要なファイルをとりあえずbinディレクトリにコピーして置いておくのだけど、安易に消してほしくない、ということがあるかもしれない。まともなMSBuildカスタムビルドタスクがbinディレクトリをまるっと削除することが無いのは、そういう理由による。ここをガン無視すれば「クリーンアップで、ビルド出力以外のファイルが削除される」という問題は生じるものの、想定されていない再ビルド結果には、ならなくなる。

「クリーンアップが期待通りにファイルを削除しない」というのは、ビルドタスクの作業を追加して 中間ファイルが新しく増えたことを忘れている、というのがよくある要因だろう。また、そもそもビルドタスクの中で外部ツールを呼んでファイルを生成していたりすると、呼び出すタスクを実装している側も、その外部ツールの実行結果なんて把握していられないこともあり、そうなると一貫しない結果が生じる余地が大きくなる。

「クリーンアップで、期待されている以上にファイルが削除され、次のビルドに時間がかかりすぎる」というのは、たとえばクリーンビルド時にMavenやNuGetの依存ライブラリを毎回ダウンロードしてローカルにキャッシュする作業が毎回発生してはならない、ということである。完全に「クリーン」なビルドがきちんと実現できるのは一番安全ではあるが、毎回ダウンロードまで完了しなければクリーンビルドが出来ないというのは、やりすぎである。

この問題の亜種で、「クリーンアップで消してはならないファイルを消してしまって、それなのに間違ったビルド初期化キャッシュ情報が残っている結果、初回ビルドの時に行ったダウンロードと初期化の処理は行われず、結果的に以降のビルドが失敗する」という例もあると思われる。

…とまあ、いろいろな事情が考えられる。いずれにしろ、ビルドタスクが想定通りに動かない問題に遭遇したら、こんな感じで列挙した問題のいずれかにひっかかっていることもあると思うので、参考になることがあれば参考にされたい。

技術書典3向けXamarin同人誌の執筆者を募集します

あの技術書典が今秋にも行われることになりました。

techbookfest.org

そういうわけで、技術書典2の時と同様に、サークル参加してみようと思います。前回はおっかなびっくりいろいろ手探り状態で進めていたわけですが(それでもTechBoosterなどを見ていたのでいろいろやりやすかったはず)、今回は進めやすいかなと思っています。

募集といっても雑なものなので、前回の募集要項振り返りをもとに、書きたいという方がいらっしゃったら、以下のgithubリポジトリのissueで参加表明などしていただければと思います。

github.com

前回募集していた時はそこまで意識していなかったのですが、前回は結果的にXamarin関係の執筆者としてはかなりの強者が集まっていて、商業クオリティの本どころか現時点で最強クラスのXamarin関係書籍が2冊分も出来て実際に商業化の作業も進んでいるわけですが、わたしとしてはあくまで同人誌として「みんなが書きたいことを気軽に書ける」ことを目標にしたいと思っています。もちろん、ガチガチのクオリティで書いてほしくない、ということは無くて、最強の原稿を書き上げていただければ、可能な範囲で適宜アレンジします。

同人誌を出すサークルを回すための知見もいろいろ共有しているつもりなので、執筆しながらその辺も見ておきたいという人も気軽に参加してもらえればと思っています。

誰もいなければわたしが1人で適当に何か書いて出します(まあそんなことにはならないと思いますが)。

Android Architecture Components for XamarinのProof of Concept実装

表題のようなものをproof of conceptとして作成したので出しました。

www.nuget.org

実質2日程度の成果で、基本的にはまだ使い物になりませんが、Lifecycleの雰囲気を味わうことくらいはできます。あ、Roomのサポートは実装していません。ORMはJava方面でも他にいろいろあるので(いやRoomはORMじゃないけど)、その辺を眺めつつ、Roomは後回しでいいやと思っています。

Android Architecture Components(以下AAC)は、Google I/O 2017で公表された新しいライブラリで、いずれサポートライブラリにも統合されるはずのものです。ここではAACがどんなものであるかについての説明はしません(既にさまざまな 解説が 出ているので)。

ただ、単なるAndroidライブラリであればバインディングを用意するだけなので瞬殺なのですが、AACにはapt(annotation processor)に依存する部分があるので、そこを何とかして代替しなければいけませんでした。実際にわたしが作ったのは以下の2点です。

ライブラリのバインディング

基本的には普通のAndroid Bindingライブラリです。

ここでも書かれていますが、android.arch.lifecycle_runtime.aar中にAndroidManifest.xmlが含まれており、その中でカスタムproviderが定義されている仕組みです。これを参照しているアプリケーションではmanifest mergerが必要な要素を追加してくれます。(Xamarin.Androidには簡易manifest mergerが実装されています。)

java.lang.Enumを使っている部分については、C#でAttributeとして指定できないため、JavaEnumに対応するC#Enumを定義し、それに対応するimplicit operatorと、enumをパラメータとするAttributeのコンストラクタを定義すればOK…と思っていましたが、実際にはenumにすることは出来ませんでした。enumにすると、後でMSBuildタスクの中でcecilで値を取り出すときに、そのenum型がデスクトップ上でインスタンス化出来なければならないところ、モバイルプロファイル上でビルドしてしまったenumを復元することはできなかったためです。仕方ないのでenumはあきらめてintにしています。早いとこenumは滅ぼして@IntDefに相当するIntDefAttributeに移行したい(ま、Googleがまともなannotations.zipを提供していないので難しいですが)。

aptツールの代替手段

基本的には、NuGetパッケージを参照するかたちにしておけば、このライブラリを参照しているアプリケーション プロジェクトのビルド時に、NuGetパッケージに含まれるMSBuild targetsが自動的に追加されるので、BeforeTargets/AfterTargetsを利用してビルドに追加タスクを差し込むことが出来ます。Xamarin.FormsのXamlCタスクがコレで出来ているはず。

LifecycleAdapterの自動生成

Lifecycleについては、以下のようなコードに対して:

class TestObserver : Java.Lang.Object, ILifecycleObserver
{
    [OnLifecycleEvent (OnLifecycleEvent.OnAny)]
    public void OnAny (ILifecycleOwner owner, Lifecycle.Event evt)
    {
        Console.WriteLine ("OnAny invoked.");
    }
    [OnLifecycleEvent (OnLifecycleEvent.OnStop)]
    public void OnStopped ()
    {
        Console.WriteLine ("OnStopped invoked.");
    }
    [OnLifecycleEvent (OnLifecycleEvent.OnStart)]
    public void OnStarted ()
    {
        Console.WriteLine ("OnStarted invoked.");
    }
    [OnLifecycleEvent (OnLifecycleEvent.OnStart | OnLifecycleEvent.OnStop)]
    public void OnStartedStopped ()
    {
        Console.WriteLine ("OnStartedStopped invoked.");
    }
}

以下のようなコードが自動生成されてビルドされる必要があります:

class TestObserver_LifecycleAdapter : Java.Lang.Object, IGenericLifecycleObserver
{
    readonly TestObserver mReceiver;

    public TestObserver_LifecycleAdapter (TestObserver receiver)
    {
        this.mReceiver = receiver;
    }

    public Java.Lang.Object Receiver => mReceiver;

    public void OnStateChanged(ILifecycleOwner owner, Lifecycle.Event evt)
    {
        mReceiver.OnAny(owner, evt);

        if (evt == Lifecycle.Event.OnStart)
        {
            mReceiver.OnStarted();
        }
        if (evt == Lifecycle.Event.OnStart)
        {
            mReceiver.OnStartedStopped ();
        }
        if (evt == Lifecycle.Event.OnStop)
        {
            mReceiver.OnStartedStopped ();
        }
        if (evt == Lifecycle.Event.OnStop)
        {
            mReceiver.OnStopped ();
        }
    }
}

やっていることは、ILifecycleObserverを実装するクラスを全て洗い出して、そのLifecycleAdapterを定義して、その中のOnStateChangedの中で、関連属性の付いているものを呼び出すようにコードを生成するだけです(と、理解していますが、もしほかにもやるべきことがあったら教えてください)。

どこでLifecycleAdapterを自動生成すべきか

やり方はいくつか考えられます:

(1) コンパイル前にソースコードを自動生成し、まとめてビルドする

Roslynを使ってプロジェクトを解析するやり方。これはどの時点でタスクを挟み込めるのか見極めが必要になるし、セマンティック分析まで行ってからコード生成しないといけないので、それなりに重いです(ビルドを二重に行っているような状態になるので)。

(2) コンパイル後にバイナリを解析しソースを自動生成し、別途ビルドする

Csc後に出力アセンブリをロードして、バイナリを解析し、コードを生成した後、別のコードファイルを生成し、参照を追加した上で別のアセンブリコンパイルします。実装は比較的素直になりますが、コンパイルは(生成コードは小さいとはいえ)二度発生します(キャッシュしておけば回避できるでしょう)。また、アプリケーション上のinternalクラスもきちんとサポートするためにはInternalsVisibleToAttributeを追加してビルドすることになるでしょう。そして、GenerateJavaStubsタスクがこの後に実行される必要があるので、順序を気にしなければなりません。

(3) 実行時にコードをクラスを生成する方式。Reflection.EmitやExpression Treeやらを駆使して動的にこのクラスを生成するアプローチですが、これはAndroid Architecture Componentsと組み合わせることは出来ません。なぜなら、Architecture ComponentsのLifecycleRuntimeTrojanProviderはJavaのLifecycleObserverインターフェースの実装(か何か)を探索して、それをフレームワーク側から呼び出しているため、Java Callable Wrapperを生成する必要があるからです(これはXamarin.Androidのドキュメントで「出来ない」と明記されている制限事項)。またAOTビルドと相性が悪いのもあまり好ましくないでしょう。

以上のように考えて、結局(2)を採用したわけですが、Xamarin.Androidチームが愚劣な設計を行っていて、生成されるJavaコードのパッケージ名はmd5でmanglingされてしまう問題があって、これがあるせいで、別のアセンブリにあるクラスは同じネームスペースに含まれていてもJava上では違うパッケージになってしまいます。これはAndroid Architecture Componentsの実行時型処理を根本的に台無しにしてしまうし、Javaパッケージ名をC#から推測できるようにする類のライブラリにとって害悪でしか無いので、生まれる前に消し去る必要があります。いずれにせよ、現状ではC#で書いているすべての型について、[Register(javapackagename.ClassName)]を追加する必要があります。

ちなみに、なんでそんなmd5hashを使うような設計になっているかというと、Javaアプリと違って.NETでは1つのプログラム(エントリポイントから参照・実行される複数のアセンブリということにしておいて)で複数のアセンブリが同じ名前空間と同じ名前の型をもつことが許されるので、JCWでは識別性のある名前にしないとサポートできない…という理由によります。そんなのサポートしなければいいんですが。library package developersを救ってextensibility developersとend user developersに迷惑をかける仕様じゃ意味ないし。

特に最後の問題は、Xamarin.Androidのまともなユーザーであれば絶対に採用し得ないレベルのものなので、コレはproof of conceptです。(1)にシフトすれば解決する問題ですが、md5 manglingは滅ぶべきなので、(2)で通したいところです。パフォーマンスも全然違うだろうし。

あと、MSBuildのAfterTargetsなどで指定している対象が、(暗黙的に)パブリックなターゲットではないので(名前が_で始まっている)、Xamarinが内部実装を書き換えたら動かなくなる可能性があります。この辺はpublicなMSBuildプロパティとしてGenerateJavaStubsDependsOnとかXamarin.Androidの中に作ってしまえば良いことです(まあ開発者だから言えることですが)。

そんなわけでいろいろalpha qualityというよりproof of conceptですが、試したい方はどうぞ。

補遺: パッケージング

NuGetパッケージング プロジェクトはなぜかビルド時にNullReferenceExceptionを発生させていたのと、MSBuild targetsをうまく扱えなかったのとあって、今回は使いませんでした。