Skip to content

Commit

Permalink
fix(android): improve memory handling of TiBlob image methods (#11412)
Browse files Browse the repository at this point in the history
*  implement v8 gc functionality
* attempt to reclaim memory when limited
* optimize TiBlob image manipulation methods for memory
* update to androidx

Fixes TIMOB-27695
  • Loading branch information
garymathews authored and sgtcoolguy committed Mar 24, 2020
1 parent 411f2de commit 26982f3
Show file tree
Hide file tree
Showing 5 changed files with 171 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@
import org.appcelerator.kroll.KrollExceptionHandler.ExceptionMessage;
import org.appcelerator.kroll.common.Log;
import org.appcelerator.kroll.common.TiMessenger;
import org.appcelerator.kroll.runtime.v8.V8Runtime;
import org.appcelerator.kroll.util.KrollAssetHelper;

import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import androidx.annotation.Nullable;

/**
* The common Javascript runtime instance that Titanium interacts with.
Expand Down Expand Up @@ -109,18 +111,45 @@ public static void init(Context context, KrollRuntime runtime)
runtime.doInit();
}

@Nullable
public static KrollRuntime getInstance()
{
// TODO: Prevent this method from requiring `null` checks.
return instance;
}

/**
* Suggest V8 garbage collection during idle.
*/
public static void suggestGC()
{
if (instance != null) {
instance.setGCFlag();
}
}

/**
* Force V8 garbage collection.
*/
public static void softGC()
{
// Force V8 garbage collection.
if (instance != null) {
instance.forceGC();
}
}

/**
* Force both V8 and JVM garbage collection.
*/
public static void hardGC()
{
softGC();

// Force JVM garbage collection.
System.gc();
}

public static boolean isInitialized()
{
if (instance != null) {
Expand Down Expand Up @@ -441,6 +470,11 @@ public void setGCFlag()
// No-op V8 should override.
}

public void forceGC()
{
// No-op V8 should override.
}

public State getRuntimeState()
{
return runtimeState;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,12 @@ public static boolean isEmulator()
|| "google_sdk".equals(Build.PRODUCT);
}

@Override
public void forceGC()
{
nativeIdle();
}

@Override
public void initRuntime()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,13 @@ public void onLowMemory()
// Release all the cached images
TiBlobLruCache.getInstance().evictAll();
TiImageLruCache.getInstance().evictAll();

// Perform hard garbage collection to reclaim memory.
KrollRuntime instance = KrollRuntime.getInstance();
if (instance != null) {
instance.hardGC();
}

super.onLowMemory();
}

Expand All @@ -436,6 +443,12 @@ public void onTrimMemory(int level)
// Release all the cached images
TiBlobLruCache.getInstance().evictAll();
TiImageLruCache.getInstance().evictAll();

// Perform soft garbage collection to reclaim memory.
KrollRuntime instance = KrollRuntime.getInstance();
if (instance != null) {
instance.softGC();
}
}
super.onTrimMemory(level);
}
Expand Down
70 changes: 49 additions & 21 deletions android/titanium/src/java/org/appcelerator/titanium/TiBlob.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import org.appcelerator.kroll.KrollDict;
import org.appcelerator.kroll.KrollProxy;
import org.appcelerator.kroll.KrollRuntime;
import org.appcelerator.kroll.annotations.Kroll;
import org.appcelerator.kroll.common.Log;
import org.appcelerator.kroll.util.KrollStreamHelper;
Expand Down Expand Up @@ -688,14 +689,18 @@ public TiBlob imageAsCropped(Object params)
} catch (OutOfMemoryError e) {
TiBlobLruCache.getInstance().evictAll();
Log.e(TAG, "Unable to crop the image. Not enough memory: " + e.getMessage(), e);
return null;
} catch (IllegalArgumentException e) {
Log.e(TAG, "Unable to crop the image. Illegal Argument: " + e.getMessage(), e);
return null;
} catch (Throwable t) {
Log.e(TAG, "Unable to crop the image. Unknown exception: " + t.getMessage(), t);
return null;
} finally {
// Perform soft garbage collection to reclaim memory.
KrollRuntime instance = KrollRuntime.getInstance();
if (instance != null) {
instance.softGC();
}
}
return null;
}

@Kroll.method
Expand Down Expand Up @@ -786,14 +791,18 @@ public TiBlob imageAsResized(Number width, Number height)
} catch (OutOfMemoryError e) {
TiBlobLruCache.getInstance().evictAll();
Log.e(TAG, "Unable to resize the image. Not enough memory: " + e.getMessage(), e);
return null;
} catch (IllegalArgumentException e) {
Log.e(TAG, "Unable to resize the image. Illegal Argument: " + e.getMessage(), e);
return null;
} catch (Throwable t) {
Log.e(TAG, "Unable to resize the image. Unknown exception: " + t.getMessage(), t);
return null;
} finally {
// Perform soft garbage collection to reclaim memory.
KrollRuntime instance = KrollRuntime.getInstance();
if (instance != null) {
instance.softGC();
}
}
return null;
}

@Kroll.method
Expand Down Expand Up @@ -826,20 +835,23 @@ public TiBlob imageAsCompressed(Number compressionQuality)
} catch (OutOfMemoryError e) {
TiBlobLruCache.getInstance().evictAll();
Log.e(TAG, "Unable to get the thumbnail image. Not enough memory: " + e.getMessage(), e);
return null;
} catch (IllegalArgumentException e) {
Log.e(TAG, "Unable to get the thumbnail image. Illegal Argument: " + e.getMessage(), e);
return null;
} catch (Throwable t) {
Log.e(TAG, "Unable to get the thumbnail image. Unknown exception: " + t.getMessage(), t);
return null;
} finally {
// [MOD-309] Free up memory to work around issue in Android
if (img != null) {
img.recycle();
img = null;
}
bos = null;

// Perform soft garbage collection to reclaim memory.
KrollRuntime instance = KrollRuntime.getInstance();
if (instance != null) {
instance.softGC();
}
}

