はてなブログでRust対応のアップデートをしてほしい!

お題「シンタックス・ハイライト機能で対応してほしい言語」

Rust のシンタックス・ハイライトは対応されているようですが、この日記を書いた時点だと、extern, match, where などがハイライトされていなくて、対応バージョンが古いような気がします。シンタックス・ハイライトのアップデートをお願いします!

例:

// echo.rs
extern crate getopts;

fn main() {
    let args: Vec<String> = std::env::args().skip(1).collect();

    let mut opts = getopts::Options::new();
    opts.optflag("n", "", "do not output the trailing newline");
    let matches = opts.parse(&args);

    match matches {
        Ok(m) => {
            show(&m.free);

            if !m.opt_present("n") {
                print!("\n");
            }
        }
        Err(_) => {
            show(args);
            print!("\n");
        }
    };
}

fn show<C: IntoIterator>(args: C)
    where C::Item: std::fmt::Display
{
    let mut i = 0;
    for arg in args {
        if i > 0 {
            print!(" ");
        }
        i += 1;
        print!("{}", arg);
    }
}

github.comのアカウントは仕事用と私用で分ける方がいいの?

追記(2019/04/11): sonots氏がGitHubの方と相談しつつ設計した運用方法が公開されました。

全社的に会社用GitHubアカウントを廃止した件 - ZOZO Technologies TECH BLOG

このガイドラインは今後のデファクトスタンダードになると思うのでtl;drを引用します。

  1. 個人で会社用と私用の2つの無料GitHubアカウントを持つことはGitHubの規約「非」準拠だった
  2. 会社用GitHubアカウントを廃止し私用GitHubアカウントを利用する規定に変更した
  3. セキュリティを担保するためGitHubのSSO機能を利用した

GitHubの規約的には、おそらく「会社として会社用アカウントを pro版にする」「個人が身バレを防ぐために個人アカウントをpro版にして会社用アカウントを別途作る」などの運用も可能だとは思います。しかし、GitHubというサービス自体マルチアカウントで使うような設計になっていないため、よほどのことでもないかぎりはこの「アカウントを分けない」というガイドラインでやっていくのが王道だということが示されたと思います。

以下もともとの本文は、記録のために そのまま 残しておきます(下記エントリにある「会社用アカウント」は、sonots氏のエントリによれば、(個別にpro版にしないかぎりは)フリーアカウントに該当するということが示されています)。


一行まとめ:分ける理由・分けない理由両方あるのでどちらにすべきということはない

会社で github.com (not GHE) を採用するにあたって、アカウントについてガイドラインを制定すべきなのかどうか悩んでいます*1。とりあえず現状把握のためにアンケートを取ってみました。

だいたい 1対2くらいで「分けている」と「分けてない」に別れる結果となりました。私のアカウントは「分けていない」し、周りでも分けている人をあまり見かけなかったので、分けないのが普通と思っていたのですが、意外と分けている人が多く「分けないのが普通」とはいえないなというのが第一印象です。

分けないメリットは個人にも会社にもあると思います:

  • アカウント所有者にとっては管理が単純
    • github.com はアカウント切り替えが簡単ではない(2FAを有効にしていると特に)のでなおさら
  • 「仕事でOSSにコミットする」というケースを個人の成果としても積み上げられる
  • 「プライベートでOSSにコミットする」というケースを会社のポジティブなイメージにも繋げられる

一方で、分ける方も言い分はあります。

  • 仕事とプライベートの活動を混在させたくないという思いはあって当然だと考えられる
  • 実際、家にいる時も会社のリポジトリの通知やアクティビティが見えてしまうのは嫌だという意見を聞いた
  • アカウントを分けないと実名とネット上のアイデンティティが紐付けられやすくなり、プライバシーの問題が起きうる
    • 会社のorganizationを管理する側としては、アカウント名は自由でよくても見えるところに本名を設定してほしいという願いがある
    • その結果、「会社としては、本名とハンドルネームをひも付けてほしい」ということになる(それをルールにするかどうかは別として)
    • 「身バレしたくない」という個人の願いと「管理しやすい状態であってほしい」という会社の願いが矛盾する
      • その結果として生まれるのが アカウント管理台帳 だとしたら哀しい世界だが…
  • 一般論として、クラウドサービスのアカウントは個人用と会社用で分けるべきという考えもある
    • たとえば、Google accountなどは分かれていてしかるべき(たとえGoogle Apps for Workを採用していないとしても)
    • ただしこの点では github.com は「会社としても個人としても分けないメリットがある」という性質をもつので一般論を適用する必要はないと思っています

無難なとところとしては、会社としてはルールは作らず、GitHubのアカウント管理方法を説明して「どちらでもよい」とするしかないのかなという気がします。

ここで上げていない分ける理由、分けない理由もあると思うので、もし思いついたらコメントなどいただけると幸いです。

*1:https://help.github.com/articles/github-terms-of-service/ によると、複数のfree accountは認められないとありますが、会社用アカウントはfree accountではないと考えられるのでここではアカウントを分けることは利用規約に準拠していると解釈して話をしています。

Android Instant Apps FAQをざっと眺めた

Google I/O 2016で発表されたアプリをインストールしなくても使える "Android Instant Apps" ですが、技術が不明ということで様々な憶測を呼んでいます。

developer.android.com

これについては、すでにFAQがあっていくつかの疑問には答えられていました。

https://developer.android.com/topic/instant-apps/faqs.html

まとめると

  • Android 4.1から使える
  • プロジェクトごとに成果物(APK)は通常版とinstant版の2つのビルド設定を持つ
    • "Developers simply configure the project to create two build artifacts: the installable APK and the instant version"
  • instant版は通常版と同じAndroid APIが使えるが、バックグラウンドサービスなどは利用できない
  • パーミッションモデルはruntime permissionsを使う
    • 「古いデバイス用の追加作業は不明」と書いてあるが…?
    • Google Play servicesが処理を担うからパーミッションはいらないのかな?
  • publishは通常版とおなじくGoogle Play Developer Consoleで行う

これを読んでもよくわかりませんが、専用のbuild variantでAPKを作ってpublishする感じに見えますね。

GlideのバックエンドをOkHttp3にしてカスタムインスタンスを与える

github.com

GlideのバックエンドをOkHttp3にするには、 okhttp3-integration を使います。

dependencies {
    compile 'com.github.bumptech.glide:okhttp3-integration:1.4.0@aar'
}

これだけで、GlideのバックエンドHTTPクライアントがOkHttp3になります。

しかし、カスタムOkHttp3インスタンスを与えるにはさらに一工夫必要で、具体的には、アプリケーション用のGlideModuleを実装してAndroidManifestで宣言する必要があります。

まず、GlideModuleを実装したクラスを定義します。

public class OkHttp3GlideModule implements GlideModule {

    OkHttpClient client = ...; // 何らかの方法でOkHttpClientのインスタンスを得る

    @Override
    public void applyOptions(Context context, GlideBuilder builder) {
    }

    @Override
    public void registerComponents(Context context, Glide glide) {
        Log.d("OkHttp3GlideModule", "registerComponents");

        // glideに登録する。
        // OkHttpUrlLoaderはokhttp3-integrationに定義されたクラス
        glide.register(GlideUrl.class, InputStream.class,
                new OkHttpUrlLoader.Factory(client));
    }
}

これをAndroidManifestで宣言します。

<application>
    <meta-data
        android:name="com.example.app.OkHttp3GlideModule"
        android:value="GlideModule"
        />
</application>

これでGlideでカスタムOkHttp3インスタンスが使われるようになります。

See Also

JRubyをライブラリとして使う(Android編)

三行まとめ

  • JRuby 1.7.24 (Ruby 1.9.3相当) はAndroidから普通に使える
  • JRuby 9.1.0.0 (Ruby 2.3相当) はOracle JDK固有のクラスを使っていてAndroidでは使えない
  • JRuby on Androidに興味のある開発チームはいるようなので、しばらくすると使えるようにはなるかもしれない

解説

Re:VIEW のソースを直接レンダリングするAndroidアプリがあったら面白いかなと思い、かといってRe:VIEWをJavaで再実装するのも面倒なのでまずJRubyをAndroidで動かせるかどうか試してみました。といっても、すでにJRubyを処理系として使うRubotoというAndroid用フレームワークがあるので、ある程度は使えることが分かっていました。

このJRubyを試しているリポジトリは https://github.com/gfx/HelloJRuby にあります。

依存関係

まず依存は次のように指定するだけです。ただし、JRubyは9000系と1.7系があり、相当するRubyのバージョンが違います。

// JRuby 9000 (Ruby 2.x相当)
dependencies {
   compile 'org.jruby:jruby:9.1.0.0'
}

// JRuby 1.7 (Ruby 1.9相当)
dependencies {
   compile 'org.jruby:jruby:1.7.24'
}

実行

JRuby wiki に簡単な使い方が書いてありるので、それにしたがってRubyスクリプトを評価するコードを書きます。JITを無効化するのを忘れないように。

from MainActivity.java:

Log.d(TAG, "Ruby starting");

// Rubyランタイムの設定
RubyInstanceConfig rubyInstanceConfig = new RubyInstanceConfig();
// JITはOFFにする
rubyInstanceConfig.setCompileMode(RubyInstanceConfig.CompileMode.OFF);
// Rubyラインタイムを生成する
Ruby runtime = Ruby.newInstance(rubyInstanceConfig);

Log.d(TAG, "Ruby instantiated (" + (System.currentTimeMillis() - t0) + "ms)");

// Rubyスクリプト。android.util.Log.d()を呼んだあと、BuildConfig.APPLICATION_IDを返す
String script = "require 'java'\n"+
        "Java::AndroidUtil::Log.d %Q{Ruby}, %Q{This is JRuby (Ruby #{RUBY_VERSION})}\n"+
        "Java::ComGithubGfxAndroidHellojruby::BuildConfig::APPLICATION_ID";

// Rubyスクリプトを実行する
IRubyObject result = runtime.evalScriptlet(script);

Log.d(TAG, "Ruby evaluated (" + (System.currentTimeMillis() - t0) + "ms)");

TextView textView = (TextView) findViewById(R.id.text);
assert textView != null;
// RubyオブジェクトのもつJavaオブジェクトを取り出す
textView.setText(result.toJava(String.class).toString());

さて、これをJRuby 9.1.0.0で動かすと、java.lang.invoke.SwitchPoint が存在しないという実行時例外で落ちてしまいました。これはAndrodには実装されていないようです。そこで一旦JRubyのバージョンを1.7.24に落としてみると、無事に実行できました。

Re:VIEWの最小サポートバージョンがRuby 2.0 なので Ruby 2.2に相当するJRuby 9.1.0.0を使いたいところです。現在は実行できない以上、当初の目的であったRe:VIEWの実行はできませんね。残念…。

Kotlin Coroutines の様子を眺める

先日、JetBrainsのブログでKotlinにコルーチンが導入されるという発表がありました。

以下のリポジトリで先行事例の調査や仕様の検討が行われています。

https://github.com/Kotlin/kotlin-coroutines

先行事例の調査は以下のissueです。

https://github.com/Kotlin/kotlin-coroutines/issues/2

Kotlinにおける仕様の草稿(と思われるもの)は以下の文書です。

https://github.com/Kotlin/kotlin-coroutines/blob/master/kotlin-coroutines-informal.md

examples/ にあるのは、coroutinesがどのように実装されるのかのスケッチにみえます。つまり、coroutinesを使ったコードがコンパイルされる結果の例で、標準ライブラリに含まれるであろうヘルパーコードの実装も含みます。

https://github.com/Kotlin/kotlin-coroutines/tree/master/examples

検討は始まったばかりであり、Kotlinへのコルーチンの実装はまだまだ先になりそうですが、このリポジトリをみるとコルーチンの振る舞いをある程度予想できるようになっているため、少し解説してみましょう。


さて、コルーチンは、あるメソッドを、ローカル変数などの状態を維持したまま中断・再開させる機能です。C# の yieldasync/await はコルーチンの一種です。最近はJavaScriptにも yieldasync/await が導入され、babelなどのトランスパイラで利用できます。

