update: clear memory more aggressively
add: the noCache property for marker
This property works for only http(s)://
* this property is experimental
MODIFY
src/android/plugin/google/maps/AsyncLoadImage.java
src/android/plugin/google/maps/AsyncLoadImage.java
@@ -3,6 +3,7 @@ package plugin.google.maps;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
import java.net.URL;
import android.annotation.SuppressLint;
@@ -63,13 +64,34 @@ public class AsyncLoadImage extends AsyncTask<String, Void, Bitmap> {
mIconCache = new BitmapCache(cacheSize);
}
- private void addBitmapToMemoryCache(String key, Bitmap image) {
+
+ public static String getCacheKey(String url, int width, int height) {
+ try {
+ return getCacheKey(new URL(url), width, height);
+ } catch (MalformedURLException e) {
+ return null;
+ }
+ }
+ public static String getCacheKey(URL url, int width, int height) {
+ return url.hashCode() + "/" + width + "x" + height;
+ }
+
+ public static void addBitmapToMemoryCache(String key, Bitmap image) {
if (getBitmapFromMemCache(key) == null) {
mIconCache.put(key, image.copy(image.getConfig(), true));
}
}
- private Bitmap getBitmapFromMemCache(String key) {
+ public static void removeBitmapFromMemCahce(String key) {
+ Log.d("removeBitmap", key);
+ Bitmap image = mIconCache.remove(key);
+ if (image == null || image.isRecycled()) {
+ return;
+ }
+ image.recycle();
+ }
+
+ private static Bitmap getBitmapFromMemCache(String key) {
Bitmap image = mIconCache.get(key);
if (image == null || image.isRecycled()) {
return null;
@@ -94,11 +116,13 @@ public class AsyncLoadImage extends AsyncTask<String, Void, Bitmap> {
protected Bitmap doInBackground(String... urls) {
try {
URL url = new URL(urls[0]);
- String cacheKey = url.hashCode() + "/" + mWidth + "x" + mHeight;
+ String cacheKey = getCacheKey(url, mWidth, mHeight);
+ if (!noCaching) {
Bitmap image = getBitmapFromMemCache(cacheKey);
if (image != null) {
return image;
}
+ }
boolean redirect = true;
HttpURLConnection http = null;
@@ -154,6 +178,7 @@ public class AsyncLoadImage extends AsyncTask<String, Void, Bitmap> {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
myBitmap = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length, options);
+ myBitmap.recycle();
if (mWidth < 1 && mHeight < 1) {
mWidth = options.outWidth;
@@ -189,6 +214,7 @@ public class AsyncLoadImage extends AsyncTask<String, Void, Bitmap> {
myBitmap = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length, options);
canvas.drawBitmap(myBitmap, middleX - options.outWidth / 2, middleY - options.outHeight / 2, new Paint(Paint.FILTER_BITMAP_FLAG));
myBitmap.recycle();
+ myBitmap = null;
canvas = null;
imageBytes = null;

MODIFY
src/android/plugin/google/maps/PluginMap.java
src/android/plugin/google/maps/PluginMap.java
@@ -663,6 +663,13 @@ public class PluginMap extends MyPlugin implements OnMarkerClickListener,
e.printStackTrace();
}
}
+ String[] pluginNames = plugins.keySet().toArray(new String[plugins.size()]);
+ PluginEntry pluginEntry;
+ for (int i = 0; i < pluginNames.length; i++) {
+ pluginEntry = plugins.remove(pluginNames[i]);
+ pluginEntry.plugin.onDestroy();
+ pluginEntry = null;
+ }
//Log.d("pluginMap", "--> mapView = " + mapView);
map = null;
mapView = null;
@@ -671,6 +678,7 @@ public class PluginMap extends MyPlugin implements OnMarkerClickListener,
if (callbackContext != null) {
sendNoResult(callbackContext);
}
+ PluginMap.this.onDestroy();
}
});
}
@@ -1575,21 +1583,6 @@ public class PluginMap extends MyPlugin implements OnMarkerClickListener,
@SuppressWarnings("unused")
public void clear(JSONArray args, final CallbackContext callbackContext) throws JSONException {
- this.activity.runOnUiThread(new Runnable() {
- @Override
- public void run() {
- boolean isSuccess = false;
- while (!isSuccess) {
- try {
- map.clear();
- isSuccess = true;
- } catch (Exception e) {
- //e.printStackTrace();
- isSuccess = false;
- }
- }
- }
- });
cordova.getThreadPool().submit(new Runnable() {
@Override
@@ -1606,11 +1599,28 @@ public class PluginMap extends MyPlugin implements OnMarkerClickListener,
((MyPlugin) pluginEntry.plugin).clear();
}
}
+
+ activity.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ boolean isSuccess = false;
+ while (!isSuccess) {
+ try {
+ map.clear();
+ isSuccess = true;
+ } catch (Exception e) {
+ //e.printStackTrace();
+ isSuccess = false;
+ }
+ }
if (callbackContext != null) {
sendNoResult(callbackContext);
}
}
});
+
+ }
+ });
}
/**

MODIFY
src/android/plugin/google/maps/PluginMarker.java
src/android/plugin/google/maps/PluginMarker.java
@@ -44,6 +44,8 @@ public class PluginMarker extends MyPlugin implements MyPluginInterface {
}
private ArrayList<AsyncTask> iconLoadingTasks = new ArrayList<AsyncTask>();
+ private ArrayList<String> iconCacheKeys = new ArrayList<String>();
+ private ArrayList<Bitmap> icons = new ArrayList<Bitmap>();
@Override
public void initialize(CordovaInterface cordova, final CordovaWebView webView) {
@@ -56,6 +58,9 @@ public class PluginMarker extends MyPlugin implements MyPluginInterface {
cordova.getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
+ if (objects == null) {
+ return;
+ }
Set<String> keySet = objects.keySet();
String[] objectIdArray = keySet.toArray(new String[keySet.size()]);
@@ -64,7 +69,9 @@ public class PluginMarker extends MyPlugin implements MyPluginInterface {
if (objectId.startsWith("marker_") &&
!objectId.startsWith("marker_property_")) {
Marker marker = (Marker) objects.remove(objectId);
+ marker.setIcon(null);
marker.remove();
+ marker = null;
} else {
Object object = objects.remove(objectId);
object = null;
@@ -81,12 +88,68 @@ public class PluginMarker extends MyPlugin implements MyPluginInterface {
@Override
protected void clear() {
+ //--------------------------------------
+ // Cancel tasks
+ //--------------------------------------
+ cordova.getThreadPool().submit(new Runnable() {
+ @Override
+ public void run() {
AsyncLoadImage[] tasks = iconLoadingTasks.toArray(new AsyncLoadImage[iconLoadingTasks.size()]);
for (int i = 0; i < tasks.length; i++) {
tasks[i].cancel(true);
}
iconLoadingTasks.clear();
- super.clear();
+ }
+ });
+
+ //--------------------------------------
+ // Recycle bitmaps as much as possible
+ //--------------------------------------
+ String[] cacheKeys = iconCacheKeys.toArray(new String[iconCacheKeys.size()]);
+ for (int i = 0; i < cacheKeys.length; i++) {
+ AsyncLoadImage.removeBitmapFromMemCahce(iconCacheKeys.remove(0));
+ }
+ Bitmap[] cachedBitmaps = icons.toArray(new Bitmap[icons.size()]);
+ Bitmap image;
+ for (int i = 0; i < cachedBitmaps.length; i++) {
+ image = icons.remove(0);
+ if (image != null && !image.isRecycled()) {
+ image.recycle();
+ }
+ image = null;
+ }
+ icons.clear();
+
+ //--------------------------------------
+ // clean up properties as much as possible
+ //--------------------------------------
+ cordova.getActivity().runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ Set<String> keySet = objects.keySet();
+ String[] objectIdArray = keySet.toArray(new String[keySet.size()]);
+
+ for (String objectId : objectIdArray) {
+ if (objects.containsKey(objectId)) {
+ if (objectId.startsWith("marker_") &&
+ !objectId.startsWith("marker_property_")) {
+ Marker marker = (Marker) objects.remove(objectId);
+ marker.setIcon(null);
+ marker.remove();
+ marker = null;
+ } else {
+ Object object = objects.remove(objectId);
+ object = null;
+ }
+ }
+ }
+
+ objects.clear();
+ PluginMarker.super.clear();
+
+ }
+ });
+
}
/**
@@ -146,6 +209,11 @@ public class PluginMarker extends MyPlugin implements MyPluginInterface {
} else {
properties.put("disableAutoPan", false);
}
+ if (opts.has("noCache")) {
+ properties.put("noCache", opts.getBoolean("noCache"));
+ } else {
+ properties.put("noCache", false);
+ }
if (opts.has("useHtmlInfoWnd")) {
properties.put("useHtmlInfoWnd", opts.getBoolean("useHtmlInfoWnd"));
} else {
@@ -652,12 +720,18 @@ public class PluginMarker extends MyPlugin implements MyPluginInterface {
return;
}
+ String[] cacheKeys = iconCacheKeys.toArray(new String[iconCacheKeys.size()]);
+ for (int i = 0; i < cacheKeys.length; i++) {
+ AsyncLoadImage.removeBitmapFromMemCahce(cacheKeys[i]);
+ }
+
cordova.getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
marker.remove();
objects.remove(id);
+
String propertyId = "marker_property_" + id;
objects.remove(propertyId);
sendNoResult(callbackContext);
@@ -772,6 +846,10 @@ public class PluginMarker extends MyPlugin implements MyPluginInterface {
}
private void setIcon_(final Marker marker, final Bundle iconProperty, final PluginAsyncInterface callback) {
+ boolean noCaching = false;
+ if (iconProperty.containsKey("noCache")) {
+ noCaching = iconProperty.getBoolean("noCache");
+ }
if (iconProperty.containsKey("iconHue")) {
cordova.getActivity().runOnUiThread(new Runnable() {
@Override
@@ -880,6 +958,7 @@ public class PluginMarker extends MyPlugin implements MyPluginInterface {
if (image == null) {
return null;
}
+ icons.add(image);
Boolean isResized = false;
if (iconProperty.containsKey("size")) {
@@ -902,6 +981,7 @@ public class PluginMarker extends MyPlugin implements MyPluginInterface {
if (!isResized) {
image = PluginUtil.scaleBitmapForDevice(image);
}
+ icons.add(image);
return image;
}
@@ -971,7 +1051,10 @@ public class PluginMarker extends MyPlugin implements MyPluginInterface {
height = sizeInfo.getInt("height", height);
}
- AsyncLoadImage task = new AsyncLoadImage("Mozilla", width, height, new AsyncLoadImageInterface() {
+ String cacheKey = AsyncLoadImage.getCacheKey(iconUrl, width, height);
+ iconCacheKeys.add(cacheKey);
+
+ AsyncLoadImage task = new AsyncLoadImage("Mozilla", width, height, noCaching, new AsyncLoadImageInterface() {
@Override
public void onPostExecute(Bitmap image) {
@@ -982,6 +1065,7 @@ public class PluginMarker extends MyPlugin implements MyPluginInterface {
}
try {
+ icons.add(image);
BitmapDescriptor bitmapDescriptor = BitmapDescriptorFactory.fromBitmap(image);
marker.setIcon(bitmapDescriptor);

MODIFY
src/android/plugin/google/maps/PluginUtil.java
src/android/plugin/google/maps/PluginUtil.java
@@ -243,6 +243,7 @@ public class PluginUtil {
Canvas canvas = new Canvas(scaledBitmap);
canvas.setMatrix(scaleMatrix);
canvas.drawBitmap(bitmap, middleX - bitmap.getWidth() / 2, middleY - bitmap.getHeight() / 2, new Paint(Paint.FILTER_BITMAP_FLAG));
+ bitmap.recycle();
return scaledBitmap;
}

MODIFY
www/Map.js
www/Map.js
@@ -819,6 +819,7 @@ Map.prototype.addMarker = function(markerOptions, callback) {
markerOptions.opacity = parseFloat("" + markerOptions.opacity, 10) || 1;
markerOptions.disableAutoPan = markerOptions.disableAutoPan === true;
markerOptions.useHtmlInfoWnd = !(markerOptions.infoWindow === undefined);
+ markerOptions.noCache = markerOptions.noCache === true; //experimental
if ("styles" in markerOptions) {
markerOptions.styles = typeof markerOptions.styles === "object" ? markerOptions.styles : {};

update: clear memory more aggressively

add: the noCache property for marker
	This property works for only http(s)://
	* this property is experimental