I just implemented a HTTP Downloader. It has 4 button to do some operation: start, pause, resume, cancel download. When pressing pause or cancel button, I use AsyncTask.cancel() to cancel the download AsyncTask and disable all UI widget temporarily. When AsyncTask onCancelled triggers, I enable the UI widget.
Here's a question: I found inputStram.close() somehow is very slow sometimes. Cause it is called before onCancelled(), it will block UI for a while. I found some article discussed about it but none of their answer really work. It confuses me so bad...
Related questions:
Sometimes HttpURLConnection.getInputStream executes too slowly
InputStream won't close, or takes forever to
Below is my code in AsyncTask. Wish someone give some help. I will appreciate that a lot.
#Override
protected String doInBackground(URL... urls) {
InputStream input = null;
OutputStream output = null;
HttpURLConnection httpURLConnection = null;
try {
URL url = urls[0];
httpURLConnection = (HttpURLConnection) url.openConnection();
httpURLConnection.setConnectTimeout(CONNECT_TIMEOUT);
httpURLConnection.setInstanceFollowRedirects(true);
httpURLConnection.setRequestProperty("Range", "bytes=" + mDownloadedBytes + "-");
httpURLConnection.setUseCaches(false);
httpURLConnection.setReadTimeout(5000);
httpURLConnection.setRequestProperty("Connection", "close");
System.setProperty("http.keepAlive", "false");
httpURLConnection.connect();
if (!(httpURLConnection.getResponseCode() == HttpURLConnection.HTTP_OK // 200
|| httpURLConnection.getResponseCode() == HttpURLConnection.HTTP_CREATED // 201
|| httpURLConnection.getResponseCode() == HttpURLConnection.HTTP_ACCEPTED // 202
|| httpURLConnection.getResponseCode() == HttpURLConnection.HTTP_NOT_AUTHORITATIVE // 203
|| httpURLConnection.getResponseCode() == HttpURLConnection.HTTP_NO_CONTENT // 204
|| httpURLConnection.getResponseCode() == HttpURLConnection.HTTP_RESET //205
|| httpURLConnection.getResponseCode() == HttpURLConnection.HTTP_PARTIAL)) { //206
return "Download fail, server returned HTTP " + httpURLConnection.getResponseCode()
+ " " + httpURLConnection.getResponseMessage();
}
String fileName = getFileName();
String fileExtension = getFileExtension(httpURLConnection);
if(mDownloadFile == null) {
mDownloadFile = getDownloadFile(fileName, fileExtension);
output = new FileOutputStream(mDownloadFile);
} else {
if(mDownloadFile.exists()) {
output = new FileOutputStream(mDownloadFile, true);
} else {
cancel(true);
return null;
}
}
int fileLength = httpURLConnection.getContentLength();
if(sTotalFileLength == -1 && fileLength != -1){
sTotalFileLength = fileLength;
}
// update download state depending on fileLength
updateUI(fileLength);
// check free space if server respond this value
if(fileLength != -1){
if(!isFreeSpaceEnough(fileLength)){
return "Download fail, you don't have enough free space to save the file";
}
}
// download the file
input = httpURLConnection.getInputStream();
byte data[] = new byte[BUFFER_SIZE];
int count;
while ((count = input.read(data)) != -1) {
if (isCancelled()) {
return null;
}
mDownloadedBytes += count;
if(sTotalFileLength > 0){
int progress = (int)(mDownloadedBytes * 100 / sTotalFileLength);
publishProgress(progress);
}
try {
output.write(data, 0, count);
} catch (IOException e){
return "No enough free space to save file!";
} catch (IndexOutOfBoundsException e){
return "Write to file error";
}
}
// write a record to Download DB after download complete
// just record image in DownloadDb
if(!fileExtension.equals("") && isInImageExtensionList(fileExtension)) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
mDbRow = new DownloadDbRow(dateFormat.format(new Date()), fileName+"."+fileExtension);
mDb.insert(mDbRow);
}
// Tell system to scan for media file change
mMediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
Uri contentUri = Uri.fromFile(mDownloadFile);
mMediaScanIntent.setData(contentUri);
} catch (Exception e) {
return e.toString();
} finally {
try {
if (output != null) {
output.flush();
output.close();
}
/////////////////////
// //
// problems here!! //
// //
/////////////////////
if (input != null) {
input.close();
}
} catch (IOException e) {
return e.toString();
}
if (httpURLConnection != null) {
httpURLConnection.disconnect();
}
}
return DOWNLOAD_SUCCESSFULLY;
}
Related
protected void doDownload(final String urlLink, final String fileName) {
Thread dx = new Thread() {
public void run() {
File root = android.os.Environment.getExternalStorageDirectory();
File dir = new File (root.getAbsolutePath() + "/Content2/");
if(dir.exists()==false) {
dir.mkdirs();
}
//Save the path as a string value
try
{
URL url = new URL(urlLink);
Log.i("FILE_NAME", "File name is "+imageFile);
Log.i("FILE_URLLINK", "File URL is "+url);
URLConnection connection = url.openConnection();
connection.connect();
// this will be useful so that you can show a typical 0-100% progress bar
int fileLength = connection.getContentLength();
// download the file
InputStream input = new BufferedInputStream(url.openStream());
OutputStream output = new FileOutputStream(dir+"/"+imageFile);
byte data[] = new byte[1024];
long total = 0;
int count;
while ((count = input.read(data)) != -1) {
total += count;
output.write(data, 0, count);
}
output.flush();
output.close();
input.close();
}
catch (Exception e)
{
e.printStackTrace();
Log.i("ERROR ON DOWNLOADING FILES", "ERROR IS" +e);
}
}
};
dx.start();
}
through this, I cannot download the file from the server.
How to solve this problem?
First of all you should use Async-Task.
Here is how you can do this
final DownloadTask downloadTask = new DownloadTask(YourActivity.this);
downloadTask.execute("the url to the file you want to download");
// usually, subclasses of AsyncTask are declared inside the activity class.
// that way, you can easily modify the UI thread from here
private class DownloadTask extends AsyncTask<String, Integer, String> {
private Context context;
public DownloadTask(Context context) {
this.context = context;
}
#Override
protected String doInBackground(String... sUrl) {
InputStream input = null;
OutputStream output = null;
HttpURLConnection connection = null;
try {
URL url = new URL(sUrl[0]);
connection = (HttpURLConnection) url.openConnection();
connection.connect();
// expect HTTP 200 OK, so we don't mistakenly save error report
// instead of the file
if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
return "Server returned HTTP " + connection.getResponseCode()
+ " " + connection.getResponseMessage();
}
// this will be useful to display download percentage
// might be -1: server did not report the length
int fileLength = connection.getContentLength();
// download the file
input = connection.getInputStream();
output = new FileOutputStream("/sdcard/file_name.extension");
byte data[] = new byte[4096];
long total = 0;
int count;
while ((count = input.read(data)) != -1) {
// allow canceling with back button
if (isCancelled()) {
input.close();
return null;
}
total += count;
}
} catch (Exception e) {
return e.toString();
} finally {
try {
if (output != null)
output.close();
if (input != null)
input.close();
} catch (IOException ignored) {
}
if (connection != null)
connection.disconnect();
}
return null;
}
#Override
protected void onPostExecute(String result) {
if (result != null)
Toast.makeText(context,"Download error: "+result, Toast.LENGTH_LONG).show();
else
Toast.makeText(context,"File downloaded", Toast.LENGTH_SHORT).show();
}
public class PreviewDownload extends AsyncTask<String, Void, String> {
public static final String TAG = "PreviewDownload";
public String inputPath = null;
public String outputFolder = null;
public IRIssue issue = null;
#Override
protected String doInBackground(String... parms) {
InputStream input = null;
OutputStream output = null;
HttpURLConnection connection = null;
issue = Broker.model.issueDataStore.getIRIssue(parms[0]);
outputFolder = IRConstant.issueFolder(issue.year, issue.month, issue.day, issue.pubKey);
try {
inputPath = IRConstant.downloadFile(issue.year, issue.month, issue.day, issue.pubKey, "preview", "0");
URL url = new URL(inputPath);
Log.d (TAG,"input: " + inputPath);
connection = (HttpURLConnection) url.openConnection();
connection.connect();
if (connection.getResponseCode() != HttpURLConnection.HTTP_OK)
return null;
// return "Server returned HTTP " + connection.getResponseCode()
// + " " + connection.getResponseMessage();
// download the file
input = connection.getInputStream();
output = new FileOutputStream(outputFolder + "/preview.zip");
Log.d (TAG,"output: " + output);
byte data[] = new byte[1024];
int count;
while ((count = input.read(data)) != -1) {
output.write(data, 0, count);
}
} catch (Exception e) {
// return e.toString();
return null;
} finally {
try {
if (output != null)
output.close();
if (input != null)
input.close();
} catch (IOException ignored) {
}
if (connection != null)
connection.disconnect();
}
return outputFolder;
}
#Override
protected void onPostExecute(String outputFolder) {
// TODO Auto-generated method stub
super.onPostExecute(outputFolder);
if (outputFolder != null) {
File zipFile = new File (outputFolder + "/preview.zip");
if (Utils.unzip(outputFolder,outputFolder + "/preview.zip" )) {
zipFile.delete();
issue.isThumbDownloaded = 1;
} else {
issue.isThumbDownloaded = 0;
}
} else {
Toast.makeText(Broker.launcherActivity.getBaseContext(), R.string.wordCantDownload, Toast.LENGTH_LONG).show();
issue.isThumbDownloaded = 0;
}
issue.updateProgress(issue.progress);
}
}
Here is the downloader I implemented , the problem is , when the network lost, the output become null and show error message, however, if I would like to retry two times before showing error message, are there any way to do this? If I perfer not to pass in an object instead of string ,is it not recommended? thanks
What prevents you from re-instanciating and re-executing a "Downloader" from your catch blocks in case of errors ?
You could use a single common shared object between dowloader instances to count the attempts, or better, pass a parameter to each of them. In the catch block, you would then retry if you didn't reach the limit, and increase the value passed to a new downloader... Something recursive.
int expectedLength = connection.getContentLength();
can you compare with the expectedLength & downloaded length and retry?
I have an AsyncTask to download files one by one, and I make it as a queue, when it's running on android 2.x, good. In android 4.0+ it stop working. Here I passed a ProgressBar to AsyncTask, so it will update the loading progress bar, and indicate where it is.
The strange part is the progress bar will go 100% very quick not match the real size of file. And the length of file output in logcat also wrong...
All tasks will execute serially so it won't hurt the parallel limitation above SDK 11. I guess the problem might be inside the download part, just don't know where it is.
public function download ()
{
.....
if (task != null) {
task.cancel (true);
}
task = new OnlineDownloadTask (progress);
task.execute (url, path);
.....
}
class OnlineDownloadTask extends AsyncTask<String, String, String> {
private final WeakReference<OfflineQueueIndicatorView> progressbarReference;
public OnlineDownloadTask(OfflineQueueIndicatorView progress) {
progressbarReference = new WeakReference<OfflineQueueIndicatorView>(
progress);
}
#Override
protected void onPreExecute() {
super.onPreExecute();
}
#Override
protected String doInBackground(String... aurl) {
int count;
try {
URL url = new URL(aurl[0]);
HttpURLConnection conn = (HttpURLConnection) url
.openConnection();
conn.setConnectTimeout(10000);
conn.setReadTimeout(10000);
conn.setRequestMethod("GET");
conn.setAllowUserInteraction(false);
conn.setDoInput(true);
conn.setDoOutput(true);
conn.connect();
int lengthOfFile = conn.getContentLength();
android.util.Log.v("offline.downloader", lengthOfFile + "");
InputStream input = new BufferedInputStream(url.openStream());
OutputStream output = new FileOutputStream(aurl[1]);
try {
byte data[] = new byte[1024];
long total = 0;
while ((count = input.read(data)) != -1) {
total += count;
publishProgress(""
+ (int) ((total * 100) / lengthOfFile));
if (stopoffline) {
android.util.Log.v("file.downloader", "stopped");
break;
}
output.write(data, 0, count);
}
if (stopoffline) {
output.flush();
output.close();
input.close();
conn.disconnect();
File file = new File(aurl[1]);
if (file.exists()) {
file.delete();
}
stopoffline = false;
return null;
} else {
output.flush();
output.close();
input.close();
conn.disconnect();
if (DiskCache.getInstance().offlineDirectoryExist(
DiskCache.getInstance().offlineCurrentFolder)) {
} else {
if (!DiskCache
.getInstance()
.makeOfflineFolder(
DiskCache.getInstance().offlineCurrentFolder)) {
return null;
}
}
android.util.Log.v("offline",
DiskCache.getInstance().offlineCurrentFolder);
unzip(aurl[1],
DiskCache.getInstance().offlineCurrentFolder);
DiskCache.getInstance().deleteFile(aurl[1]);
return "succ";
}
} finally {
if (output != null) {
output.flush();
output.close();
}
if (input != null) {
input.close();
}
if (conn != null) {
conn.disconnect();
}
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
protected void onProgressUpdate(String... progress) {
try {
if (progressbarReference != null) {
OfflineQueueIndicatorView p = progressbarReference.get();
if (p != null) {
int i = Integer.parseInt(progress[0]);
p.setProgress(i);
}
}
}
catch (Exception e) {
e.printStackTrace();
}
}
#Override
protected void onPostExecute(String ret) {
try {
if (progressbarReference != null) {
if (ret != null) {
queue.get(currentId).put("state", "complete");
} else {
if (queue != null) {
if (currentId != null) {
queue.get(currentId).put("state", "failed");
}
}
}
}
}
catch (Exception e) {
e.printStackTrace();
}
download();
}
}
It's possible that the newer version of HttpUrlConnection in Android 4.0 is causing the server to use Chunked Transfer Encoding, which is supported in HTTP/1.1. The Android 2.x version may not have supported CTE. When sending a response with CTE (e.g., during file/video streaming), the server will not return a content length. As such, you may want to show an indeterminate ProgressBar when the content length is not available.
I finally found what's wrong after I remove conn.setDoOutput(true), it working well on both android 2.x and 4.x emulator, I think also acj has the point, sometimes Chunked Transfer Encoding is the reason too.
I didn't find any question like this here.
Yesterday I finally got Gingerbread 2.3.4 on my Nexus One. When I opened my application (basically loads an XML Feed into a ListView) again, it got stuck while downloading.
It seems that InputStream stream; -> stream.read(buffer); doesn't return -1 any more, when it's finished.
The Code ist nearly the same from here Download Progress
Here's my code:
public InputStream getInputStreamFromURL(String urlString, DownloadProgressCallback callback)
throws IOException, IllegalArgumentException
{
InputStream in = null;
conn = (HttpURLConnection) new URL(urlString).openConnection();
fileSize = conn.getContentLength();
out = new ByteArrayOutputStream((int) fileSize);
conn.connect();
stream = conn.getInputStream();
// loop with step 1kb
while (status == DOWNLOADING) {
byte buffer[];
if (fileSize - downloaded > MAX_BUFFER_SIZE) {
buffer = new byte[MAX_BUFFER_SIZE];
} else {
buffer = new byte[(int) (fileSize - downloaded)];
}
int read = stream.read(buffer);
if (read == -1) {
break;
}
// writing to buffer
out.write(buffer, 0, read);
downloaded += read;
// update progress bar
callback.progressUpdate((int) ((downloaded / fileSize) * 100));
}// end of while
if (status == DOWNLOADING) {
status = COMPLETE;
}
in= (InputStream) new ByteArrayInputStream(out.toByteArray());
// end of class DownloadImageTask()
return in;
}
The problem basically is that when the download finishes, stream.read(buffer) returns 0 instead of -1. When I change
if (read == -1) {
break;
}
to 0 or
if (fileSize == downloaded) {
break;
}
I get ParseExceptions (ExpatParser) on my MainActivity.
On 2.2 it runs really perfect.
I cleared the app cache and tried a few other things already, but I'm really stuck now.
I hope that someone can help me. :)
UPDATE:
That's awesome, you're the man, Guillaume. :)
Thank you very much, that saved my evening! :)
Your Code for my needs here:
public InputStream getStreamFromURL(String urlString, DownloadProgressCallback callback){
// initialize some timeouts
HttpParams httpParameters = new BasicHttpParams();
HttpConnectionParams.setConnectionTimeout(httpParameters,3000);
// create the connection
URL url;
try {
url = new URL(urlString);
URLConnection connection = url.openConnection();
HttpURLConnection httpConnection = (HttpURLConnection) connection;
// connection accepted
if(httpConnection.getResponseCode() == HttpURLConnection.HTTP_OK) {
int size = connection.getContentLength();
int index = 0;
int current = 0;
InputStream input = connection.getInputStream();
BufferedInputStream buffer = new BufferedInputStream(input);
byte[] bBuffer = new byte[1024];
out = new ByteArrayOutputStream((int) size);
while((current = buffer.read(bBuffer)) != -1) {
out.write(bBuffer, 0, current);
index += current;
callback.progressUpdate((index/size)*100);
}
out.close();
}
} catch (MalformedURLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return (InputStream) new ByteArrayInputStream(out.toByteArray());
}
This code work on my 2.3.4 Nexus One :
try {
// initialize some timeouts
HttpParams httpParameters = new BasicHttpParams();
HttpConnectionParams.setConnectionTimeout(httpParameters, 3000);
// create the connection
URL url = new URL(toDownload);
URLConnection connection = url.openConnection();
HttpURLConnection httpConnection = (HttpURLConnection) connection;
// connection accepted
if (httpConnection.getResponseCode() == HttpURLConnection.HTTP_OK) {
try {
file = new File(destination);
// delete the file if exists
file.delete();
} catch (Exception e) {
// nothing
}
int size = connection.getContentLength();
int index = 0;
int current = 0;
try {
file = new File(destination);
file.delete();
FileOutputStream output = new FileOutputStream(file);
InputStream input = connection.getInputStream();
BufferedInputStream buffer = new BufferedInputStream(input);
byte[] bBuffer = new byte[10240];
while ((current = buffer.read(bBuffer)) != -1) {
if (isCancelled()) {
file.delete();
break;
}
try {
output.write(bBuffer, 0, current);
} catch (IOException e) {
e.printStackTrace();
}
index += current;
publishProgress(index / (size / 100));
}
output.close();
} catch (SecurityException se) {
se.printStackTrace();
return 1;
} catch (FileNotFoundException e) {
e.printStackTrace();
return 1;
} catch (Exception e) {
e.printStackTrace();
return 2;
}
return 0;
}
// connection refused
return 2;
} catch (IOException e) {
return 2;
}
My code (reproduced below), connects to a url and downloads the file to disk on android. All standard stuff. When I try using this code on a file on S3 accessed via a subdomain on our server mapped to a bucket (e.g. foo.example.com => bucket called foo.example.com), it often fails. Turns out (using the handy curl command..
"curl -v -L -X GET http://foo.example.com/f/a.txt")
.. that there's a redirect going on here.
The file download works ok, as HttpURLConnection will follow redirects by default, but the calls that require the header infomation (getContentLength, getHeaderFieldDate("Last-Modified", 0 ) etc) are returns the headers from the 307 redirect, and not the actual file thats downloaded.
Anyone know how to get around this?
Thanks
File local = null;
try {
Log.i(TAG, "Downloading file " + source);
conn = (HttpURLConnection) new URL(source).openConnection();
fileSize = conn.getContentLength(); // ** THIS IS WRONG ON REDIRECTED FILES
out = new BufferedOutputStream(new FileOutputStream(destination, false), 8 * 1024);
conn.connect();
stream = new BufferedInputStream(conn.getInputStream(), 8 * 1024);
byte[] buffer = new byte[MAX_BUFFER_SIZE];
while (true) {
int read = stream.read(buffer);
if (read == -1) {
break;
}
// writing to buffer
out.write(buffer, 0, read);
downloaded += read;
publishProgress(downloaded, fileSize);
if (isCancelled()) {
return "The user cancelled the download";
}
}
} catch (Exception e) {
String msg = "Failed to download file " + source + ". " + e.getMessage();
Log.e(TAG, msg );
return msg;
} finally {
if (out != null) {
try {
out.flush();
out.close();
} catch (IOException e) {
Log.e(TAG, "Failed to close file " + destination);
e.printStackTrace();
}
}
if (stream != null) {
try {
stream.close();
} catch (IOException e) {
Log.e(TAG, "Failed to close file " + destination);
e.printStackTrace();
}
} else {
long dateLong = conn.getHeaderFieldDate("Last-Modified", 0 ); // ** THIS IS WRONG ON REDIRECTED FILES
Date d = new Date(dateLong);
local.setLastModified(dateLong);
}
have you tried to set redirects to false and try to manually capture the redirected URL and associated header fields with it?
For example something like this:
URL url = new URL(url);
HttpURLConnection ucon = (HttpURLConnection) url.openConnection();
ucon.setInstanceFollowRedirects(false);
URL secondURL = new URL(ucon.getHeaderField("Location"));
URLConnection conn = secondURL.openConnection();
This example captures the redirected URL, but you could easily tweak this to try for any other header field. Does this help?
Consider using httpclient-android. You should get the right headers after redirection with this:
HttpClient client = HttpClientBuilder.create().build();
HttpGet request = new HttpGet(YOUR_URL);
HttpResponse response = client.execute(request);
response.getAllHeaders()
Note that android comes with an older version of httpclient, but it has the same problem as you reported. You actually need to import "httpclient-android" for a newer version.
Note: The code snippet is for v4.3. For other versions, look for how to do it in regular apache HttpClient.
Well, I've been playing a bit and this code, which uses the HttpClient library rather than HttpUrlConnection works fine. The headers it returns are those of the final redirect hop.
At least on the devices I've tested it on.
HttpClient client = null;
HttpGet get = null;
HttpResponse response = null;
try {
client = new DefaultHttpClient();
get = new HttpGet(source);
response = client.execute(get);
Header contentSize = response.getFirstHeader("Content-Length");
if (contentSize != null) {
String value = contentSize.getValue();
fileSize = Long.parseLong(value);
}
if (fileSize == -1) {
Log.e(TAG, "Failed to read the content length for the file " + source);
}
Header lastModified = response.getFirstHeader("Last-Modified");
lastModifiedDate = null;
if (lastModified != null) {
lastModifiedDate = DateUtils.parseDate(lastModified.getValue());
}
if (lastModifiedDate == null) {
Log.e(TAG, "Failed to read the last modified date for the file " + source);
}
out = new BufferedOutputStream(new FileOutputStream(destination, false), 8 * 1024); // false means don't append
stream = new BufferedInputStream(response.getEntity().getContent(), 8 * 1024);
byte[] buffer = new byte[MAX_BUFFER_SIZE];
int count = 0;
while (true) {
int read = stream.read(buffer);
if (read == -1) {
break;
}
// writing to buffer
out.write(buffer, 0, read);
downloaded += read;
publishProgress(downloaded, fileSize);
if (isCancelled()) {
Log.w(TAG, "User Cancelled");
return; // NOTE that onPostExecute is not called here..
}
}// end of while
publishProgress(downloaded, fileSize);
} catch (Exception e) {
String msg = "Failed to download file " + source + ". " + e.getMessage();
Log.e(TAG, msg );
return msg;
} finally {
if (out != null) {
try {
out.flush();
out.close();
} catch (IOException e) {
Log.e(TAG, "Failed to close file " + destination);
e.printStackTrace();
}
}
if (stream != null) {
try {
stream.close();
} catch (IOException e) {
Log.e(TAG, "Failed to close file " + destination);
e.printStackTrace();
}
}
}