Problem Description
downloadcompleted returns e.data as a blob in physical memory!! If the file size is large, for example, over 1GB, it could cause low memory crash. Plus, using file.write(e.data) in this case takes too much time to run in the background. iOS only allows 30 seconds. Chances are the app will be either crashed or suspended before the file is saved.
I suggest: upon downloadcompleted move the file to a temp directory and return the path instead of a blob.
TESTING ENVIRONMENT
CLI version 3.3.0,
Titanium SDK version 3.3.0.GA
iOS7
CODE SAMPLE
app.js
// Require in the urlSession module
var urlSession = require("com.appcelerator.urlSession");
var session;
// App UI
var win = Ti.UI.createWindow({backgroundColor:"white"});
var progress = Ti.UI.createProgressBar({
width: 200,
height: 50,
min: 0,
max: 1,
value: 0,
style: Ti.UI.iPhone.ProgressBarStyle.PLAIN,
top: 10,
message: 'Downloading image URL',
font: { fontSize: 12, fontWeight: 'bold'},
color: '#888'
});
win.add(progress);
var imageView = Ti.UI.createImageView({
top:150,
height:300,
width:200
});
win.add(imageView);
var button = Ti.UI.createButton({
title:'Download Image (url)',
height:40,
width:200,
top:70
});
button.addEventListener('click', function()
{
// Create a session configuration
// The string parameter is an arbitrary string used to identify the session in events
var sessionConfig = urlSession.createURLSessionBackgroundConfiguration("com.appcelerator.test");
// Create a session
session = urlSession.createURLSession(sessionConfig);
// Create a background download task to get the asset with the URL
urlSession.backgroundDownloadTaskWithURL(session,"https://raw.github.com/appcelerator-developer-relations/KitchenSink/master/Resources/images/dog@2x~iphone.jpg");
progress.show();
});
win.add(button);
win.open();
// Monitor this event to receive updates on the progress of the download
Ti.App.iOS.addEventListener("downloadprogress", function(e) {
// Update the progress indicator
progress.value = (e.totalBytesWritten/e.totalBytesExpectedToWrite);
});
// Monitor this event to know when the download completes
Ti.App.iOS.addEventListener("downloadcompleted", function(e) {
Ti.API.info("download completed " + JSON.stringify(e));
// Update the image
imageView.image = e.data;
// Invalidate the session and cancel current session tasks
urlSession.invalidateAndCancel(session);
// Notify the user the download is complete if the application is in the background
Ti.App.iOS.scheduleLocalNotification({
alertBody:"Download complete!",
date:new Date().getTime()
});
progress.hide();
});
// Monitor this event to know when all session tasks have completed
Ti.App.iOS.addEventListener('sessioncompleted', function(e) {
Ti.API.info("sessioncompleted " + JSON.stringify(e));
if (e.success) {
alert("Downloads completed successfully.");
}
});
STEP TO REPRODUCE
- Create a new Project
- Double-click the Project's "tiapp.xml" file to open it in the Overview tab.
- In the Modules section, click the Add button (green plus sign) to open the Mobile Modules dialog.
- Locate and select "com.appcelerator.urlSession". This module is part of the Titanium SDK since Release 3.2.0
- Click OK.
- Copy the code segment above to the project "app.js" file
- Run the Project
OBSERVED RESULT
If the URL is changed to download a large file, progress bar would not response and the app will crash after some time.
Thanks for your report. The Platform team will set the priority on this issue.
When will you set a priority on this issue? This is important to me. If you can release your URLSession module's source code, I can implement this myself. Thanks.
[~shawnlan] the module is here. We would welcome a PR to fix this issue: https://github.com/appcelerator-modules/ti.urlsession
I don't see any source file other than this one: https://github.com/appcelerator-modules/ti.urlsession/blob/master/ios/js/app.hjs That's the only source file for this module?
Yes, since this was built using Hyperloop. It may be relatively difficult to figure out how to update it ATM, but there are instructions on the repo.
Actually, to fix this issue, I think we have to mainly deal with the iOS application-level events: Titanium.App.iOS.backgroundtransfer Titanium.App.iOS.downloadprogress Titanium.App.iOS.downloadcompleted - especially this one Titanium.App.iOS.sessioncompleted Titanium.App.iOS.sessioneventscompleted they are in the Titanium SDK, right? I'll wait for you guys to fix this. Thanks.
Here it is: https://github.com/appcelerator/titanium_mobile/blob/afc36d4f421424dc1ef6d618201f43616bb7857b/iphone/Classes/TiApp.m (line 612) Instead of loading the file into a blob, simply move the file into a temporary location and return the path. Should be simple.
URL Session Module fixed to be able to download file sizes larger than 1Gb with no low memory crash. Not required to return file location. If necessary, it is possible to obtain file via blob.getFile(); updated test code:
*Note: the link downloaded is 1GB in size in the test code above. Replace with commented out lines of code linked to smaller files if necessary.*
PR Here: https://github.com/appcelerator/titanium_mobile/pull/6527
Your solution doesn't require returning file location, but it also doesn't load the blob into memory. Right? If it loads blob into memory like before, then there would be memory-low issue.
[~shawnlan], yes, my solution doesn't load blob into memory. The fix is instead of allocating memory, Save a copy of the file in the temporary directory, and the returned blob actually just reference that file.
Hi [~cng], does this change require a doc update?
Hi [~tstatler], No doc update required since the parameters remain the same. However it'll be good to put a note somewhere about downloading big files it's advisable to make a copy as described in the above sample code, since the returned blob references a file location that's only temporary for that app instance.
Verified fix on: Mac OSX 10.10.1 Appcelerator Studio, build: 3.4.1.201410281743 Titanium SDK build: 3.6.0.v20150116070927 Titanium CLI, build: 3.4.1 Alloy: 1.5.1 Xcode 6.1.1 iPhone 6 Plus (8.1.1), iPad Air 2 (8.2b4) Using the code in Kiats comment, ran on both devices and downloaded each of the files, the app no longer crashes with low memory. Observed memory in Xcode Instruments during the download as well. Closing ticket
Will this fix be available with SDK 3.6 or SDK 4.0?
[~shawnlan] 4.0, as the fix version says.
Today I found a problem again with large file download. To re-produce, download a large file > 1GB, press "Home" to make the app go to the background. Once the download finishes, only "backgroundtransfer" is fired. The other events are not: sessioneventscompleted, downloadcompleted, etc. Without downloadcompleted, the file can't be saved. Without sessioneventscompleted, the app won't call endBackgroundHandler, and it will run over 30 seconds, eventually being terminated by the system. With a medium size download (2-300MB) in the background, calling endBackgroundHandler when sessioneventscompleted fired throws the following error, and the callback is failed to complete.