現在、最大64タイルをメモリ上にキャッシュしている。できるだけメモリの獲得、解放をやめて再利用する。 地図アプリ起動時に全タイル分のビットマップメモリを獲得して置き、必要に応じて、中身だけを書き換える。
タイルの Status は、free(空き、未使用)か、wait(レンダリングやダウンロードなどをリクエスト中)、 busy(レンダリングやダウンロードなどを実行中)、ready(ビットマップが利用可能)のいずれかである。
enum Status { free, waiting, busy, ready }
タイルは Tile.Source src, int zoom, int x, int y で識別できるが、 次のように、全体を一つの 8バイト整数で表す。
long key(Tile.Source src, int zoom, int x, int y) { return (((long) src.ordinal())<<56) + (((long)zoom)<<48) + (((long)x)<<24) + y; }
キャッシュ配列をサイクリックバッファ(FIFO)として使用している。 検索は当初 HashMap を使っていたが、パフォーマンス上問題はないため、順次検索に変更した。
パフォーマンスに拘り現在のプログラムは少々複雑にしすぎた。 一度、原点に戻す。
表示したいタイルがキャッシュになかった場合、タイルを割り当てると同時に状態を waiting とする。 これによって、ファイル読み込み/ダウンロード/レンダリングのリクエストとなる。
@Override
protected void onDraw(Canvas canvas) {
long start = System.currentTimeMillis();
super.onDraw(canvas);
MAX = MAX(); // (int)Math.pow(2,zoom);
W = super.getWidth();
H = super.getHeight();
int offX = (CX - W / 2) % PX;
int offY = (CY - H / 2) % PX;
int bx = BX(0);
int ex = EX(0);
int by = BY(0);
int ey = EY(0);
for (int xx = bx; xx < ex; xx++) {
int x = (xx + MAX) % MAX;
for (int y = by; y < ey; y++) {
long key = key(src, zoom, x, y);
Tile tile = Tile.get(key);
if (tile == null) { // キャッシュにない
Tile.alloc(src, zoom, x, y, key); // waitingになる
} else { // キャッシュにある
if (tile.status == Tile.Status.ready) {
int tx = (tile.x + MAX) % MAX;
drawBitmap(canvas, tile.bmp, PX*(tx-bx) - offX, PX*(tile.y-by) - offY);
} else if (tile.status != Tile.Status.busy &&
tile.status != Tile.Status.waiting) {
System.out.printf("onDraw error: status=%s, path=%s\n",
tile.status.name(), tile.path);
}
}
}
}
if (drawCenterLine) drawCenterLine(canvas, paint);
if (drawTileBoundary) drawTileBoundary(canvas, paint);
if (tx() != currTX || ty() != currTY) {
currTX = tx();
currTY = ty();
String str = zoom + "_" + currTX + "_" + currTY;
textViewBottom.setText(str); // 画面中央のタイル座標を表示する
}
// メモリ使用量の表示: 所要時間は小さいことを確認した
Runtime runtime = Runtime.getRuntime();
long v = (runtime.totalMemory() - runtime.freeMemory()) / (1024 * 1024);
if (v != lastMemory) {
lastMemory = (int) v;
String msg = v + "MB";
textViewTop.setText(msg); // 現在のメモリ使用量を表示する
}
System.out.printf("onDraw %dms zoom=%d\n", System.currentTimeMillis() - start, zoom);
}
OSM(OpenStrretMap)のレンダリングがメインのため、RenderThred としたが、 国土地理院地図の場合は、アーカイブしたタイル画像ファイルの読み込みか 新らたなダウンロードが実行される。
レンダリング/ファイル読み込み/ダウンロードが終わったら、ステータスを ready に変えて、 Map view を invalidate() する。これによって、上記の onDraw(Canvas canvas)が起動される。
public class RenderThread extends Thread {
final static int NumThreads = 8;
final static Thread[] threads = new Thread[NumThreads];
static boolean run;
int thread_number;
Renderer renderer;
Map map;
public RenderThread(Map map, int num) {
this.map = map;
this.thread_number = num;
this.renderer = new Renderer(map, num);
System.out.printf("RenderThead #%d ready.\n", num);
}
public void run() {
run = true;
while (run) {
Tile tile = Tile.getRequest(map.src, map.zoom);
if (tile != null) {
tile.status = Tile.Status.busy;
renderer.render(tile);
tile.status = Tile.Status.ready;
map.invalidate();
} else {
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
【プログラム・ソースコード】
Map.java
Tile.java
RenderThread.java現時点では、陸地ポリゴンだけを描画(レンダリング)している。 2月12日の実測結果と比較する。
結果を下に示す。驚いたことに約10倍速くなった。描画結果には何も問題はない。
// 今回(2月20日)の結果 ズーム 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 平均 郊 外 2 2 1 2 1 4 3 3 2 2 3 2 1 3 1 2.1ms 8タイル 東 京 2 1 2 3 3 3 2 4 2 3 3 2 3 3 6 2.8ms 12タイル
// 2月12日の結果 ズーム 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 平均 郊 外 34 32 31 50 58 70 6 8 6 8 8 8 7 7 8 23ms 8タイル 東 京 32 43 31 45 59 40 33 36 7 8 7 8 7 6 8 25ms 12タイル