📄 maptilecache.java
字号:
package org.sreid.j2me.gmapviewer;import java.io.*;import java.util.*;import javax.microedition.rms.*;import javax.microedition.lcdui.*;import javax.microedition.io.*;import org.sreid.j2me.util.*;class MapTileCache { private static final byte CACHE_VERSION = 1; private static final String RECORD_STORE_NAME = "GMapViewer.tilecache"; private static final int CACHE_INDEX_RECORD_ID = 1; private static final int KILO = 1024; // debatable if this should be 1000 or 1024 private final GMapViewer app; private final LinkedHashtable cache; // XYZ:MapTile (last is most recently used) // XXX Should I hold rms open (as I do now) or open/use/close repeatedly? // getSizeAvailable() seems inaccurate with current approach (in emulator at least) // which results in overzealous deletes. private RecordStore rms = null; int imageCacheSize; // How many decompressed Image objects to keep. Set by GMapCanvas. private final Vector trash = new Vector(); // used only by cacheMaintenance, otherwise empty // Cache info private int memUsed = 0, rmsUsed = 0; private int memCount = 0, rmsCount = 0; MapTileCache(GMapViewer app) { this.app = app; XYZ xyz = new XYZ(0,0,0); cache = new LinkedHashtable(xyz.getClass(), new MapTile(xyz).getClass()); } synchronized MapTile get(XYZ xyz) { MapTile mt = (MapTile)cache.get(xyz); if (mt == null) return null; keep(mt); return mt; } synchronized void put(MapTile mt) { keep(mt); cacheMaintenance(); } synchronized void loadFromRMS(MapTile mt) { if (mt.bytes != null || mt.rmsID == -1 || rms == null) return; // Load it from the RMS cache. try { byte[] record = rms.getRecord(mt.rmsID); ByteArrayInputStream bais = new ByteArrayInputStream(record); DataInputStream din = new DataInputStream(bais); MapTile tmp = new MapTile(din, MapTile.WITH_BYTES); din.close(); bais.close(); if (!mt.xyz.equals(tmp.xyz)) { throw new Exception("RMS cache is inconsistent. This is the wrong tile: " + tmp); } mt.bytes = tmp.bytes; if (mt.rmsBytesUsed != mt.bytes.length) { app.debug("Size mismatch: " + mt.rmsBytesUsed + " should be " + mt.bytes.length); mt.rmsBytesUsed = mt.bytes.length; } } catch (Exception e) { app.exception("An error occured while fetching a cached map tile from RMS.", e); deleteFromRMS(mt); } } private void keep(MapTile mt) { // Move it to the top of the pile cache.putLast(mt.xyz, mt); if (mt.bytes != null && mt.rmsID == -1 && rms != null) { // Freshly downloaded. Put it into the RMS cache. byte[] buffer = (byte[])app.sharedBuffers.claimResourceIgnoreInterrupt(); try { FixedByteArrayOutputStream baos = new FixedByteArrayOutputStream(buffer); DataOutputStream dos = new DataOutputStream(baos); mt.writeTo(dos, MapTile.WITH_BYTES); dos.close(); baos.close(); mt.rmsID = rms.addRecord(buffer, 0, baos.count()); mt.rmsBytesUsed = mt.bytes.length; } catch (Exception e) { app.exception("An error occured while caching a map tile to RMS.", e); } finally { app.sharedBuffers.releaseResource(buffer); } } } synchronized void invalidate(MapTile mt) { deleteFromRMS(mt); mt.bytes = null; mt.image = null; } private void deleteFromRMS(MapTile mt) { if (mt.rmsID != -1 && rms != null) { try { app.debug("Deleting from rms: " + mt); rms.deleteRecord(mt.rmsID); } catch (Exception e) { app.exception("An error occured while deleting a cached map tile from RMS.", e); } finally { mt.rmsID = -1; mt.rmsBytesUsed = 0; } } } synchronized void cacheMaintenance() { int memLimit = app.prefs.getInt("memCacheSize", 100) * KILO; int rmsLimit = app.prefs.getInt("rmsCacheSize", 2000) * KILO; int rmsReserved = app.prefs.getInt("rmsSpaceReserved", 100) * KILO; int rmsMustFree = 0; if (rms != null) { try { rmsMustFree = rmsReserved - rms.getSizeAvailable(); // if avail<reserved, rmsMustFree will be positive } catch (Exception e) { app.exception("An error occured while trying to get RMS capacity information.", e); } } trash.removeAllElements(); int imageCount = 0, freedImages = 0, freedMem = 0, freedRms = 0; memUsed = memCount = 0; rmsUsed = rmsCount = 0; for (Enumeration enum = cache.elementsInReverse() ; enum.hasMoreElements() ; ) { MapTile mt = (MapTile)enum.nextElement(); // Count and free decoded images if (mt.image != null) { imageCount++; if (imageCount > imageCacheSize) { imageCount--; freedImages++; mt.image = null; } } // Count and free memory if (mt.bytes != null) { memCount++; memUsed += mt.bytes.length; if (memUsed > memLimit) { memCount--; memUsed -= mt.bytes.length; freedMem += mt.bytes.length; mt.bytes = null; // free the memory } } // Count and free RMS space if (mt.rmsID != -1) { rmsCount++; rmsUsed += mt.rmsBytesUsed; if (rmsUsed > rmsLimit || rmsMustFree > 0) { rmsCount--; rmsUsed -= mt.rmsBytesUsed; rmsMustFree -= mt.rmsBytesUsed; freedRms += mt.rmsBytesUsed; deleteFromRMS(mt); // free the RMS space } } // Note stuff that may not belong in the cache anymore if (mt.image == null && mt.bytes == null && mt.rmsID == -1 && !mt.downloading && !mt.decoding) { trash.addElement(mt); } } // Clean up the cache itself. We keep a certain number of stale tiles around in case they are still referenced. int trashToKeep = imageCacheSize * 10 + 100; for (int i = trashToKeep; i < trash.size(); i++) { MapTile mt = (MapTile)trash.elementAt(i); synchronized (mt) { if (mt.image == null && mt.bytes == null && mt.rmsID == -1 && !mt.downloading && !mt.decoding) { cache.remove(mt.xyz); } } } trash.removeAllElements(); //app.debug("cacheMaintenance: used=" + imageCount + '/' + memUsed + '/' + rmsUsed + " freed=" + freedImages + '/' + freedMem + '/' + freedRms); int callGC = app.prefs.getInt("callgc", 0); if (callGC >= 1 && freedImages > 0) System.gc(); else if (callGC >= 2 && freedMem > 0) System.gc(); } /** Cleans up memory that can be simply re-constructed later. */ synchronized void compact() { for (Enumeration enum = cache.elements() ; enum.hasMoreElements() ; ) { MapTile mt = (MapTile)enum.nextElement(); mt.image = null; mt.bytes = null; } } synchronized void openRMS() { try { rms = RecordStore.openRecordStore(RECORD_STORE_NAME, true); app.debug("rms.getNextRecordID(): " + rms.getNextRecordID()); if (rms.getNextRecordID() == CACHE_INDEX_RECORD_ID) { // Create a new empty cache index app.debug("Creating new empty cache index."); int id = rms.addRecord(new byte[] { CACHE_VERSION }, 0, 1); if (id != CACHE_INDEX_RECORD_ID) { throw new Exception("RecordStore lied! Next record ID was bogus!"); } } Vector tileInfo = new Vector(); byte[] buffer = (byte[])app.sharedBuffers.claimResource(); try { // load in the cache index records int rlen = rms.getRecord(CACHE_INDEX_RECORD_ID, buffer, 0); ByteArrayInputStream bais = new ByteArrayInputStream(buffer, 0, rlen); DataInputStream dis = new DataInputStream(bais); if (dis.readByte() != CACHE_VERSION) { app.debug("Invalid cache version"); rms.closeRecordStore(); RecordStore.deleteRecordStore(RECORD_STORE_NAME); openRMS(); // possibly endless recursion if something goes wrong here return; } while (bais.available() > 0) { MapTile mt = new MapTile(dis, MapTile.WITH_RMSID); tileInfo.addElement(mt); } dis.close(); bais.close(); } finally { app.sharedBuffers.releaseResource(buffer); } // add them to the cache for (Enumeration enum = tileInfo.elements() ; enum.hasMoreElements() ; ) { MapTile mt = (MapTile)enum.nextElement(); MapTile existing = (MapTile)cache.get(mt.xyz); if (existing != null) { // probably won't happen existing.rmsBytesUsed = mt.rmsBytesUsed; existing.rmsID = mt.rmsID; mt = existing; } cache.putLast(mt.xyz, mt); } } catch (Exception e) { app.exception("An error occured while getting the map tile cache index from RMS.", e); } } synchronized void closeRMS() { if (rms == null) return; try { // make sure we have plenty of space to work with imageCacheSize = 0; cacheMaintenance(); byte[] buffer = (byte[])app.sharedBuffers.claimResource(); try { FixedByteArrayOutputStream fbaos = new FixedByteArrayOutputStream(buffer); DataOutputStream dos = new DataOutputStream(fbaos); dos.writeByte(CACHE_VERSION); for (Enumeration enum = cache.elements() ; enum.hasMoreElements() ; ) { MapTile mt = (MapTile)enum.nextElement(); mt.writeTo(dos, MapTile.WITH_RMSID); } dos.close(); fbaos.close(); rms.setRecord(CACHE_INDEX_RECORD_ID, buffer, 0, fbaos.count()); rms.closeRecordStore(); } finally { app.sharedBuffers.releaseResource(buffer); } } catch (Exception e) { app.exception("An error occured while closing the map tile cache RMS", e); } finally { rms = null; } } synchronized String validateMem() throws Exception { if (rms == null) return "No RMS cache to validate against."; // Check for dangling references byte[] buffer = (byte[])app.sharedBuffers.claimResource(); try { int missing = 0; for (Enumeration enum = cache.elements() ; enum.hasMoreElements() ; ) { MapTile mt = (MapTile)enum.nextElement(); if (mt.rmsID == -1) continue; try { if (rms.getRecord(mt.rmsID, buffer, 0) < 1) throw new Exception("Empty record"); } catch (Exception e) { app.debug("No such record ID: " + mt.rmsID); missing++; deleteFromRMS(mt); } } return "Found " + missing + " dangling references."; } finally { app.sharedBuffers.releaseResource(buffer); } } synchronized String validateRMS() throws Exception { if (rms == null) return "No RMS cache open."; // Check for orphaned records int invalid = 0; RecordEnumeration re = rms.enumerateRecords(null, null, false); byte[] buffer = (byte[])app.sharedBuffers.claimResource(); try { while (re.hasNextElement()) { int id = re.nextRecordId(); if (id == CACHE_INDEX_RECORD_ID) continue; // special record, ignore it MapTile fromRms = null; try { int rlen = rms.getRecord(id, buffer, 0); ByteArrayInputStream bais = new ByteArrayInputStream(buffer, 0, rlen); DataInputStream din = new DataInputStream(bais); fromRms = new MapTile(din, MapTile.WITH_MINIMUM); fromRms.bytes = null; fromRms.rmsID = id; din.close(); bais.close(); MapTile mt = (MapTile)cache.get(fromRms.xyz); if (mt == null) throw new Exception("orphaned record"); if (!mt.xyz.equals(fromRms.xyz)) throw new Exception("XYZ mismatch"); if (mt.rmsID != fromRms.rmsID) throw new Exception("ID mismatch"); } catch (Exception e) { if (fromRms != null) { app.debug("Invalid record #" + id + ": " + e); invalid++; deleteFromRMS(fromRms); //FIXME: Should reclaim record instead of deleting it } } } } finally { app.sharedBuffers.releaseResource(buffer); } return "Found " + invalid + " invalid records."; } synchronized void clearCache() throws Exception { if (rms != null) { for (;;) { try { rms.closeRecordStore(); } catch (RecordStoreNotOpenException e) { break; } } } RecordStore.deleteRecordStore(RECORD_STORE_NAME); cache.clear(); openRMS(); } synchronized String getInfo() { String availRms; try { availRms = "" + (rms.getSizeAvailable() / KILO) + "KB"; } catch (Exception e) { availRms = e.toString(); } return "Map tile cache status\n\n" + "Mem used: " + (memUsed / KILO) + "KB as " + memCount + " tiles\n" + "Mem free: " + (Runtime.getRuntime().freeMemory() / KILO) + "KB\n" + "Mem total: " + (Runtime.getRuntime().totalMemory() / KILO) + "KB\n\n" + "Used RMS: " + (rmsUsed / KILO) + "KB as " + rmsCount + " tiles\n" + "Free RMS: " + availRms + "\n"; }}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -