我写了这段代码。
public static Slider downloadStorageFile(String url, OnComplete<Integer> percentageCallback, OnComplete<String> filesavedCallback) {
final String filename = getNewStorageFilename();
ConnectionRequest cr = Rest.get(url).fetchAsBytes(response -> CN.callSerially(() -> filesavedCallback.completed(filename)));
cr.setDestinationStorage(filename);
Slider slider = new Slider();
slider.addDataChangedListener((int type, int index) -> {
CN.callSerially(() -> percentageCallback.completed(slider.getProgress()));
});
sliderBridgeMap.put(cr, slider);
SliderBridge.bindProgress(cr, slider);
return slider;
}
基本上,你可以猜到,它异步下载一个文件,它提供两个回调,将在EDT上运行,并立即返回一个Slider,跟踪下载进度。
在Android上,即使应用程序处于后台,下载也会继续并结束。而在iOS上,应用必须一直处于活动状态和前台。
在iOS上,是否可以在应用进入后台时也能完成下载?
或者,我可以得到 ConnectionRequest.retry()
, 当应用程序在iOS上返回到前台时,我的网络错误处理程序会自动调用它。在下载到达的地方重新开始,而不是从头开始?
更好的是,我可以同时得到第1点和第2点吗?
这在Android上也是有问题的,因为当系统受到限制时,操作系统可能会突然杀死你正在进行的下载。例如,在开发者工具中,只要立即打开杀死活动,当你最小化应用的那一刻,就会看到你的下载死亡。
大多数设备不会这样做,但如果设备在省电模式下运行,可能会发生这种情况。
解决的办法是进入后台时使用后台获取。问题是,这并不能提供常规的前台下载所能得到的那种漂亮的用户界面,所以你需要选择使用哪一种。
请在这里查看该类的JavaDoc。https:/www.codenameone.comjavadoccomcodename1backgroundBackgroundFetch.html
还有关于这个问题的略显过时的博文。https:/www.codenameone.comblogbackground-fetch.html
我想设计一个大大小小的文件下载系统,这个系统要非常健壮,即抗网络错误,并且能够在网络条件允许的情况下尽快恢复下载,并且对用户完全透明。
但是,这与跨平台的方式所带来的某些限制相冲突。我不确定后台获取是下载重度多媒体内容最合适的解决方案,我也不知道后台获取中的网络错误是否被一般的错误处理程序所捕获。也许我会研究一下。
我阐述了一个有利有弊的解决方案,它规避了这个问题。
优点:总能让你完成下载,即使非常大的下载量(比如100MB),即使连接不稳定(网络错误),即使应用暂时进入后台,也能完成下载。
缺点:由于我的想法是基于将下载分成小部分,所以这种方式会造成很多GET请求,略微拖慢下载速度,造成的流量比正常需要的多。
前提1:在全局网络错误处理中,必须有一个自动的 .retry()
如这段代码。区分服务器端错误和连接问题。
前提条件2:对于 getFileSizeWithoutDownload(String url)
落实情况和 Wrapper
实现,见。https:/stackoverflow.coma621303711277576。
解释:代码应该是不言自明的。基本上,它每次下载512kbyte,然后与输出合并。如果发生网络错误(如果在iOS上应用进入后台),已经下载的所有内容不会丢失(最多只有最后一个512kbyte的片段丢失)。当每个片段被下载时,ConnectionRequest会调用自己,改变部分下载的头。在下载的过程中,连接请求(ConnectionRequest)会调用自己,改变部分下载的头。filesavedCallback
只有当所有下载完成后才会调用回调。
码。
public static void downloadToStorage(String url, OnComplete<Integer> percentageCallback, OnComplete<String> filesavedCallback) throws IOException {
final String output = getNewStorageFilename(); // get a new random available Storage file name
final long fileSize = getFileSizeWithoutDownload(url); // total expected download size
final int splittingSize = 512 * 1024; // 512 kbyte, size of each small download
Wrapper<Integer> downloadedTotalBytes = new Wrapper<>(0);
OutputStream out = Storage.getInstance().createOutputStream(output); // leave it open to append partial downloads
Wrapper<Integer> completedPartialDownload = new Wrapper<>(0);
ConnectionRequest cr = new GZConnectionRequest();
cr.setUrl(url);
cr.setPost(false);
if (fileSize > splittingSize) {
// Which byte should the download start from?
cr.addRequestHeader("Range", "bytes=0-" + splittingSize);
cr.setDestinationStorage("split-" + output);
} else {
Util.cleanup(out);
cr.setDestinationStorage(output);
}
cr.addResponseListener(a -> {
CN.callSerially(() -> {
try {
// We append the just saved partial download to the output, if it exists
if (Storage.getInstance().exists("split-" + output)) {
InputStream in = Storage.getInstance().createInputStream("split-" + output);
Util.copyNoClose(in, out, 8192);
Util.cleanup(in);
Storage.getInstance().deleteStorageFile("split-" + output);
completedPartialDownload.set(completedPartialDownload.get() + 1);
}
// Is the download finished?
if (fileSize <= 0 || completedPartialDownload.get() * splittingSize >= fileSize || downloadedTotalBytes.get() >= fileSize) {
// yes, download finished
Util.cleanup(out);
filesavedCallback.completed(output);
} else {
// no, it's not finished, we repeat the request after updating the "Range" header
cr.addRequestHeader("Range", "bytes=" + downloadedTotalBytes.get() + "-" + (downloadedTotalBytes.get() + splittingSize));
NetworkManager.getInstance().addToQueue(cr);
}
} catch (IOException ex) {
Log.p("Error in appending splitted file to output file", Log.ERROR);
Log.e(ex);
Server.sendLogAsync();
}
});
});
NetworkManager.getInstance().addToQueue(cr);
NetworkManager.getInstance().addProgressListener((NetworkEvent evt) -> {
if (cr == evt.getConnectionRequest() && fileSize > 0) {
downloadedTotalBytes.set(completedPartialDownload.get() * splittingSize + evt.getSentReceived());
// the following casting to long is necessary when the file is bigger than 21MB, otherwise the result of the calculation is wrong
percentageCallback.completed((int) ((long) downloadedTotalBytes.get() * 100 / fileSize));
}
});
}
我在模拟器中尝试了这个解决方案,在Android和iOS上,在不同的网络条件下,下载量为100MB,偶尔在后台移动应用程序(或让它自动运行)。在所有情况下,应用都能成功完成下载。但是,当应用在后台时,Android和iOS的差异仍然存在。
我希望这对你有用。如果有人想进一步完善这段代码,可以再加一个答案:)