Search code examples
codenameone

Codename One "out of memory" when using Object-C native interface (HEIC to JPEG conversion)


Since I'm implementing a custom gallery for Android and iOS, I have to access directly to the gallery files stored in the FileSystemStorage through native interfaces.

The basic idea is to retrieve the file list through a native interface, and then make a cross-platform GUI in Codename One. This works on Android, I had to make the thumbs generation (in the Codename One side, not in the native interface side) as fast as possible and the overall result is quite acceptable.

On iOS, I have an additional issue, that is the HEIC image file format, that needs to be converted in JPEG to become usable in Codename One. Basically, I get the file list through the code in this question (I'm waiting for an answer...), then I have to convert each HEIC file to a temporary JPEG file, but my HEICtoJPEG native interface makes the app crashing after few images with an "out of memory" Xcode message...

I suspect that the problematic code is the following, maybe the UIImage* image and/or the NSData* mediaData are never released:

#import "myapp_utilities_HEICtoJPEGNativeImpl.h"

@implementation myapp_utilities_HEICtoJPEGNativeImpl

-(NSData*)heicToJpeg:(NSData*)param{
    UIImage* image = [UIImage imageWithData:param];
    NSData* mediaData = UIImageJPEGRepresentation(image, 0.9);
    return mediaData;
}

-(BOOL)isSupported{
    return YES;
}

@end

This is the Java native interface:

import com.codename1.system.NativeInterface;

/**
 * @deprecated
 */
public interface HEICtoJPEGNative extends NativeInterface {
        
    public byte[] heicToJpeg(byte[] heicInput);
}

and this the Java public API:

import com.codename1.io.FileSystemStorage;
import com.codename1.io.Util;
import com.codename1.system.NativeLookup;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public class HEICtoJPEG {
    private static HEICtoJPEGNative nativeInterface = NativeLookup.create(HEICtoJPEGNative.class);
    
    /**
     * Public API to convert an HEIC file to a new JPEG file (placed in /heic)
     * @param heicFile in the FileSystemStorage
     * @return a new file (with unique name)
     */
    public static String convertToJPEG(String heicFile) throws IOException {
        if (nativeInterface != null && nativeInterface.isSupported()) {
            
            // It ensures that the directory exists.
            FileSystemStorage fss = FileSystemStorage.getInstance();
            String heicDir = fss.getAppHomePath() + "/heic";
            if (!fss.isDirectory(heicDir)) {
                fss.mkdir(heicDir);
            }
            
            ByteArrayOutputStream outHeic = new ByteArrayOutputStream();
            InputStream inHeic = fss.openInputStream(heicFile);
            Util.copy(inHeic, outHeic);
            byte[] heicData = outHeic.toByteArray();
            
            byte[] jpegData = nativeInterface.heicToJpeg(heicData);
            
            String jpegFile = heicDir + "/" + DeviceUtilities.getUniqueId() + ".jpg";
            OutputStream outJpeg = fss.openOutputStream(jpegFile);
            ByteArrayInputStream inJpeg = new ByteArrayInputStream(jpegData);
            Util.copy(inJpeg, outJpeg);
            
            return jpegFile;
            
        } else {
            return null;
        }
    }
}

Since the Android counterpart works, I hope that the rest of my custom gallery code is fine and that this out-of-memory issue is inside code I posted here.

I hope you can indicate me a working solution. Thank you


Solution

  • There was a memory leak in the way that the iOS port invoked native interface methods which received or returned primitive arrays (byte[], int[], etc..).

    I have just committed a fix for this (native interface invocations are now wrapped in an autorelease pool) which will be available on the build server next Friday (October 9, 2020).

    EDIT: (Friday October 2, 2020)

    This fix has been deployed to the build server already so it you should be able to build it again immediately and see if it fixes your issue.