3

I am trying to write a 20MB blob to file on an Android device. This code works fine for 4-5MB or so, but crashes for larger files.

tp = cordova.file.dataDirectory; // for Android $cordovaFile.writeFile(tp, "temp-file.gif", blob,true);

is there a way to handle larger blobs? thanks. I already have android:largeHeap="true" in the manifest.

the logcat error:

E/JavaBinder(  809): !!! FAILED BINDER TRANSACTION !!!
I/art     (  809): Background sticky concurrent mark sweep GC freed 62202(4MB) AllocSpace objects, 96(3MB) LOS objects, 20% free, 25MB/31MB, paused 20.488ms total 176.243ms
E/JavaBinder(  809): !!! FAILED BINDER TRANSACTION !!!
W/ActivityManager(  809): Exception thrown during pause
W/ActivityManager(  809): android.os.TransactionTooLargeException
W/ActivityManager(  809):   at android.os.BinderProxy.transactNative(Native Method)
W/ActivityManager(  809):   at android.os.BinderProxy.transact(Binder.java:496)
W/ActivityManager(  809):   at android.app.ApplicationThreadProxy.schedulePauseActivity(ApplicationThreadNative.java:711)
W/ActivityManager(  809):   at com.android.server.am.ActivityStack.startPausingLocked(ActivityStack.java:829)
W/ActivityManager(  809):   at com.android.server.am.ActivityStack.finishActivityLocked(ActivityStack.java:2749)
W/ActivityManager(  809):   at com.android.server.am.ActivityStack.finishTopRunningActivityLocked(ActivityStack.java:2606)
W/ActivityManager(  809):   at com.android.server.am.ActivityStackSupervisor.finishTopRunningActivityLocked(ActivityStackSupervisor.java:2544)
W/ActivityManager(  809):   at com.android.server.am.ActivityManagerService.handleAppCrashLocked(ActivityManagerService.java:11721)
W/ActivityManager(  809):   at com.android.server.am.ActivityManagerService.makeAppCrashingLocked(ActivityManagerService.java:11618)
W/ActivityManager(  809):   at com.android.server.am.ActivityManagerService.crashApplication(ActivityManagerService.java:12330)
W/ActivityManager(  809):   at com.android.server.am.ActivityManagerService.handleApplicationCrashInner(ActivityManagerService.java:11819)
W/ActivityManager(  809):   at com.android.server.am.NativeCrashListener$NativeCrashReporter.run(NativeCrashListener.java:86)
user1361529
  • 2,667
  • 29
  • 61
  • Where is this particular blob coming from, and is there a reason why you need to use the File API to save it? (If I were downloading a file, I'd use File Transfer instead.) – Kerri Shotts Dec 02 '16 at 04:13
  • Yup - I'd love to use file transfer. The data is a blob which is created in memory, combining multiple files from the server (the blob is an animated gif). I tried converting the blob to a blob: url ( URL.createObjectURL(blob);) but fileTransfer doesn't like it. – user1361529 Dec 02 '16 at 13:09
  • Any reason the server can't do the combination and send you the final result (which you could use with FileTransfer)? Or is this app something like a GIF creator that needs to build the GIF locally? – Kerri Shotts Dec 02 '16 at 21:07
  • Yes, the latter. The server is 3rd party. GIF creation is a local app feature – user1361529 Dec 02 '16 at 21:42

2 Answers2

2

The solution, after experimentation is to break up the write into chunks - that seems to work for 20-30MB files that I've tested so far.

    function writeFile2( path, file, blob, isAppend)
    {
        var csize = 4 * 1024 * 1024; // 4MB
        var d = $q.defer();
        NVRDataModel.debug ("Inside writeFile2 with blob size="+blob.size);

        // nothing more to write, so all good?
        if (!blob.size)
        {
            NVRDataModel.debug ("writeFile2 all done");
            d.resolve(true);
            return $q.resolve(true); 
        }


        if (!isAppend)
           {
               // return the delegated promise, even if it fails
               return $cordovaFile.writeFile(path, file, blob.slice(0,csize), true)
                   .then (function (succ) {
                       return writeFile2(path,file,blob.slice(csize),true);
                   });
           }
           else
           {
               // return the delegated promise, even if it fails
               return $cordovaFile.writeExistingFile(path, file, blob.slice(0,csize))
                   .then (function (succ) {
                       return writeFile2(path,file,blob.slice(csize),true);
                   });   
           }


    }

Credit: @Andre Werlang helped me with this here: https://stackoverflow.com/a/40935231/1361529

Community
  • 1
  • 1
user1361529
  • 2,667
  • 29
  • 61
  • Hey user I am using your code it is working fine in some mobiles downloading any size file and in some mobiles it is showing 500 internal server eror cannot find file://random name .file is being downloaded from server but writing to file system has some issue in function any idea how to deal ? – arun G Jan 04 '18 at 12:53
  • @arun Same issue here. Did u find any workaround to this? happens when I wrap my ArrayBuffer downloaded file into a Blob apparently a request is made to something like blob:file:///0ba95f93-ac5d-4110-9dbc-c4661756cdfa which fails with a 500 error saying "DOMException: requested file could not be read, typically due to permission problems...." – Juri Jan 24 '18 at 10:32
  • @Juri yesterday Issue is still present .I am using transfer file in cordova rather than write file . so it is working with transfer. – arun G Jan 25 '18 at 11:17
1

This is what I use and I have no limit or haven't reached it

function writeFile(__filename, __data){
        window.resolveLocalFileSystemURL(cordova.file.dataDirectory, function(dir){
            dir.getFile(__filename, {create:true}, function(file){            
                file.createWriter(function(fileWriter){
                    var blob = new Blob([__data], {type:'text/plain'});
                    fileWriter.write(blob);

                });                     
            });
        });   
    };
Eric
  • 9,870
  • 14
  • 66
  • 102