さて、ここでは yield (ジェネレータ)を見てみましょう。

Kotlinの場合、 yield は次のように使います。gen() の戻り値は [1, 2] という反復子を表す Iterator<Integer> となります。

fun gen() = generate {
    println("yield(1)")
    yield(1)
    println("yield(2)")
    yield(2)
    println("done")
}

次の f()gen() を使うメソッドです。

fun f() {
    val iter = gen() 
    iter.next(); // "yield(1)" を出力したあと 1 を返す
    iter.next(); // "yield(2)" を出力したあと 2 を返す
}

まず、gen() を呼び出すと即座にジェネレータを生成して呼び出し元の f() に返します。次の iter.next() により gen() の実行が再開して println("yield(1)") が呼ばれたあと、 yield(1) によってまた gen() の実行が中断され、 f() に戻り、 iter.next() の戻り値として 1 を得ます。

そして次の iter.next() によって再び gen() の実行が再開され、 yeild(2) によって一時的に中断して呼び出しもとにもどり、 iter.next()2 を返します。

iterIterable<T> なので、拡張for文で反復することもできると思われます。 Iterator<T> と異なり、関数内のコンテキストを維持したまま値を生成することができるため、可読性の高い Iterator<T> を定義できるといえます。


さてこの yeild ですが、JVMやDalvikで実行する以上はバイトコードレベルの特別な機能は期待できません。したがって、Kotlinそのものでも表現できるようなコード変換が行われるだけだと思われます。

この変換結果は、たとえば次のファイルにあります。コメント内が yield を使ったコード、その他の部分がヘルパー実装とコンパイル後の実装と思われます。日本語のコメントは筆者によります。

https://github.com/Kotlin/kotlin-coroutines/blob/dd07d35/examples/yield.kt

// ...

fun main(args: Array<String>) {
/*
    fun gen() = generate {
        println("yield(1)")
        yield(1)
        println("yield(2)")
        yield(2)
        println("done")
    }
*/
    // 上記コメントのコードが変換されるのが class __anonymous__ となる
    fun gen() = generate({__anonymous__()})

    // gen() を使うコード。戻り値は普通の `Iterator<T>` として使える
    println(gen().joinToString())

    val sequence = gen()
    println(sequence.zip(sequence).joinToString())
}

// LIBRARY CODE
// Note: this code is optimized for readability, the actual implementation would create fewer objects
// yieldの実装を担うライブラリコード(可読性のため多少いじってあり、本物のコードではない、と書いてある)

// ジェネレータ生成用メソッド
fun <T> generate(@coroutine c: () -> Coroutine<GeneratorController<T>>): Sequence<T> = object : Sequence<T> {
    override fun iterator(): Iterator<T> {
        val iterator = GeneratorController<T>()
        iterator.setNextStep(c().entryPoint(iterator))
        return iterator
    }
}

// ジェネレータヘルパの実体で、 `Iterator<T>` を実装している
class GeneratorController<T>() : AbstractIterator<T>() {
    private lateinit var nextStep: Continuation<Unit>

    override fun computeNext() {
        nextStep.resume(Unit)
    }

    fun setNextStep(step: Continuation<Unit>) {
        this.nextStep = step
    }


    @suspend fun yieldValue(value: T, c: Continuation<Unit>) {
        setNext(value)
        setNextStep(c)
    }

    @operator fun handleResult(result: Unit, c: Continuation<Nothing>) {
        done()
    }
}

// GENERATED CODE
// メソッド内でyeildを使うと次のようなクラスに変換される

class __anonymous__() : Coroutine<GeneratorController<Int>>, Continuation<Any?> {
    private lateinit var controller: GeneratorController<Int>

    override fun entryPoint(controller: GeneratorController<Int>): Continuation<Unit> {
        this.controller = controller
        return this as Continuation<Unit>
    }

    override fun resume(data: Any?) = doResume(data, null)
    override fun resumeWithException(exception: Throwable) = doResume(null, exception)

