読者です 読者をやめる 読者になる 読者になる

Meblog

このブログ記事は個人の見解であり、所属する組織の公式見解ではありません

Google Map api v2 Androidのutility libraryでClusterを用いたときに、アイコンをカスタマイズする

開発 Android

目標

Google Maps Android API utility libraryを用いてClusterを使ったのはいいものの、Clusterから外れたMarkerに対し、画像を用いたアイコンを適用する方法がわからなかった。本稿では、その方法を調査したので、紹介します。

緒言

GoogleMap api v2を最近職場で使う用事があった。
Markerをカスタムし、オリジナルのアイコンを1000個ほど落とした時、やたらGCが発生するようになってしまった。

GC_FOR_ALLOC freed ***

アイコン作成時にBitmapまわりでメモリ管理を誤ったかと思ったが、なんだかそうでもないらしい。

問題らしきもの

ここを参照すると、単にピンを落としただけでGCが走るような現象が書いてある。
android google maps v2 lag when markers are being drawn/updated - Stack Overflow

タイトル:Android google map v2における更新時のラグ
マーカ(ピンのこと)の正しい管理方法は何ですか?
スクロールやズーム時に見えているところだけ表示しようとしているのですが、うまくいきません。

Clusterを使っているのはいいけど、APIをコールしすぎだね。
君のソースだと、みたところ25MBほどメモリを食っているね。
GoogleMapv2では全てのAPI呼び出しでプロセス間通信を行うからGCが走るんだろうね。
解決法?メモリ消費を十分に抑えるってのがヒントになるんじゃないかな。

うーむ、本当にプロセス間通信を使っているかは眉唾ものだし、メモリ消費を減らせ、といわれてもなあ。

ところで、Clusterってなんのことを言っているんだろう??

Cluster

ピンを落としただけでGCが走るようじゃ使い物にならないが、そうはいってもしかたがない。
ここでClusterを調査してみた。

Clusterってのは、ピンが数個あつまったところをまとめて表示してくれる代物らしい。ピンを描画するのに凄く時間がかかっているみたいなので、Clusterを用いて描画対象を減らせばなんとかなるんじゃないかと思う。目的は、Markerを大量に落とすことではなく、表示方法にはこだわらず落ちないアプリにすることである。
なので、特にこだわりなく、Clusterを使ってみる。

Google Maps Android API utility library

ここで、Google MapのAPIを拡張してくれるライブラリを導入する。
Google Maps Android API utility library

ライブラリの導入ってのは何だか面倒なイメージがあるが、AndroidStudioを使っていればGradleに次を追加し、Syncすれば一発で導入できる。
module配下にあるbuild.gradleに書き込む。そしたら依存関係は数秒で解消だ。

dependencies {
    compile 'com.google.maps.android:android-maps-utils:0.3+'
}

いやー、便利だ。

動かし方

サンプルがdemo配下のClusteringDemoActivity.javaにある。
googlemaps/android-maps-utils · GitHub

また簡単なインストラクションが次にある。
Google Maps Android Marker Clustering Utility - Google Maps Android API v2 — Google Developers

でも不満点が..

サンプルをビルドするとわかるけど、Cluster自体を実装するのは割りと簡単にできる。

でもこれだとMarkerがデフォルトのデザインのままで変更できない。Clusterから外れた個別のMarkerがデフォルトのピンの見た目なのだ。
Clusterの方はインストラクションの方にカスタム方法が書いてあるが、Marker側に関しては言及がない。
さて、どうしたものか。。

ClusterRendererを継承したアイコンカスタマイズ

いろいろ調査し、独自にコードを書いてみた。
以下抜粋である。

/**
 * 各地点をClusterとして管理するインナークラス
 */
public class MyItem implements ClusterItem {
    private final LatLng mPosition;
    private final Bitmap mBitmap;

    public MyItem(double lat, double lng, Bitmap bitmap) {
        mPosition = new LatLng(lat, lng);
        mBitmap = bitmap;
    }

    @Override
    public LatLng getPosition() {
        return mPosition;
    }

    public String getBitmap() {
        return mBitmap;
    }
}

/**
 * レンダラのカスタムクラス
 */
public class OwnIconRendered extends DefaultClusterRenderer<MyItem>{
    public OwnIconRendered(Context context, GoogleMap map, ClusterManager<MyItem> clusterManager) {
        super(context, map, clusterManager);
    }

    @Override
    protected void onBeforeClusterItemRendered(MyItem item,
                                               MarkerOptions markerOptions) {
        // ここで更新をかける。
        // 引数としてitemが与えられるので、これの画像を変更する
        Bitmap bitmap = item.getBitmap();
        if(bitmap != null) {
            markerOptions.icon(BitmapDescriptorFactory.fromBitmap(bitmap));
        }
    }
}

単にレンダラのメソッドをオーバライドし、itemに対してbitmapを変更しているだけである。
都合により、スクリーンショットが載せられないが、これでCluster外のMarkerに対して、画像によるピン画像をはりつけることができた。

何故かこの方法がGoogleでサーチをかけても出てこず、苦労した。

ちなみに

Clusterをそのまま用いると、ズームをしないと更新されないことがある。
レンダラをオーバライドしたせいかなと思ったが、違うようだ。。
これはClusterManager#cluster()を用いると更新することができる。