return result;
Expand Down Expand Up @@ -923,14 +935,18 @@ public TiBlob imageAsThumbnail(Number size, @Kroll.argument(optional = true) Num
} catch (OutOfMemoryError e) {
TiBlobLruCache.getInstance().evictAll();
Log.e(TAG, "Unable to get the thumbnail image. Not enough memory: " + e.getMessage(), e);
return null;
} catch (IllegalArgumentException e) {
Log.e(TAG, "Unable to get the thumbnail image. Illegal Argument: " + e.getMessage(), e);
return null;
} catch (Throwable t) {
Log.e(TAG, "Unable to get the thumbnail image. Unknown exception: " + t.getMessage(), t);
return null;
} finally {
// Perform soft garbage collection to reclaim memory.
KrollRuntime instance = KrollRuntime.getInstance();
if (instance != null) {
instance.softGC();
}
}
return null;
}

@Kroll.method
Expand Down Expand Up @@ -975,14 +991,18 @@ public TiBlob imageWithAlpha()
} catch (OutOfMemoryError e) {
TiBlobLruCache.getInstance().evictAll();
Log.e(TAG, "Unable to get the image with alpha. Not enough memory: " + e.getMessage(), e);
return null;
} catch (IllegalArgumentException e) {
Log.e(TAG, "Unable to get the image with alpha. Illegal Argument: " + e.getMessage(), e);
return null;
} catch (Throwable t) {
Log.e(TAG, "Unable to get the image with alpha. Unknown exception: " + t.getMessage(), t);
return null;
} finally {
// Perform soft garbage collection to reclaim memory.
KrollRuntime instance = KrollRuntime.getInstance();
if (instance != null) {
instance.softGC();
}
}
return null;
}

@Kroll.method
Expand Down Expand Up @@ -1035,14 +1055,18 @@ public TiBlob imageWithRoundedCorner(Number cornerRadius, @Kroll.argument(option
} catch (OutOfMemoryError e) {
TiBlobLruCache.getInstance().evictAll();
Log.e(TAG, "Unable to get the image with rounded corner. Not enough memory: " + e.getMessage(), e);
return null;
} catch (IllegalArgumentException e) {
Log.e(TAG, "Unable to get the image with rounded corner. Illegal Argument: " + e.getMessage(), e);
return null;
} catch (Throwable t) {
Log.e(TAG, "Unable to get the image with rounded corner. Unknown exception: " + t.getMessage(), t);
return null;
} finally {
// Perform soft garbage collection to reclaim memory.
KrollRuntime instance = KrollRuntime.getInstance();
if (instance != null) {
instance.softGC();
}
}
return null;
}

@Kroll.method
Expand Down Expand Up @@ -1090,14 +1114,18 @@ public TiBlob imageWithTransparentBorder(Number size)
} catch (OutOfMemoryError e) {
TiBlobLruCache.getInstance().evictAll();
Log.e(TAG, "Unable to get the image with transparent border. Not enough memory: " + e.getMessage(), e);
return null;
} catch (IllegalArgumentException e) {
Log.e(TAG, "Unable to get the image with transparent border. Illegal Argument: " + e.getMessage(), e);
return null;
} catch (Throwable t) {
Log.e(TAG, "Unable to get the image with transparent border. Unknown exception: " + t.getMessage(), t);
return null;
} finally {
// Perform soft garbage collection to reclaim memory.
KrollRuntime instance = KrollRuntime.getInstance();
if (instance != null) {
instance.softGC();
}
}
return null;
}

@Override
Expand Down
69 changes: 69 additions & 0 deletions tests/Resources/ti.blob.addontest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* Appcelerator Titanium Mobile
* Copyright (c) 2020 by Axway, Inc. All Rights Reserved.
* Licensed under the terms of the Apache Public License
* Please see the LICENSE included with this distribution for details.
*/
/* eslint-env mocha */
/* eslint no-unused-expressions: "off" */
'use strict';
var should = require('./utilities/assertions');

describe('Titanium.Blob', function () {
var win;

afterEach(function (done) {
if (win) {
// If `win` is already closed, we're done.
let t = setTimeout(function () {
if (win) {
win = null;
done();
}
}, 3000);

win.addEventListener('close', function listener () {
clearTimeout(t);

if (win) {
win.removeEventListener('close', listener);
}
win = null;
done();
});
win.close();
} else {
win = null;
done();
}
});

it('resize very large image', function (finish) {

win = Ti.UI.createWindow({ backgroundColor: 'gray' });
const img = Ti.UI.createImageView();

// Obtain large image blob. (8000px, 8000px)
let blob = Ti.Filesystem.getFile('large.jpg').read();
should(blob).be.an.Object;

win.addEventListener('open', () => {

// Keep re-sizing the image down by 10%
for (let i = 0; i < 10; i++) {

// De-reference original blob so it can be freed.
blob = blob.imageAsResized(blob.width / 1.1, blob.height / 1.1);
should(blob).be.an.Object;
}

// Display re-sized image.
img.image = blob;

finish();
});

win.add(img);
win.open();
});
});

0 comments on commit 26982f3

Please sign in to comment.