    /*
        generate {
            println("yield(1)")
            yield(1)
            println("yield(2)")
            yield(2)
            println("done")
        }
     */
    // gen() が中断・再開しているように見えるのは、
    // ジェネレータの実体がステートをもつオブジェクトであり
    // label (現在のメソッドの位置) により移動できるように振る舞うから
    private var label = 0
    private fun doResume(data: Any?, exception: Throwable?) {
        when (label) {
            0 -> {
                if (exception != null) throw exception
                data as Unit
                println("yield(1)")
                label = 1 // 次の「位置」を更新
                controller.yieldValue(1, this) // yield(1) に相当
                // このあとdoResume() は中断して、再開後はlabel=1を処理する
            }
            1 -> {
                if (exception != null) throw exception
                data as Unit
                println("yield(2)")
                label = 2
                controller.yieldValue(2, this) // yield(2) に相当
                // このあとdoResume() は中断し、再開後は label=2 を処理する
            }
            2 -> {
                if (exception != null) throw exception
                data as Unit
                println("done")
                label = -1 // ジェネレータの終了
                controller.handleResult(Unit, this)
            }
            else -> throw UnsupportedOperationException("Coroutine $this is in an invalid state")
        }
    }
}

このように、 yieldIterator<T> を実装する方法の一つですが、素のままだと非常に煩雑なコードをシンプルに書けることが期待できます。Kotlinに実装されるのが楽しみですね。

コルーチンについては C# に一日の長があります。以下の書籍は2012年のものですが、このようなコルーチンをつかったコードの変換についても触れており、参考になります。

ViewPagerを使った読書画面の基本的な実装

電子書籍関係者で勉強会をやったので資料を公開します*1

speakerdeck.com

追記: Fragmentの状態の復帰はFragment#onViewStateRestred(Bundle)) でできるのではという指摘をいただきました。試したところ想定通り動いたのでコードの方は修正済みです。

デモ実装は https://github.com/gfx/TinyPdfReader で、以下のようなことが実装されています。詳細は資料をご覧ください。

  • ピンチイン・ピンチアウトでのズーム(PhotoViewを使用)
  • 画面の左右タップでの移動
  • Landscape時の見開き(1画面2ページ)
  • 見開きでも表紙は1ページだけでセンタリングする
  • 画面回転時の読書位置の保存
  • ページロード時の非同期画像読み込み +ふわっと表示
  • 全画面表示(immersive mode) & 画面中央タップでトグル

その他、TinyPdfReaderでは有効にしていませんが、RVP: ReversibleViewPagerというライブラリを作ったのでその紹介もしています。

読書画面はいろいろ工夫しがいがあって面白いですね。

*1:勉強会自体は非公開ですが、資料は公開しても問題ないように作りました。

Android N preview-2 でJava8 Streamが実装された!

Android N preview-2 がきましたね。

Android N Developer Preview 2, out today! | Android Developers Blog

AOSPにはまだtagがきていませんが、masterブランチをみるとStream APIが実装されています。

追記: android-n-preview-2 tagがきてました android-n-preview-2 - platform/libcore - Git at Google

ojluni/src/main/java/java/util/stream - platform/libcore - Git at Google

もちろんOptionalもきてます。preview-1のときになかったのは、単に移植が間に合わなかったというだけみたいですね。

ojluni/src/main/java/java/util/Optional.java - platform/libcore - Git at Google

ただし、これらが使えるのは minSdkVersion=24 に設定したときだけです。とはいえいずれ使えるようになることが明らかになった以上、Lightweight-Stream-API などのbackport libraryなどで慣れておくのがいいでしょう!

新しいAndroid FrameworkのAPIもOptionalに対応しているといいのですが、そこまではまだ調べていません。とりいそぎ速報でした。

See Also

gfx.hatenablog.com

Google Maps Android APIでsetMyLocation()を正しく設定する

AndroidアプリでGoogle Mapをライブラリとして使うGoogle Maps Android APIというのがGoogle Play servicesにあるのですが、こいつの setMyLocation() まわりがここ1年でずいぶん変わりました。

Android 6.0 / Google Play services 8.4.0現在、これを正しく設定する方法を調べたので記録しておきます。

なおこれらのtipsは半径Nキロメートルという物件検討用メモアプリを作る際に調べたもので、このアプリにはもう適用済みです。

