AndroidにおけるJava8のサポート状況 2017年版

公式ドキュメントにありました。一言でまとめると、Android Oのpreviewが出た現在においても「Android N (API version 24)と同水準」となっています。

Use Java 8 language features | Android Studio

Android Studio 2.4 preview 4 (およびそれが要求するツールチェイン)の段階では、 desugar と呼ばれるツール(実体はAndroid Gradle PluginのTransform APIによるbytecode weaving tool)によって、一度javacでコンパイルしたバイトコードのJava8の言語機能(lambda, repating annotationsなど)をJava6水準のバイトコードに変換し、それをdxコマンドでdexにコンパイルするというプロセスを経るようです。このdesugarされたバイトコードは $subproject/build/intermediates/transforms/desugar/* にクラスファイルとして残されるので、どのようにバイトコードが変更されたかは確認できます。

この変換されたバイトコードをみるかぎり、Android O相当のAndroid SDKによってサポートされた「Java8の言語機能」はretrolambdaとほぼ同じです。

件のドキュメントの表にもあるとおり、 “Java8 Language API” つまりJava8の標準ライブラリ(stream, optional, etc.)を使うには依然としてAPI level 24が必要で、状況としては一年前のAndorid Nの時点とほぼ変わりません。

つまり、Android Oのpreviewが出ている現在でも、Java8に関して去年からの唯一のアップデートは、 retrolambda相当のツールがAndroid SDKに同梱されるようになった というだけということになります *1

Android Oで java.lang.invoke パッケージが追加されていたので、minSdkVersion = O ならもしかしたらinvokedynamicを使った本物のlambdaを使えるのではないかと思ったのですが、いまのところそのようにコンパイルされることは確認できませんでした。つまり、minSdkVersionの値に関わらず、desugarが必ず入るようです。このあたりはもしかしたら正規版までに変更があるかもしれませんが。

*1:去年の時点でβだったJackは正規版になるまえにdeprecatedになったのでノーカンで。

React Reduxファーストインプレッション

今更感ありますがReact Reduxを導入したの所感をメモしておきます。

github.com

  • ざっとみてこれなら自分でも再実装できそうだなという印象
  • いままではreact-micro-container でfluxしてた
  • React Reduxにすると、個々のreact componentをfluxフレームワークに依存しない形で設計できる
    • これに対して react-micro-container はcontainerに制御されることを意識した設計になる(=containerとcomponentで設計が異なる)
    • React ReduxなしでReactを初めてRect Reduxを導入するのは簡単だし、あとから別のflux実装にするのも簡単にできそう
    • アプリケーションに小さく導入することもそこまで難しくない
  • 個々のcomponentはシンプルに設計できていいが、Reduxとの繋ぎ込みでReact Reduxが魔法のように処理するところがやや多く、かつ繋ぎこみ用のマッピングが冗長な印象はある
  • 型がない素のJSだと繋ぎこみ部分がかなり不安。flowtypeなりTypeScriptなりは導入しないといずれ破綻しそう
  • ディレクトリ構成に悩む。まだ考え中

『Androidを支える技術』(I, II) の内容に興奮した

  • 『Androidを支える技術 I』 ~ 60fpsを達成するモダンなGUIシステム ~
  • 『Androidを支える技術 II』 ~ 真のマルチタスクに挑んだモバイルOSの心臓部 ~

これらを著者の有野さん よりご恵贈いただきました。ありがとうございます。

始めて知る内容も多かったのですが、既に知っていることでも著者の意見が反映されているのを読むと、いくつものモバイルOSを見てきたハッカーからみるとこう見えるのか!という新鮮な面白さがありました。

IとIIのテーマは独立しているので、どちらから読んでもいいと思います。

以下個人的に面白かった章をピックアップします。

I の見どころ

  • §1: ActivityThread.java にあるAndroidアプリのエントリポイント public static void main(String[] args) の役割
    • ActivityTheadはデバッグしてるとたまに見ることはあるものの全然役割を知らず、それどころかここに main() があるのすら知りませんでした
  • §3: Thread, Handler, Looperあたりの話
    • HandlerとLooperまわりは難解で、なんとなく表面的に知っているだけにとどまっていました。そのあたりを詳しく解説しているのは大変ありがたい…
  • §7: バイトコード実行系の話
    • Android 7.0でARTからVM + on-demand compileに戻ったのは初めて知った!たしかにART時代のAOT compileの遅さは気になっていたので、AOSPチームのその判断自体は納得できます

IIの見どころ

  • §3: OOM Killerとの戦いは涙なしには読めませんね…
  • §4: IIで一番驚いたはなんといってもinstance stateの保存(Bundle)に関して、「BundleがSystemServerのメモリ上に保持されている」という点です。そんな事がありえるのかと思うのですが、調べたかぎりだとそうとしか思えないとのこと
  • 本作はコラムが本体かってくらいの力作で、特にモバイルOSの歴史編(勝手に命名)はAndroid, iOS以前のモバイルOSの歴史を全然知らない私にとっては黎明期の壮大な物語です。こういうのが読みたかった。

なお『Androidを支える技術』レビュアーでありGoogleエンジニアでもあるomoさんのエッセイもすばらしいです。

死んでしまったOSたちへ – To Phantasien

MathJaxによる描画を細かく制御する

MathJax というのはLaTeXなどで書いた数式を美しく描画するための処理系です。 なおこのエントリは MathJax v2.7.0 を対象にしています。

www.mathjax.org

まず前提として、ほとんどのケースではMathJaxをconfigパラメータを与えてロードするだけで自動的にtypeset *1 が行われます。

この自動typesetは、HTML中のすべての要素に対してただ一度だけ行われるので、content editableでウェブエディタを実装しているケースやリアルタイムプレビューとの相性が非常に悪いのです。つまりcontent editable中のLaTeX記法は置き換えるべきではないのに置き換えてしまうし、プレビューが更新されてもtypesetされないのでこちらはLaTeX記法が処理されません。

そこで細かくMathJaxを制御する必要があるのですが、これがなかなか苦労したので、ハマったことや考えたことをここにメモしておきます。MathJaxは奥が深すぎて同じことをするのに何通りもの方法があり、ここに書いたものが唯一の方法ではありません。実際に実装するときは最新のドキュメントを参照することをおすすめします。

APIリファレンス

リファレンスはこちら。 ぐぐるとよく過去のバージョンのものが出てくることが多いです。何かを探すときは最新版をサイト内検索するのがいいでしょう。

http://docs.mathjax.org/en/latest/api/

SSRかCSRか

MathJax-node があるので、SSR: Server Side Renderingもできるようです。

MathJaxの描画処理はかなり時間がかかり、またpreviewとして仮描画したものを出したりするため、数式の描画を表示するときにかなりガクガクします。

SSRするとこのガクガクがおそらくなくなるので、可能であればSSRしたいところです。しかしそのためには専用のnodejsサーバを立てなければならず、ほかの(たとえばReact SSR)レンダリングサーバに相乗りできるならいいですが、そうではなくMathJaxのためだけに新規にnodejsサーバを立てるのであれば大抵の場合オーバースペックでしょう。

今回はCSR: Client Side Renderingにしました。

MathJaxのロード

MathJaxパッケージはnpmにもありますが、それをwebpackで固めるとextensionのロードが404になるという現象になり、結局解決できませんでした。

なのでcdnjsのようなpublic CDNを使うか、MathJax配布キットのとおりの構造でデプロイするかしないといけないようです。

MathJax 3.0でwebpack対応予定 ということなので、webpackに組み込みたいときは3.0を待つのがいいようです。

自動typesetを無効にする

configに skipStartupTypeset: true を渡します。

MathJax.Hub を設定するためのドキュメントはこちら:

http://docs.mathjax.org/en/latest/options/hub.html

MathJaxの初期化終了コールバック

MathJaxは非同期でコンポーネントをロードするので、 MathJax オブジェクトが存在するからといって使用可能とは限りません。

MathJax.isReady は一つの指標としては使えますが、初期化の終了イベントをフックしたいところです。

初期化シーケンスは Startup Sequence にあり、それぞれのタイイングでコールバックを呼ぶのでイベントリスナを登録すればよさそうです。

このシーケンスの詳細は省きますが、End が初期終了時に呼ばれるイベントです。

MathJax.Hub.Register.StartupHook('End', () => {
  // MathJax APIがすべて利用可能になった!
});

MathJax Typeset API

MathJax.Hub.Typeset(target, callback) を使うと、target elementを指定してtypesetを起動できます。

target は HTML element またはその配列、あるいは null で、 nullの場合は document.body 全体が処理の対象になります。

MathJaxは処理をシーケンシャルにするために MathJax.Callback.Queue というjob queueを持っているので、typesetの処理は実際にはこのようになります *2

MathJax.Callback.Queue(() => {
  MathJax.Hub.Typeset(elements, () => {
    console.log(`typeset is done for ${elements.length} elements.`);
  });
});

エラーハンドリング

プロダクション環境ではエラーがあってもサービス提供者としては何もでないことのほうが多いでしょうが、開発中はエラーの詳細を知りたいというケースでは “Math Processing Error” イベントをフックすることでコンソールにエラーの詳細を出力できます。

MathJax.Hub.Register.MessageHook("Math Processing Error", (message) => {
  console.error(message);
});

MathJax Preview

LaTeXをちゃんとレンダリングするのはかなり時間がかかるようで、デフォルトだと本描画のまえに数回プレビュー描画が入ります。

このpreviewは様々なレイヤでやっているらしく制御の仕方がはっきりしないのですが、 MathJax_Preview CSS classが指定されているので、これに対してスタイルを当てると見た目はそれなりに制御できます。

全体像

というわけで全体像です。実際に運用しているコードそのものではありませんが、だいたいこんな感じです。 requestTypeset() を呼ぶと (1) MathJaxがロードされていなければロードする (2) MathJaxが使えるようになるまで待ってtypesetを起動する (3) requestTypset() は呼ぶたびにtypesetを起動する、ということを行うコードです。

const CONFIG = 'TeX-AMS-MML_HTMLorMML'; // 適当なconfig

const DEV = (process.env.NODE_ENV != 'production');

let isLoading = false;

function loadMathJax(onComplete) {
  const script = document.createElement('script');
  // from https://cdnjs.com/libraries/mathjax
  script.src = `https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.0/MathJax.js?config=${CONFIG}`;
  script.async = true;
  script.onload = () => {
    const MathJax = window.MathJax;
    console.log(`loaded MathJax/${MathJax.version} (${CONFIG})`);

    MathJax.Hub.Config({
      messageStyle: DEV ? "normal" : "none", // 左下に出るメッセージボックスは開発時のみ有効にする
      skipStartupTypeset: true,
      tex2jax: {
        inlineMath: [
          ['$','$'],
        ],
        displayMath: [
          ['$$', '$$'],
        ],
        processEscapes: true,
      },
    });
    MathJax.Hub.Register.MessageHook("Math Processing Error", (message) => {
      console.error(message);
    });
    MathJax.Hub.Register.StartupHook('End', onComplete);
  };
  document.head.appendChild(script);
}

export function triggerTypeset() {
  const elements = [...document.querySelectorAll('.content')];

  const MathJax = window.MathJax;
  console.assert(MathJax && MathJax.isReady);

  MathJax.Callback.Queue(() => {
    MathJax.Hub.Typeset(elements, () => {
      console.log(`typeset is done for ${elements.length} elements.`);
    });
  });
}

export function requestTypeset() {
  if (!document.querySelector('.content')) {
    return;
  }

  if (window.MathJax && window.MathJax.isReady) {
    triggerTypeset();
  } else {
    if (isLoading) {
      return;
    }
    isLoading = true;

    loadMathJax(() => {
      triggerTypeset();
    });
  }
}

*1:MathJaxの描画処理のことで、原義は「活字を組む」という意味です。MathJaxでこの用語を使うのはTeXに由来すると思われます。

*2:queueを使う必要はもしかしたらないのかもしれませんが、MathJaxは内部で様々なイベントが飛び交う複雑な非同期システムなのでこのようにするのが無難でしょう。

yarnpkgのdependenciesにgithubのリポジトリを直接書こうとしてハマった

追記:(2017/04/14): "foo" : "gfx/foo"$github_user/$repository)というフォーマットでうまくいきました。ε-(´∀`*)ホッ


追記(2017/04/13): 結局解決できてませんでした。つらい。

fooというnpm moduleにパッチを当てた gfx/foo という版をどうしてもいま使いたい、というときに、package.jsonにはこんな感じにURLを直接書けるはずということになっているのですが

{
  "dependencies": {
    "foo": "git+https://github.com/gfx/foo#branch",
  }
}

これがどうもCircleCI上のyarnpkgだと刺さってしまって動きませんでした。yarnpkgのアップグレードなどでも解決せず。結局以下のように .git suffixを付けると解決できました。

{
  "dependencies": {
    "foo": "git+https://github.com/gfx/foo.git#branch",
  }
}

CommonMarkの現状 2017年4月版

CommonMarkというのはMarkdownの標準化を目指して2014年に立ち上がったプロジェクトです。

当時のニュース:

Standard Markdown Becomes Common Markdown then CommonMark (日本語版)

公式サイト:

http://commonmark.org/

あれから3年、とうとうGitHubがCommonMarkを採用したというアナウンスがありました。つまり、長らくMarkdownデファクトと考えられてきたGFM: GitHub Flavored Markdownは、いまやCommonMarkのスーパーセットなのです。

A formal spec for GitHub Flavored Markdown | GitHub Engineering (2017/03/14)

To ensure that the rendered Markdown in our website is fully compliant with the CommonMark spec, the new backend implementation for GFM parsing on GitHub is based on cmark,

実際には、このエントリにあるように、GitHubはオリジナルの参照実装であるcmarkをforkしたもの(cmark-gfm)を使っています。これは、GFMのいくつかの拡張について仕様が定まっていないためで、それらの議論が終わったらオリジナルのcmarkにマージするつもりだとのこと。GitHubはCommonMarkのコアメンバーなので、このあたりについては心配はいらなそうです。

https://github.com/github/cmark

拡張: https://github.com/github/cmark/tree/master/extensions

Ruby gemの CommonMarker も、実際に参照しているのは github/cmark で、いまはGitHub社のkivikakk氏がcommonmarkerとgithub/cmark両方をメンテしているようですね。

このGitHubのアナウンスをうけて、国内外のMarkdown採用サービスが CommonMark (あるいはその拡張のGFM)に移行してゆくと考えられます。

Kibela的にはCommonMarkの拡張構文への関心が高いところです。すでにPlantUMLとLaTeXという二種類の記法を描画するためにMarkdown構文を拡張しているからです。

blog.kibe.la

blog.kibe.la

CommonMarkの仕様や拡張については talk.commonmark.org というフォーラムがあるので、CommonMarkの将来についてはこのあたりを眺めるとよさそうですね。

https://talk.commonmark.org/

JavaScript SSRのためのレンダリングサーバがほしい

いまReactのSSRのためにHypernova を使っていて、これはこれでちゃんと動くんですがいくつか不満があります。

  • Unix Domain Socketをサポートしていない
  • アプリケーションサーバ(express)の設定をほとんど変えられない
    • worker数とか
    • bodyの圧縮にzstdを使うとか

さらに、JSでレンダリングしたいコンテンツは他にもあって、たとえば

  • MathJax
  • Markdown
    • CommonMarkだとC実装とJS実装があるのでめちゃくちゃ頑張らなくても同じようなものを作ることはできるが…

なのでさしあたってこの三種類のコンテンツをレンダリングするためのNodeJSサーバがあるといいなと。

ここまでくるとNodeJSでアプリケーションサーバを書いたほうがいい気もしますが、まあそこは既存の資産があるのでステートレスなレンダリングだけでもまずはNodeJSでやれればなあと思いました。

というか、こうなるとHTTPサーバである必要もなさそう。

<meta name="robots" content="noindex"/> でも検索エンジンにインデックスされるケースがある

noindex tagがあるにも関わらずインデックスされているケースを見かけたので調べてみたところ、googlebotに関してはどうやらそういう仕様があるようです。 noindex tag は常にインデックスの削除を行うのだと思っていたのでちょっと驚きました。

メタタグを使用して検索インデックス登録をブロックする - Search Console ヘルプ

重要: noindex メタタグを有効にするには、robots.txt ファイルでページをブロックしないでください。ページが robots.txt ファイルでブロックされると、クローラは noindex タグを認識しません。そのため、たとえば他のページからリンクされていると、ページは検索結果に引き続き表示される可能性があります。

Block search indexing with meta tags - Search Console Help (同ページ英語版)

Important! For the noindex meta tag to be effective, the page must not be blocked by a robots.txt file. If the page is blocked by a robots.txt file, the crawler will never see the noindex tag, and the page can still appear in search results, for example if other pages link to it.

ちょっとこの説明だけだとよくわかりませんが、 noindex を指定していても検索エンジンにインデックスされるケースがあるようです。

googlebotが「noindex tagをみるとそのページをインデックスしない、既にインデックス済みの場合はインデックスを消す」というロジックだと仮定すると、たとえば次のようなケースではインデックスされるのかもしれません。

  1. noindex tagを指定しないままページを公開する
  2. noindex tagの指定とrobots.txtによる該当ページのブロックを 同時に 行う
  3. googlebotは該当ページを見に行かないので、「noindex tagをみてインデックスを削除する」という処理を行わない

これが正しいのだとすると、このgooglebotの注意書きのとおり、 robots.txt で該当ページのブロックを外せばインデックスが削除されるはずですね。goolebotがrobots.txt でブロックしているのを見つけたら、ブロックされているURLですでにあるインデックスをすべて削除すればいいような気もしますが、そうはなっていないようです。

ただ上記のgooglebotの注意書きの「たとえば他のページからリンクされていると、ページは検索結果に引き続き表示される可能性があります」はよくわかりません。最初からnoindex tagとrobots.txtによるブロックしていて、かつ他のページからリンクされているときもインデックスされるかのように読めますが、この解釈が正しいのかどうかはわかりません。

Ormaに関する雑談&質問用にgitterのchat roomをつくりました

en: https://gitter.im/Android-Orma/Lobby ja: https://gitter.im/Android-Orma/Ja

背景

GitLab、Slackライクなサービス「Gitter」を買収。有償プラン廃止で何人でも無料で利用可能に。オープンソース化も約束 - Publickey でひさびさにgitterを知って、悪くないんじゃないかと思って設置してみました。

OSSコミュニティの母体としてSlackをつかうことが増えてるようですが、gitterだとpublic channelであれば招待されなくても入れるし、コミュニティとしてはそのほうがいいですよね。

正直チャットサービスとしてのクオリティはSlackのほうが高いです。gitterはリアクションとかできないし、モバイルアプリもGitHubのパスワードをアプリ内ブラウザで入力しないといけないので使えないし。しかしライブラリやフレームワークごとにslackチャンネルに入るのも大変面倒なので、それよりはgitterのほうがよさそうだなと思っています。

カンファレンススタッフ用アプリがあるとよさそう

DroidKaigi 2017お疲れさまでした。スタッフとしては3回目、スピーカーとしては初めての参加でした。

スタッフとしては、受付の誘導、司会、副司会、会場の片付けなどをしました。特に司会はわりと臨機応変に行動しなければならず、部屋ごとにけっこうバラバラだったのではないかなあと思います。

カンファレンスが始まってからも次々と様々な仕様変更がきて、「意識を高くもって気をつける」だけだとカバーしきれないところがあります。そういう点をカバーできるようにカンファレンススタッフ用アプリがあるといいなーと思って記憶が鮮明なうちにメモしておきます。もちろん関係者でのKTPは別途しますが、それは公開されないと思うので。

実際に起きたこと

  • スタッフのシフト表の変更
    • 頻繁におきてた印象、というのもいくつか不整合がみつかるので
    • シフト表にいれるスタッフは40人くらいいるので、不整合なく保つのは至難の業
  • セッションのTimetableの変更
    • やむを得ない事情によりキャンセルになった枠に当日の判断でFireside Chatをいれることになった
    • 決定があった時点でアナウンスしたけど館内放送があるわけではないので全員に周知できたかどうかは不明
  • 司会用チートシートがどんどん増える問題
    • 部屋 x 時間帯(ランチ前・一日目終了後・二日目終了後)などで微妙に言うことが違うのもなかなか大変
  • 適切な人に適切なタイミングで情報をpush通知する必要性
    • インカム(トランシーバ)を多用していたけど、インカムから情報を聞き取るのはかなり集中しないといけない
    • インカム議事録係がいたけど大変そうだった
  • 司会から発表者への残り時間の通知を適切にするためのツールが乏しく意識だけでやってる、ゆえにときどきミスる

こういうアプリがほしい

  • 1秒で起動して「いま自分が何をすべきか」を知りたい(チートシート)
  • チートシートに差分があればわかりやすく表示してほしい
  • 司会の場合、発表開始5分前、開始時、発表終了10分前、5分前、終了時に通知がほしい
  • スタッフ(あるいはスタッフグループごと)へ情報をpushできるようにする
    • 重要な知らせは強いpush通知(5分くらいバイブレーションが続くとか)があってもいいかもしれない