TileManager:
- set zoomMin/Max for current TileSource - add zoom-level switch threshold - add TileManager.getTile() - remove releaseTiles() - extract JobCompletedEvent from anonymous class
This commit is contained in:
parent
3fa121d31a
commit
a8400dd48a
@ -1,5 +1,4 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2010, 2011, 2012 mapsforge.org
|
|
||||||
* Copyright 2012, 2013 Hannes Janetzek
|
* Copyright 2012, 2013 Hannes Janetzek
|
||||||
*
|
*
|
||||||
* This file is part of the OpenScienceMap project (http://www.opensciencemap.org).
|
* This file is part of the OpenScienceMap project (http://www.opensciencemap.org).
|
||||||
@ -20,11 +19,16 @@ package org.oscim.layers.tile;
|
|||||||
import static org.oscim.layers.tile.MapTile.State.LOADING;
|
import static org.oscim.layers.tile.MapTile.State.LOADING;
|
||||||
import static org.oscim.layers.tile.MapTile.State.NONE;
|
import static org.oscim.layers.tile.MapTile.State.NONE;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A JobQueue keeps the list of pending jobs for a MapView and prioritizes them.
|
* A JobQueue keeps the list of pending jobs for a MapView and prioritizes them.
|
||||||
*/
|
*/
|
||||||
public class JobQueue {
|
public class JobQueue {
|
||||||
|
|
||||||
|
static final Logger log = LoggerFactory.getLogger(JobQueue.class);
|
||||||
|
|
||||||
private int mCurrentJob = 0;
|
private int mCurrentJob = 0;
|
||||||
private MapTile[] mJobs;
|
private MapTile[] mJobs;
|
||||||
|
|
||||||
@ -51,7 +55,12 @@ public class JobQueue {
|
|||||||
MapTile[] tiles = mJobs;
|
MapTile[] tiles = mJobs;
|
||||||
|
|
||||||
for (int i = mCurrentJob, n = mJobs.length; i < n; i++) {
|
for (int i = mCurrentJob, n = mJobs.length; i < n; i++) {
|
||||||
tiles[i].state = NONE;
|
|
||||||
|
if (tiles[i].state == LOADING)
|
||||||
|
tiles[i].state = NONE;
|
||||||
|
else
|
||||||
|
log.debug("wrong tile in queue {} {}", tiles[i], tiles[i].state);
|
||||||
|
|
||||||
tiles[i] = null;
|
tiles[i] = null;
|
||||||
}
|
}
|
||||||
mCurrentJob = 0;
|
mCurrentJob = 0;
|
||||||
|
@ -51,8 +51,8 @@ public class TileManager {
|
|||||||
private final int mCacheLimit;
|
private final int mCacheLimit;
|
||||||
private int mCacheReduce;
|
private int mCacheReduce;
|
||||||
|
|
||||||
private final int mMinZoom;
|
private int mMinZoom;
|
||||||
private final int mMaxZoom;
|
private int mMaxZoom;
|
||||||
|
|
||||||
private int[] mZoomTable;
|
private int[] mZoomTable;
|
||||||
|
|
||||||
@ -60,11 +60,12 @@ public class TileManager {
|
|||||||
* limit number tiles with new data not uploaded to GL
|
* limit number tiles with new data not uploaded to GL
|
||||||
* TODO this should depend on the number of tiles displayed
|
* TODO this should depend on the number of tiles displayed
|
||||||
*/
|
*/
|
||||||
private static final int MAX_TILES_IN_QUEUE = 40;
|
private static final int MAX_TILES_IN_QUEUE = 20;
|
||||||
|
|
||||||
/** cache limit threshold */
|
/** cache limit threshold */
|
||||||
private static final int CACHE_THRESHOLD = 30;
|
private static final int CACHE_THRESHOLD = 25;
|
||||||
private static final int CACHE_CLEAR_THRESHOLD = 10;
|
private static final int CACHE_CLEAR_THRESHOLD = 10;
|
||||||
|
private static final int QUEUE_CLEAR_THRESHOLD = 10;
|
||||||
|
|
||||||
private final Map mMap;
|
private final Map mMap;
|
||||||
private final Viewport mViewport;
|
private final Viewport mViewport;
|
||||||
@ -81,49 +82,55 @@ public class TileManager {
|
|||||||
/** counter for tiles with new data not yet loaded to GL */
|
/** counter for tiles with new data not yet loaded to GL */
|
||||||
private int mTilesForUpload;
|
private int mTilesForUpload;
|
||||||
|
|
||||||
/* new tile jobs for MapWorkers */
|
/** new tile jobs for MapWorkers */
|
||||||
private final ArrayList<MapTile> mJobs;
|
private final ArrayList<MapTile> mJobs;
|
||||||
|
|
||||||
/* counter to check whether current TileSet has changed */
|
/** counter to check whether current TileSet has changed */
|
||||||
private int mUpdateSerial;
|
private int mUpdateSerial;
|
||||||
|
|
||||||
/* lock for TileSets while updating MapTile locks - still needed? */
|
/** lock for TileSets while updating MapTile locks - still needed? */
|
||||||
private final Object mTilelock = new Object();
|
private final Object mTilelock = new Object();
|
||||||
|
|
||||||
private TileSet mCurrentTiles;
|
private TileSet mCurrentTiles;
|
||||||
/* package */TileSet mNewTiles;
|
/* package */TileSet mNewTiles;
|
||||||
|
|
||||||
/* job queue filled in TileManager and polled by TileLoaders */
|
/** job queue filled in TileManager and polled by TileLoaders */
|
||||||
private final JobQueue jobQueue;
|
private final JobQueue jobQueue;
|
||||||
|
|
||||||
private final float[] mMapPlane = new float[8];
|
private final float[] mMapPlane = new float[8];
|
||||||
|
|
||||||
private final TileIndex<TileNode, MapTile> mIndex = new TileIndex<TileNode, MapTile>() {
|
private boolean mLoadParent = false;
|
||||||
|
private int mPrevZoomlevel;
|
||||||
|
|
||||||
@Override
|
//private final double mLevelUpThreshold = 1.15;
|
||||||
public void removeItem(MapTile t) {
|
//private final double mLevelDownThreshold = 1.95;
|
||||||
if (t.node == null) {
|
private final double mLevelUpThreshold = 1;
|
||||||
log.error("already removed {}", t);
|
private final double mLevelDownThreshold = 2;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
super.remove(t.node);
|
private final TileIndex<TileNode, MapTile> mIndex =
|
||||||
t.node.item = null;
|
new TileIndex<TileNode, MapTile>() {
|
||||||
}
|
@Override
|
||||||
|
public void removeItem(MapTile t) {
|
||||||
|
if (t.node == null)
|
||||||
|
throw new IllegalStateException("already removed: " + t);
|
||||||
|
|
||||||
@Override
|
super.remove(t.node);
|
||||||
public TileNode create() {
|
t.node.item = null;
|
||||||
return new TileNode();
|
}
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
public final EventDispatcher<Listener, MapTile> events = new EventDispatcher<Listener, MapTile>() {
|
@Override
|
||||||
|
public TileNode create() {
|
||||||
|
return new TileNode();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
@Override
|
public final EventDispatcher<Listener, MapTile> events =
|
||||||
public void tell(Listener l, Event event, MapTile tile) {
|
new EventDispatcher<Listener, MapTile>() {
|
||||||
l.onTileManagerEvent(event, tile);
|
@Override
|
||||||
}
|
public void tell(Listener l, Event event, MapTile tile) {
|
||||||
};
|
l.onTileManagerEvent(event, tile);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
public interface Listener extends EventListener {
|
public interface Listener extends EventListener {
|
||||||
void onTileManagerEvent(Event event, MapTile tile);
|
void onTileManagerEvent(Event event, MapTile tile);
|
||||||
@ -149,7 +156,12 @@ public class TileManager {
|
|||||||
|
|
||||||
public void setZoomTable(int[] zoomLevel) {
|
public void setZoomTable(int[] zoomLevel) {
|
||||||
mZoomTable = zoomLevel;
|
mZoomTable = zoomLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MapTile getTile(int x, int y, int z) {
|
||||||
|
synchronized (mTilelock) {
|
||||||
|
return mIndex.getTile(x, y, z);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void init() {
|
public void init() {
|
||||||
@ -186,9 +198,10 @@ public class TileManager {
|
|||||||
public boolean update(MapPosition pos) {
|
public boolean update(MapPosition pos) {
|
||||||
|
|
||||||
// FIXME cant expect init to be called otherwise
|
// FIXME cant expect init to be called otherwise
|
||||||
if (mNewTiles == null || mNewTiles.tiles.length == 0)
|
if (mNewTiles == null || mNewTiles.tiles.length == 0) {
|
||||||
|
mPrevZoomlevel = pos.zoomLevel;
|
||||||
init();
|
init();
|
||||||
|
}
|
||||||
/* clear JobQueue and set tiles to state == NONE.
|
/* clear JobQueue and set tiles to state == NONE.
|
||||||
* one could also append new tiles and sort in JobQueue
|
* one could also append new tiles and sort in JobQueue
|
||||||
* but this has the nice side-effect that MapWorkers dont
|
* but this has the nice side-effect that MapWorkers dont
|
||||||
@ -208,7 +221,27 @@ public class TileManager {
|
|||||||
|
|
||||||
int tileZoom = FastMath.clamp(pos.zoomLevel, mMinZoom, mMaxZoom);
|
int tileZoom = FastMath.clamp(pos.zoomLevel, mMinZoom, mMaxZoom);
|
||||||
|
|
||||||
if (mZoomTable != null) {
|
//mLoadParent = pos.getZoomScale() < 1.5;
|
||||||
|
|
||||||
|
/* greater 1 when zoomed in further than current zoomlevel */
|
||||||
|
double scaleDiv = pos.scale / (1 << tileZoom);
|
||||||
|
|
||||||
|
mLoadParent = false;
|
||||||
|
|
||||||
|
if (mZoomTable == null) {
|
||||||
|
/* positive when current zoomlevel is greater than previous */
|
||||||
|
int zoomDiff = tileZoom - mPrevZoomlevel;
|
||||||
|
if (zoomDiff == 1) {
|
||||||
|
/* dont switch zoomlevel if */
|
||||||
|
if (scaleDiv < mLevelUpThreshold)
|
||||||
|
tileZoom = mPrevZoomlevel;
|
||||||
|
} else if (zoomDiff == -1) {
|
||||||
|
mLoadParent = true;
|
||||||
|
/* dont switch zoomlevel if */
|
||||||
|
if (scaleDiv > mLevelDownThreshold)
|
||||||
|
tileZoom = mPrevZoomlevel;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
int match = 0;
|
int match = 0;
|
||||||
for (int z : mZoomTable) {
|
for (int z : mZoomTable) {
|
||||||
if (z <= tileZoom && z > match)
|
if (z <= tileZoom && z > match)
|
||||||
@ -219,11 +252,13 @@ public class TileManager {
|
|||||||
|
|
||||||
tileZoom = match;
|
tileZoom = match;
|
||||||
}
|
}
|
||||||
|
//log.debug("{}|{}|{}|{}", mLoadParent, mPrevZoomlevel, pos.zoomLevel, scaleDiv);
|
||||||
|
mPrevZoomlevel = tileZoom;
|
||||||
|
|
||||||
mViewport.getMapExtents(mMapPlane, Tile.SIZE / 2);
|
mViewport.getMapExtents(mMapPlane, Tile.SIZE / 2);
|
||||||
|
|
||||||
// scan visible tiles. callback function calls 'addTile'
|
/* scan visible tiles. callback function calls 'addTile'
|
||||||
// which updates mNewTiles
|
* which updates mNewTiles */
|
||||||
mNewTiles.cnt = 0;
|
mNewTiles.cnt = 0;
|
||||||
mScanBox.scan(pos.x, pos.y, pos.scale, tileZoom, mMapPlane);
|
mScanBox.scan(pos.x, pos.y, pos.scale, tileZoom, mMapPlane);
|
||||||
|
|
||||||
@ -255,7 +290,7 @@ public class TileManager {
|
|||||||
/* unlock previous tiles */
|
/* unlock previous tiles */
|
||||||
mCurrentTiles.releaseTiles();
|
mCurrentTiles.releaseTiles();
|
||||||
|
|
||||||
/* make new tiles current */
|
/* swap newTiles with currentTiles */
|
||||||
TileSet tmp = mCurrentTiles;
|
TileSet tmp = mCurrentTiles;
|
||||||
mCurrentTiles = mNewTiles;
|
mCurrentTiles = mNewTiles;
|
||||||
mNewTiles = tmp;
|
mNewTiles = tmp;
|
||||||
@ -277,7 +312,6 @@ public class TileManager {
|
|||||||
|
|
||||||
/* sets tiles to state == LOADING */
|
/* sets tiles to state == LOADING */
|
||||||
jobQueue.setJobs(jobs);
|
jobQueue.setJobs(jobs);
|
||||||
|
|
||||||
mJobs.clear();
|
mJobs.clear();
|
||||||
|
|
||||||
if (mCacheReduce < mCacheLimit / 2) {
|
if (mCacheReduce < mCacheLimit / 2) {
|
||||||
@ -294,13 +328,11 @@ public class TileManager {
|
|||||||
|
|
||||||
if (remove > CACHE_THRESHOLD ||
|
if (remove > CACHE_THRESHOLD ||
|
||||||
mTilesForUpload > MAX_TILES_IN_QUEUE)
|
mTilesForUpload > MAX_TILES_IN_QUEUE)
|
||||||
|
|
||||||
limitCache(pos, remove);
|
limitCache(pos, remove);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** only used in setmapDatabase -- deprecate? */
|
|
||||||
public void clearJobs() {
|
public void clearJobs() {
|
||||||
jobQueue.clear();
|
jobQueue.clear();
|
||||||
}
|
}
|
||||||
@ -316,8 +348,9 @@ public class TileManager {
|
|||||||
/**
|
/**
|
||||||
* Retrive a TileSet of current tiles. Tiles remain locked in cache until
|
* Retrive a TileSet of current tiles. Tiles remain locked in cache until
|
||||||
* the set is unlocked by either passing it again to this function or to
|
* the set is unlocked by either passing it again to this function or to
|
||||||
* releaseTiles. If passed TileSet is null it will be allocated.
|
* releaseTiles.
|
||||||
*
|
*
|
||||||
|
* @threadsafe
|
||||||
* @param tileSet
|
* @param tileSet
|
||||||
* to be updated
|
* to be updated
|
||||||
* @return true if TileSet has changed
|
* @return true if TileSet has changed
|
||||||
@ -340,13 +373,6 @@ public class TileManager {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Unlock TileSet and clear all item references.
|
|
||||||
*/
|
|
||||||
public void releaseTiles(TileSet tileSet) {
|
|
||||||
tileSet.releaseTiles();
|
|
||||||
}
|
|
||||||
|
|
||||||
MapTile addTile(int x, int y, int zoomLevel) {
|
MapTile addTile(int x, int y, int zoomLevel) {
|
||||||
MapTile tile = mIndex.getTile(x, y, zoomLevel);
|
MapTile tile = mIndex.getTile(x, y, zoomLevel);
|
||||||
|
|
||||||
@ -359,14 +385,14 @@ public class TileManager {
|
|||||||
mJobs.add(tile);
|
mJobs.add(tile);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((zoomLevel > mMinZoom) && (mZoomTable == null)) {
|
if (mLoadParent && (zoomLevel > mMinZoom) && (mZoomTable == null)) {
|
||||||
/* prefetch parent */
|
/* prefetch parent */
|
||||||
MapTile p = tile.node.parent.item;
|
MapTile p = tile.node.parent.item;
|
||||||
if (p == null) {
|
if (p == null) {
|
||||||
TileNode n = mIndex.add(x >> 1, y >> 1, zoomLevel - 1);
|
TileNode n = mIndex.add(x >> 1, y >> 1, zoomLevel - 1);
|
||||||
p = n.item = new MapTile(n, x >> 1, y >> 1, zoomLevel - 1);
|
p = n.item = new MapTile(n, x >> 1, y >> 1, zoomLevel - 1);
|
||||||
addToCache(p);
|
addToCache(p);
|
||||||
// hack to not add tile twice to queue
|
/* hack to not add tile twice to queue */
|
||||||
p.state = LOADING;
|
p.state = LOADING;
|
||||||
mJobs.add(p);
|
mJobs.add(p);
|
||||||
} else if (!p.isActive()) {
|
} else if (!p.isActive()) {
|
||||||
@ -400,21 +426,19 @@ public class TileManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void removeFromCache(MapTile t) {
|
private void removeFromCache(MapTile t) {
|
||||||
|
if (t.state(NEW_DATA | READY))
|
||||||
if (t.state == NEW_DATA || t.state == READY)
|
|
||||||
events.fire(TILE_REMOVED, t);
|
events.fire(TILE_REMOVED, t);
|
||||||
|
|
||||||
synchronized (t) {
|
if (dbg)
|
||||||
/* still belongs to TileLoader thread, defer clearing to
|
log.debug("remove from cache {} {} {}", t, t.state, t.isLocked());
|
||||||
* jobCompleted() */
|
|
||||||
if (t.state != CANCEL)
|
|
||||||
t.clear();
|
|
||||||
|
|
||||||
// needed?
|
//synchronized (t) {
|
||||||
t.state = CANCEL;
|
/* When in CANCEL state tile belongs to TileLoader thread,
|
||||||
|
* defer clearing to jobCompleted() */
|
||||||
|
if (t.state(NEW_DATA | READY))
|
||||||
|
t.clear();
|
||||||
|
|
||||||
mIndex.removeItem(t);
|
mIndex.removeItem(t);
|
||||||
}
|
|
||||||
|
|
||||||
mTilesCount--;
|
mTilesCount--;
|
||||||
}
|
}
|
||||||
@ -490,18 +514,15 @@ public class TileManager {
|
|||||||
remove--;
|
remove--;
|
||||||
}
|
}
|
||||||
|
|
||||||
remove = (newTileCnt - MAX_TILES_IN_QUEUE) + 10;
|
remove = (newTileCnt - MAX_TILES_IN_QUEUE) + QUEUE_CLEAR_THRESHOLD;
|
||||||
//int r = remove;
|
//int r = remove;
|
||||||
for (int i = size - 1; i >= 0 && remove > 0; i--) {
|
for (int i = size - 1; i >= 0 && remove > 0; i--) {
|
||||||
MapTile t = tiles[i];
|
MapTile t = tiles[i];
|
||||||
if (t != null && t.state == NEW_DATA) {
|
if (t != null && !t.isLocked() && t.state == NEW_DATA) {
|
||||||
if (!t.isLocked()) {
|
newTileCnt--;
|
||||||
newTileCnt--;
|
removeFromCache(t);
|
||||||
|
tiles[i] = null;
|
||||||
removeFromCache(t);
|
remove--;
|
||||||
tiles[i] = null;
|
|
||||||
remove--;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -512,37 +533,53 @@ public class TileManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* called by TileLoader thread when tile is loaded.
|
* Called by TileLoader thread when tile is loaded.
|
||||||
*
|
*
|
||||||
|
* @threadsafe
|
||||||
* @param tile
|
* @param tile
|
||||||
* Tile ready for upload in TileRenderLayer
|
* Tile ready for upload in TileRenderLayer
|
||||||
*/
|
*/
|
||||||
public void jobCompleted(final MapTile tile, final boolean success) {
|
public void jobCompleted(MapTile tile, boolean success) {
|
||||||
mMap.post(new Runnable() {
|
|
||||||
|
|
||||||
@Override
|
/* send TILE_LOADED event on main-loop */
|
||||||
public void run() {
|
mMap.post(new JobCompletedEvent(tile, success));
|
||||||
if (!success || tile.state == CANCEL) {
|
|
||||||
|
|
||||||
log.debug("loading {}: {}",
|
/* locked means the tile is visible or referenced by
|
||||||
(success ? "canceled" : "failed"),
|
* a tile that might be visible. */
|
||||||
tile);
|
if (tile.isLocked())
|
||||||
tile.clear();
|
mMap.render();
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
|
class JobCompletedEvent implements Runnable {
|
||||||
|
final MapTile tile;
|
||||||
|
final boolean success;
|
||||||
|
|
||||||
|
public JobCompletedEvent(MapTile tile, boolean success) {
|
||||||
|
this.tile = tile;
|
||||||
|
this.success = success;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (success && tile.state != CANCEL) {
|
||||||
|
|
||||||
tile.state = NEW_DATA;
|
tile.state = NEW_DATA;
|
||||||
events.fire(TILE_LOADED, tile);
|
events.fire(TILE_LOADED, tile);
|
||||||
|
|
||||||
mTilesForUpload += 1;
|
mTilesForUpload++;
|
||||||
|
//log.debug("loading {}: ready", tile);
|
||||||
/* locked means the tile is visible or referenced by
|
return;
|
||||||
* a tile that might be visible. */
|
|
||||||
if (tile.isLocked())
|
|
||||||
mMap.render();
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
|
log.debug("loading {}: {}", tile,
|
||||||
|
success ? "canceled"
|
||||||
|
: "failed");
|
||||||
|
|
||||||
|
tile.clear();
|
||||||
|
|
||||||
|
if (tile.state == LOADING)
|
||||||
|
mIndex.removeItem(tile);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void updateDistances(MapTile[] tiles, int size, MapPosition pos) {
|
private static void updateDistances(MapTile[] tiles, int size, MapPosition pos) {
|
||||||
@ -629,4 +666,9 @@ public class TileManager {
|
|||||||
public MapTile getTile(int tileX, int tileY, byte zoomLevel) {
|
public MapTile getTile(int tileX, int tileY, byte zoomLevel) {
|
||||||
return mIndex.getTile(tileX, tileY, zoomLevel);
|
return mIndex.getTile(tileX, tileY, zoomLevel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setZoomLevel(int zoomLevelMin, int zoomLevelMax) {
|
||||||
|
mMinZoom = zoomLevelMin;
|
||||||
|
mMaxZoom = zoomLevelMax;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -81,7 +81,7 @@ public abstract class TileRenderer extends LayerRenderer {
|
|||||||
public synchronized void update(GLViewport v) {
|
public synchronized void update(GLViewport v) {
|
||||||
|
|
||||||
if (mAlpha == 0) {
|
if (mAlpha == 0) {
|
||||||
mTileManager.releaseTiles(mDrawTiles);
|
mDrawTiles.releaseTiles();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,7 +78,7 @@ public class VectorTileLayer extends TileLayer {
|
|||||||
public boolean setTileSource(TileSource tileSource) {
|
public boolean setTileSource(TileSource tileSource) {
|
||||||
pauseLoaders(true);
|
pauseLoaders(true);
|
||||||
mTileManager.clearJobs();
|
mTileManager.clearJobs();
|
||||||
|
mTileManager.setZoomLevel(tileSource.getZoomLevelMin(), tileSource.getZoomLevelMax());
|
||||||
if (mTileSource != null) {
|
if (mTileSource != null) {
|
||||||
mTileSource.close();
|
mTileSource.close();
|
||||||
mTileSource = null;
|
mTileSource = null;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user