実装については、「半径N」では当初PermissionDispatcher を使おうとしたのですが、パーミッションチェックのコード量が減るわけではないので結局すべて自前で実装することにしました。


さて、まず問題は2つ。それぞれ見ていきます。

  1. GoogleMap#setMyLocationEnabled() のruntime permissions対応
  2. GoogleMap#getMyLocation()GoogleMap#setOnMyLocationChangeListener() がdeprecatedになった対応

GoogleMap#setMyLocationEnabled() のruntime permissions対応

GoogleMap#setMyLocationEnabled() はruntime permissionsを要求するようになりました。ACCESS_FINE_LOCATION または ACCESS_COARSE_LOCATION (以降は総称してlocation permissionsとします)が必要です。

なお GoogleMap自体はlocation permissionsがなくても動作する ので、まず本当にMyLocation(=デバイスの位置情報)が必要かどうかを検討してください。「半径N」の場合はMyLocationは必須ではないため、パーミッションの要求が拒否されても動作するようにしました。

これの対応の概要は以下のとおりです。

  • setMyLocationEnabled() を呼び出す前にパーミッションのチェックと requestPermissions() の呼び出しを行う
  • Activity#onRequestPermissionsResult()requestPermissions() の結果を受け取って、GRANTEDであればもう一度 setMyLocationEnabled() の呼び出しを行う

今回は shouldShowRequestPermissionRationale() は使いませんでした。

requestPermissions()

runtime permissionsのフローは Android 6.0 の Runtime Permissions (M Permissions) に対応するためのアクティビティ図 - visible true がよくまとまっています。

requestPermissions() は、パーミッションが必要なタイミングでコントローラが行います。パーミッションの必要なメソッドを呼ぶとIDEがlint errorを出し、IDEにしたがってコードテンプレートを生成すればだいたい合ってます。

今回は、以下のようなテンプレートが生成されます。

if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
        != PackageManager.PERMISSION_GRANTED
        && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION)
        != PackageManager.PERMISSION_GRANTED) {
    // TODO: Consider calling
    //    ActivityCompat#requestPermissions
    // here to request the missing permissions, and then overriding
    //   public void onRequestPermissionsResult(int requestCode, String[] permissions,
    //                                          int[] grantResults)
    // to handle the case where the user grants the permission. See the documentation
    // for ActivityCompat#requestPermissions for more details.
    return;
}
map.setMyLocationEnabled(true);

コメントに書いてあるとおり、 requestPermissions() を呼んで onRequestPermissionsResult() で権限付与を処理しろとありますね。これにしたがうと以下のようなコードになります。

static final int RC_LOCATION_PERMISSIONS = 0x01;

static final String[] PERMISSIONS = {
        Manifest.permission.ACCESS_FINE_LOCATION,
        Manifest.permission.ACCESS_COARSE_LOCATION
};

void setMyLocationEnabled() {
    if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
            != PackageManager.PERMISSION_GRANTED
            && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION)
            != PackageManager.PERMISSION_GRANTED) {
        ActivityCompat.requestPermissions(this, PERMISSIONS, RC_LOCATION_PERMISSIONS);
        return;
    }
    map.setMyLocationEnabled(true);
}

@TargetApi(Build.VERSION_CODES.M)
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    if (requestCode == RC_LOCATION_PERMISSIONS) {
        onRequestLocationPermissionsResult(permissions, grantResults);
    }
}

@DebugLog
void onRequestLocationPermissionsResult(String[] permissions, int[] grantResults) {
    int[] granted2 = {PackageManager.PERMISSION_GRANTED, PackageManager.PERMISSION_GRANTED};
    if (Arrays.equals(permissions, PERMISSIONS) && Arrays.equals(grantResults, granted2)) {
        // 権限を取得したのでもう一度setMyLocationEnabled()を呼び出す
        setMyLocationEnabled();
    } else {
        // 権限を取得できなかったので諦める
        Toast.makeText(this, "No location permissions granted", Toast.LENGTH_LONG).show();
    }
}

厄介なのは、パーミッションを要求するメソッドを呼び出すたびに checkSelfPermission() しなければならないことです。そうしないと、lint errorになります。したがって、パーミッションを要求するメソッドはすべて checkSelfPermission() を呼び出すコードでラップして、そのラッパーメソッドを呼び出すようにするのがいいでしょう。

GoogleMap#getMyLocation()GoogleMap#setOnMyLocationChangeListener() がdeprecatedになった対応

runtime permissionsと直接は関係ないと思いますが、GoogleMapでMyLocationを取得するメソッドが非奨励になりました。かわりにGoogleApiClientから取得しなければいけません*1。GoogleMapはたとえ setMyLocationEnabled(true) していても自動でMyLocationにカメラを移動したりはしないので、「カメラの初期位置をMyLocationにする」というだけのためにGoogleApiClientを使うことになります。

「半径N」の場合は位置情報まわりの操作をPlaceEngineというクラスにまとめているので少しごちゃごちゃしていますが、シンプルに実装すると以下のようになるでしょう。 getLastLocation() はlocation permissionsを要求するので、そのハンドルがまた必要です。「半径N」の場合はマップのカメラの初期位置を設定するだけなので、情報がとれなければそのまま諦めています*2

 void initiGoogleAPiClient() {
    googleApiClient = new GoogleApiClient.Builder(context)
            .addApi(LocationServices.API)
            .addConnectionCallbacks(new GoogleApiClient.ConnectionCallbacks() {
                @Override
                public void onConnected(@Nullable Bundle bundle) {
                    handleLastLocation();
                }

                @Override
                public void onConnectionSuspended(int i) {
                    Timber.i("GoogleApiClient connection suspended");
                }
            })
            .addOnConnectionFailedListener(new GoogleApiClient.OnConnectionFailedListener() {
                @Override
                public void onConnectionFailed(@NonNull ConnectionResult connectionResult) {
                    Timber.w("GoogleApiClient connection failed: %s", connectionResult.getErrorMessage());
                }
            })
            .build();
}

private void handleLastLocation() {
    if (ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION)
            != PackageManager.PERMISSION_GRANTED
            && ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION)
            != PackageManager.PERMISSION_GRANTED) {
        return;
    }

    Location currentLocation = LocationServices.FusedLocationApi.getLastLocation(googleApiClient);
    if (currentLocation != null) {
        // コントローラがLatLngを受け取ってくれることを期待してイベントを投げる
        castMyLocation(new LatLng(currentLocation.getLatitude(), currentLocation.getLongitude()), true);
    }
}

さらなる工夫

ざっくり必要最低限の実装をしましたが、さらなる工夫もできます。

まず実サービスの場合、 shouldShowRequestPermissionRationale() を使ってパーミッションが必要な理由を説明すべきです。

また、location permissionsの場合、高精度位置情報(ACCESS_FINE_LOCATION)と低精度位置情報(ACCESS_COARSE_LOCATION)の いずれか が必要なのですが、パーミッション取得の説明画面で高精度位置情報を落とすオプションを選ばせることも、技術的には可能だと思います。実装は大変ですが、サービスの性質次第では必要かもしれません。

*1:Android Frameworkのandroid.locationtというのも存在しますが、奨励されません。参考: Making Your App Location-Aware | Android Developers

*2:実際には、SIMやlocaleなどから位置情報を取得しようとしますが、正確な情報はとれないので諦めているも同然です。

Hugoの @DebugLog アノテーションが @TargetApi を無効化するようにみえる件

Hugo便利ですね!ただtraceするだけのログを吐いたりメソッドの実行時間を測定するためにコードを書かなくてよくなるのでだいぶ使い勝手がいいデバッグ用ライブラリだと思います。

さて、Hugoを併用しているプロジェクトで@TargetApi をつけているのにlintがNewApiエラーを報告するという問題がありました。どうしたものかと思ったんですが、これはHugoの@DebugLogを同時につけているせいでした。Hugoがbytecode weavingでメソッドのバイトコードをいじり、その結果lintからみると@TargetApiなしで新しいAPIを呼んでいるようにみえるようです。@DebugLog を消せばlintエラーは消えます。