⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 maptilecache.java

📁 基于J2ME的手机地图客户端源码
💻 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 + -