Large file download on mobile
Hi. I'm tearing my hair out trying to figure this one out…
I'm trying to download a 4 meg(ish) file to local (mobile) storage from a remote server. The following code works fine for smaller files, but with larger ones I get an outOfMemory exception. This is because Ti saves the data to memory before committing it to the file… which is no good for me.
My code follows (cut down, but you get the idea)…
var videoFile = Titanium.Filesystem.getFile(localFilename);
var httpClient = Ti.Network.createHTTPClient();
httpClient.onreadystatechange = function(){
if(httpClient.readyState == 4){
videoFile.write(httpClient.responseData);
}
};
httpClient.open("GET", theRemoteFileURL);
httpClient.send(null);
Like i say… the above works fine for small files, but gets messy for larger files.
I found the following post… http://developer.appcelerator.com/question/26001/copying-a-file-from-a-server-to-your-harddisk
but this is for Ti Desktop… i tried the code anyway and just got nothing but trouble. It would seem that Filesystem.getFileStream isnt implemented in Mobile, nor is HTTPClient.receive. (I would have been able to see this without testing if I at all trusted the documentation to be in any way reliable, btw(!)).
I was hoping to maybe set an interval to Append to the file every X seconds and then maybe flush the HttpClient (to clear memory) - but from what I've seen I can't append to a file, nor flush the stream.
Does anybody have any info that can help?
Also, is there anywhere else I can get better documentation… I use KitchenSink when possible but the demos sometimes arent extensive enough.
Cheers.
9 Answers
-
I'm still encountering this problem in Android, so I dug through the Titanium 1.5.1 source for the HTTPClient, and came up with a (not entirely elegant) work-around.
Here's an example onload function for the HTTPClient:
xmlHttpObj = Ti.Network.createHTTPClient(); xmlHttpObj.onload = onSuccess; ... filename = "filename.txt"; ... function onSuccess() { Ti.API.info("Successfully downloaded file"); Ti.API.info("Saving file"); // This method results in Out of Memory Error for large files //var f = Ti.Filesystem.getFile( // Ti.Filesystem.getExternalStorageDirectory(), // filename); //f.write(xmlHttpObj.responseData); // Use the fact that TiHTTPClient internally saves to a // file to move that temporary file where we want it. // responseData is a TiBlob object, which can get data from // multiple sources, including files (TYPE == 1). if (xmlHttpObj.responseData.type == 1) { var f = Ti.Filesystem.getFile( xmlHttpObj.responseData.nativePath); var dest = Ti.Filesystem.getFile( Ti.Filesystem.getExternalStorageDirectory(), filename); if (dest.exists) dest.deleteFile(); f.move(dest.nativePath); } else { var f = Ti.Filesystem.getFile( Ti.Filesystem.getExternalStorageDirectory(), filename); f.write(xmlHttpObj.responseData); } // Do not access xmlHttpObj.responseData after this point, // since the file backing it might have been moved in the // previous step. Ti.API.info("Successfully saved file"); }
There needs to be at least twice the file size of space left on the device, since Titanium's file.move() implementation copies the file before removing the original.
And just to note, I've only done some simple testing with this code. I can't guarantee that it will always work, although I don't know why it wouldn't!
-
https://appcelerator.lighthouseapp.com/projects/32238/tickets/1019-httpclient-crashes-app-with-substantial-downloads
it's a known ticket apparently so you have to wait for it to be resolved or give up Titanium mobile
-
I am stuck on this as well. Being able to download large file to iphone is a very important feature of my app.
I am now reading the module guide to write a module to handle this. So sad to go back to that damn objective-c again. -
I've solved this problem in other apps (not titanium, but similar issue) by implementing a REST service that allows me to get chunks of the file via service calls. Then I make consecutive calls to the service and append the data on each chunk received. It can be time consuming to do, but you get other benefits as well like the ability to continue downloads where they left off if a connection is dropped, etc.
-
finally my prototype module for downloading large file is working. I still need to add more whistles and bells to it.
it will download file to disk, report progress, and resume download
I will publish it once I get it done. -
Jason,
This sounds wonderful. Which platform(s) are you targeting with your module? Is it still possible to run the project in the simulator while the module is named in the project's tiapp.xml file?
-
It looks like there's a fix in 1.5.0 for this:
https://github.com/appcelerator/titanium_mobile/commit/05ab7461dab9a38200a27d082389d2885be951f8
-
Hey Did anyone ever solve this issue? Jason, have you posted your module anywhere for us to play with? Thanks
-
Checkout the file download example in the latest Kitchen sink. You use a xhr.file = (path); after open and before send and it will automatically stream the file to that path, this will stop you running out of memory.