I am trying to write a simple application that gets updated. For this I need a simple function that can download a file and show the current progress in a ProgressDialog. I know how to do the ProgressDialog, but I'm not sure how to display the current progress and how to download the file in the first place.
There are many ways to download files. Following I will post most common ways; it is up to you to decide which method is better for your app.
Use AsyncTask and show the download progress in a dialog
=============================================================
This method will allow you to execute some background processes and update the UI at the same time (in this case, we'll update a progress bar).
Imports:
import android.os.PowerManager;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.FileOutputStream;
import java.net.HttpURLConnection;
This is an example code:
// declare the dialog as a member field of your activity
ProgressDialog mProgressDialog;
// instantiate it within the onCreate method
mProgressDialog = new ProgressDialog(YourActivity.this);
mProgressDialog.setMessage("A message");
mProgressDialog.setIndeterminate(true);
mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
mProgressDialog.setCancelable(true);
// execute this when the downloader must be fired
final DownloadTask downloadTask = new DownloadTask(YourActivity.this);
downloadTask.execute("the url to the file you want to download");
mProgressDialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
#Override
public void onCancel(DialogInterface dialog) {
downloadTask.cancel(true); //cancel the task
}
});
The AsyncTask will look like this:
// 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;
private PowerManager.WakeLock mWakeLock;
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;
// publishing the progress....
if (fileLength > 0) // only if total length is known
publishProgress((int) (total * 100 / fileLength));
output.write(data, 0, 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;
}
The method above (doInBackground) runs always on a background thread. You shouldn't do any UI tasks there. On the other hand, the onProgressUpdate and onPreExecute run on the UI thread, so there you can change the progress bar:
#Override
protected void onPreExecute() {
super.onPreExecute();
// take CPU lock to prevent CPU from going off if the user
// presses the power button during download
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
getClass().getName());
mWakeLock.acquire();
mProgressDialog.show();
}
#Override
protected void onProgressUpdate(Integer... progress) {
super.onProgressUpdate(progress);
// if we get here, length is known, now set indeterminate to false
mProgressDialog.setIndeterminate(false);
mProgressDialog.setMax(100);
mProgressDialog.setProgress(progress[0]);
}
#Override
protected void onPostExecute(String result) {
mWakeLock.release();
mProgressDialog.dismiss();
if (result != null)
Toast.makeText(context,"Download error: "+result, Toast.LENGTH_LONG).show();
else
Toast.makeText(context,"File downloaded", Toast.LENGTH_SHORT).show();
}
}
For this to run, you need the WAKE_LOCK permission.
<uses-permission android:name="android.permission.WAKE_LOCK" />
Download from Service
========================
The big question here is: how do I update my activity from a service?. In the next example we are going to use two classes you may not be aware of: ResultReceiver and IntentService. ResultReceiver is the one that will allow us to update our thread from a service; IntentService is a subclass of Service which spawns a thread to do background work from there (you should know that a Service runs actually in the same thread of your app; when you extends Service, you must manually spawn new threads to run CPU blocking operations).
Download service can look like this:
public class DownloadService extends IntentService {
public static final int UPDATE_PROGRESS = 8344;
public DownloadService() {
super("DownloadService");
}
#Override
protected void onHandleIntent(Intent intent) {
String urlToDownload = intent.getStringExtra("url");
ResultReceiver receiver = (ResultReceiver) intent.getParcelableExtra("receiver");
try {
//create url and connect
URL url = new URL(urlToDownload);
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(connection.getInputStream());
String path = "/sdcard/BarcodeScanner-debug.apk" ;
OutputStream output = new FileOutputStream(path);
byte data[] = new byte[1024];
long total = 0;
int count;
while ((count = input.read(data)) != -1) {
total += count;
// publishing the progress....
Bundle resultData = new Bundle();
resultData.putInt("progress" ,(int) (total * 100 / fileLength));
receiver.send(UPDATE_PROGRESS, resultData);
output.write(data, 0, count);
}
// close streams
output.flush();
output.close();
input.close();
} catch (IOException e) {
e.printStackTrace();
}
Bundle resultData = new Bundle();
resultData.putInt("progress" ,100);
receiver.send(UPDATE_PROGRESS, resultData);
}
}
Add the service to your manifest:
<service android:name=".DownloadService"/>
And the activity will look like this:
// initialize the progress dialog like in the first example
// this is how you fire the downloader
mProgressDialog.show();
Intent intent = new Intent(this, DownloadService.class);
intent.putExtra("url", "url of the file to download");
intent.putExtra("receiver", new DownloadReceiver(new Handler()));
startService(intent);
Here is were ResultReceiver comes to play:
private class DownloadReceiver extends ResultReceiver{
public DownloadReceiver(Handler handler) {
super(handler);
}
#Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
super.onReceiveResult(resultCode, resultData);
if (resultCode == DownloadService.UPDATE_PROGRESS) {
int progress = resultData.getInt("progress"); //get the progress
dialog.setProgress(progress);
if (progress == 100) {
dialog.dismiss();
}
}
}
}
2.1 Use Groundy library
Groundy is a library that basically helps you run pieces of code in a background service, and it is based on the ResultReceiver concept shown above. This library is deprecated at the moment. This is how the whole code would look like:
The activity where you are showing the dialog...
public class MainActivity extends Activity {
private ProgressDialog mProgressDialog;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
findViewById(R.id.btn_download).setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
String url = ((EditText) findViewById(R.id.edit_url)).getText().toString().trim();
Bundle extras = new Bundler().add(DownloadTask.PARAM_URL, url).build();
Groundy.create(DownloadExample.this, DownloadTask.class)
.receiver(mReceiver)
.params(extras)
.queue();
mProgressDialog = new ProgressDialog(MainActivity.this);
mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
mProgressDialog.setCancelable(false);
mProgressDialog.show();
}
});
}
private ResultReceiver mReceiver = new ResultReceiver(new Handler()) {
#Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
super.onReceiveResult(resultCode, resultData);
switch (resultCode) {
case Groundy.STATUS_PROGRESS:
mProgressDialog.setProgress(resultData.getInt(Groundy.KEY_PROGRESS));
break;
case Groundy.STATUS_FINISHED:
Toast.makeText(DownloadExample.this, R.string.file_downloaded, Toast.LENGTH_LONG);
mProgressDialog.dismiss();
break;
case Groundy.STATUS_ERROR:
Toast.makeText(DownloadExample.this, resultData.getString(Groundy.KEY_ERROR), Toast.LENGTH_LONG).show();
mProgressDialog.dismiss();
break;
}
}
};
}
A GroundyTask implementation used by Groundy to download the file and show the progress:
public class DownloadTask extends GroundyTask {
public static final String PARAM_URL = "com.groundy.sample.param.url";
#Override
protected boolean doInBackground() {
try {
String url = getParameters().getString(PARAM_URL);
File dest = new File(getContext().getFilesDir(), new File(url).getName());
DownloadUtils.downloadFile(getContext(), url, dest, DownloadUtils.getDownloadListenerForTask(this));
return true;
} catch (Exception pokemon) {
return false;
}
}
}
And just add this to the manifest:
<service android:name="com.codeslap.groundy.GroundyService"/>
It couldn't be easier I think. Just grab the latest jar from Github and you are ready to go. Keep in mind that Groundy's main purpose is to make calls to external REST apis in a background service and post results to the UI with easily. If you are doing something like that in your app, it could be really useful.
2.2 Use https://github.com/koush/ion
Use DownloadManager class (GingerBread and newer only)
=============================================================
GingerBread brought a new feature, DownloadManager, which allows you to download files easily and delegate the hard work of handling threads, streams, etc. to the system.
First, let's see a utility method:
/**
* #param context used to check the device version and DownloadManager information
* #return true if the download manager is available
*/
public static boolean isDownloadManagerAvailable(Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
return true;
}
return false;
}
Method's name explains it all. Once you are sure DownloadManager is available, you can do something like this:
String url = "url you want to download";
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));
request.setDescription("Some descrition");
request.setTitle("Some title");
// in order for this if to run, you must use the android 3.2 to compile your app
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
request.allowScanningByMediaScanner();
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
}
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, "name-of-the-file.ext");
// get download service and enqueue file
DownloadManager manager = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE);
manager.enqueue(request);
Download progress will be showing in the notification bar.
Final thoughts
First and second methods are just the tip of the iceberg. There are lots of things you have to keep in mind if you want your app to be robust. Here is a brief list:
You must check whether user has an internet connection available
Make sure you have the right permissions (INTERNET and WRITE_EXTERNAL_STORAGE); also ACCESS_NETWORK_STATE if you want to check internet availability.
Make sure the directory were you are going to download files exist and has write permissions.
If download is too big you may want to implement a way to resume the download if previous attempts failed.
Users will be grateful if you allow them to interrupt the download.
Unless you need detailed control of the download process, then consider using DownloadManager (3) because it already handles most of the items listed above.
But also consider that your needs may change. For example, DownloadManager does no response caching. It will blindly download the same big file multiple times. There's no easy way to fix it after the fact. Where if you start with a basic HttpURLConnection (1, 2), then all you need is to add an HttpResponseCache. So the initial effort of learning the basic, standard tools can be a good investment.
This class was deprecated in API level 26. ProgressDialog is a modal
dialog, which prevents the user from interacting with the app. Instead
of using this class, you should use a progress indicator like
ProgressBar, which can be embedded in your app's UI. Alternatively,
you can use a notification to inform the user of the task's progress. For more details Link
Don't forget to add permissions to your manifest file if you're gonna be downloading stuff from the internet!
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.helloandroid"
android:versionCode="1"
android:versionName="1.0">
<uses-sdk android:minSdkVersion="10" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission>
<uses-permission android:name="android.permission.INTERNET"></uses-permission>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"></uses-permission>
<uses-permission android:name="android.permission.READ_PHONE_STATE"></uses-permission>
<application
android:icon="#drawable/icon"
android:label="#string/app_name"
android:debuggable="true">
</application>
</manifest>
Yes the code above will work .But if you are updating your progressbar in onProgressUpdate of Asynctask and you press back button or finish your activity AsyncTask looses its track with your UI .And when you go back to your activity, even if download is running in background you will see no update on progressbar. So on OnResume() try to run a thread like runOnUIThread with a timer task that updates ur progressbar with values updating from the AsyncTask running background.
private void updateProgressBar(){
Runnable runnable = new updateProgress();
background = new Thread(runnable);
background.start();
}
public class updateProgress implements Runnable {
public void run() {
while(Thread.currentThread()==background)
//while (!Thread.currentThread().isInterrupted()) {
try {
Thread.sleep(1000);
Message msg = new Message();
progress = getProgressPercentage();
handler.sendMessage(msg);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} catch (Exception e) {
}
}
}
private Handler handler = new Handler(){
#Override
public void handleMessage(Message msg) {
progress.setProgress(msg.what);
}
};
Don't forget to Destroy the thread when ur activity is not visible.
private void destroyRunningThreads() {
if (background != null) {
background.interrupt();
background=null;
}
}
I'd recommend you to use my Project Netroid, It's based on Volley. I have added some features to it such as multi-events callback, file download management. This could be of some help.
I have modified AsyncTask class to handle creation of progressDialog at the same context .I think following code will be more reusable.
(it can be called from any activity just pass context,target File,dialog message)
public static class DownloadTask extends AsyncTask<String, Integer, String> {
private ProgressDialog mPDialog;
private Context mContext;
private PowerManager.WakeLock mWakeLock;
private File mTargetFile;
//Constructor parameters :
// #context (current Activity)
// #targetFile (File object to write,it will be overwritten if exist)
// #dialogMessage (message of the ProgresDialog)
public DownloadTask(Context context,File targetFile,String dialogMessage) {
this.mContext = context;
this.mTargetFile = targetFile;
mPDialog = new ProgressDialog(context);
mPDialog.setMessage(dialogMessage);
mPDialog.setIndeterminate(true);
mPDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
mPDialog.setCancelable(true);
// reference to instance to use inside listener
final DownloadTask me = this;
mPDialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
#Override
public void onCancel(DialogInterface dialog) {
me.cancel(true);
}
});
Log.i("DownloadTask","Constructor done");
}
#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();
}
Log.i("DownloadTask","Response " + connection.getResponseCode());
// 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(mTargetFile,false);
byte data[] = new byte[4096];
long total = 0;
int count;
while ((count = input.read(data)) != -1) {
// allow canceling with back button
if (isCancelled()) {
Log.i("DownloadTask","Cancelled");
input.close();
return null;
}
total += count;
// publishing the progress....
if (fileLength > 0) // only if total length is known
publishProgress((int) (total * 100 / fileLength));
output.write(data, 0, 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 onPreExecute() {
super.onPreExecute();
// take CPU lock to prevent CPU from going off if the user
// presses the power button during download
PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
getClass().getName());
mWakeLock.acquire();
mPDialog.show();
}
#Override
protected void onProgressUpdate(Integer... progress) {
super.onProgressUpdate(progress);
// if we get here, length is known, now set indeterminate to false
mPDialog.setIndeterminate(false);
mPDialog.setMax(100);
mPDialog.setProgress(progress[0]);
}
#Override
protected void onPostExecute(String result) {
Log.i("DownloadTask", "Work Done! PostExecute");
mWakeLock.release();
mPDialog.dismiss();
if (result != null)
Toast.makeText(mContext,"Download error: "+result, Toast.LENGTH_LONG).show();
else
Toast.makeText(mContext,"File Downloaded", Toast.LENGTH_SHORT).show();
}
}
Do not forget to replace "/sdcard..." by new File("/mnt/sdcard/...") otherwise you will get a FileNotFoundException
While I was starting to learn android development, I had learnt that ProgressDialog is the way to go. There is the setProgress method of ProgressDialog which can be invoked to update the progress level as the file gets downloaded.
The best I have seen in many apps is that they customize this progress dialog's attributes to give a better look and feel to the progress dialog than the stock version. Good to keeping the user engaged with some animation of like frog, elephant or cute cats/puppies. Any animation with in the progress dialog attracts users and they don't feel like being kept waiting for long.
We can use the coroutine and work manager for downloading files in kotlin.
Add a dependency in build.gradle
implementation "androidx.work:work-runtime-ktx:2.3.0-beta01"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.1"
WorkManager class
import android.content.Context
import android.os.Environment
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import androidx.work.workDataOf
import com.sa.chat.utils.Const.BASE_URL_IMAGE
import com.sa.chat.utils.Constants
import kotlinx.coroutines.delay
import java.io.BufferedInputStream
import java.io.File
import java.io.FileOutputStream
import java.net.URL
class DownloadMediaWorkManager(appContext: Context, workerParams: WorkerParameters)
: CoroutineWorker(appContext, workerParams) {
companion object {
const val WORK_TYPE = "WORK_TYPE"
const val WORK_IN_PROGRESS = "WORK_IN_PROGRESS"
const val WORK_PROGRESS_VALUE = "WORK_PROGRESS_VALUE"
}
override suspend fun doWork(): Result {
val imageUrl = inputData.getString(Constants.WORK_DATA_MEDIA_URL)
val imagePath = downloadMediaFromURL(imageUrl)
return if (!imagePath.isNullOrEmpty()) {
Result.success(workDataOf(Constants.WORK_DATA_MEDIA_URL to imagePath))
} else {
Result.failure()
}
}
private suspend fun downloadMediaFromURL(imageUrl: String?): String? {
val file = File(
getRootFile().path,
"IMG_${System.currentTimeMillis()}.jpeg"
)
val url = URL(BASE_URL_IMAGE + imageUrl)
val connection = url.openConnection()
connection.connect()
val lengthOfFile = connection.contentLength
// download the file
val input = BufferedInputStream(url.openStream(), 8192)
// Output stream
val output = FileOutputStream(file)
val data = ByteArray(1024)
var total: Long = 0
var last = 0
while (true) {
val count = input.read(data)
if (count == -1) break
total += count.toLong()
val progress = (total * 100 / lengthOfFile).toInt()
if (progress % 10 == 0) {
if (last != progress) {
setProgress(workDataOf(WORK_TYPE to WORK_IN_PROGRESS,
WORK_PROGRESS_VALUE to progress))
}
last = progress
delay(50)
}
output.write(data, 0, count)
}
output.flush()
output.close()
input.close()
return file.path
}
private fun getRootFile(): File {
val rootDir = File(Environment.getExternalStorageDirectory().absolutePath + "/AppName")
if (!rootDir.exists()) {
rootDir.mkdir()
}
val dir = File("$rootDir/${Constants.IMAGE_FOLDER}/")
if (!dir.exists()) {
dir.mkdir()
}
return File(dir.absolutePath)
}
}
Start downloading through work manager in activity class
private fun downloadImage(imagePath: String?, id: String) {
val data = workDataOf(WORK_DATA_MEDIA_URL to imagePath)
val downloadImageWorkManager = OneTimeWorkRequestBuilder<DownloadMediaWorkManager>()
.setInputData(data)
.addTag(id)
.build()
WorkManager.getInstance(this).enqueue(downloadImageWorkManager)
WorkManager.getInstance(this).getWorkInfoByIdLiveData(downloadImageWorkManager.id)
.observe(this, Observer { workInfo ->
if (workInfo != null) {
when {
workInfo.state == WorkInfo.State.SUCCEEDED -> {
progressBar?.visibility = View.GONE
ivDownload?.visibility = View.GONE
}
workInfo.state == WorkInfo.State.FAILED || workInfo.state == WorkInfo.State.CANCELLED || workInfo.state == WorkInfo.State.BLOCKED -> {
progressBar?.visibility = View.GONE
ivDownload?.visibility = View.VISIBLE
}
else -> {
if(workInfo.progress.getString(WORK_TYPE) == WORK_IN_PROGRESS){
val progress = workInfo.progress.getInt(WORK_PROGRESS_VALUE, 0)
progressBar?.visibility = View.VISIBLE
progressBar?.progress = progress
ivDownload?.visibility = View.GONE
}
}
}
}
})
}
My personal advice is to use Progress Dialog and build up before execution , or initiate at OnPreExecute() , publish progress often if you use horizontal style of progress bar of the progress dialog. The remaining part is to optimize the algorithm of doInBackground.
Use Android Query library, very cool indeed.You can change it to use ProgressDialog as you see in other examples, this one will show progress view from your layout and hide it after completion.
File target = new File(new File(Environment.getExternalStorageDirectory(), "ApplicationName"), "tmp.pdf");
new AQuery(this).progress(R.id.progress_view).download(_competition.qualificationScoreCardsPdf(), target, new AjaxCallback<File>() {
public void callback(String url, File file, AjaxStatus status) {
if (file != null) {
// do something with file
}
}
});
Permissions
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
Using HttpURLConnection
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import android.app.Activity;
import android.app.Dialog;
import android.os.Bundle;
import android.os.Environment;
import android.view.View;
import android.view.Window;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
public class DownloadFileUseHttpURLConnection extends Activity {
ProgressBar pb;
Dialog dialog;
int downloadedSize = 0;
int totalSize = 0;
TextView cur_val;
String dwnload_file_path =
"http://coderzheaven.com/sample_folder/sample_file.png";
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Button b = (Button) findViewById(R.id.b1);
b.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
showProgress(dwnload_file_path);
new Thread(new Runnable() {
public void run() {
downloadFile();
}
}).start();
}
});
}
void downloadFile(){
try {
URL url = new URL(dwnload_file_path);
HttpURLConnection urlConnection = (HttpURLConnection)
url.openConnection();
urlConnection.setRequestMethod("GET");
urlConnection.setDoOutput(true);
//connect
urlConnection.connect();
//set the path where we want to save the file
File SDCardRoot = Environment.getExternalStorageDirectory();
//create a new file, to save the downloaded file
File file = new File(SDCardRoot,"downloaded_file.png");
FileOutputStream fileOutput = new FileOutputStream(file);
//Stream used for reading the data from the internet
InputStream inputStream = urlConnection.getInputStream();
//this is the total size of the file which we are downloading
totalSize = urlConnection.getContentLength();
runOnUiThread(new Runnable() {
public void run() {
pb.setMax(totalSize);
}
});
//create a buffer...
byte[] buffer = new byte[1024];
int bufferLength = 0;
while ( (bufferLength = inputStream.read(buffer)) > 0 ) {
fileOutput.write(buffer, 0, bufferLength);
downloadedSize += bufferLength;
// update the progressbar //
runOnUiThread(new Runnable() {
public void run() {
pb.setProgress(downloadedSize);
float per = ((float)downloadedSize/totalSize) *
100;
cur_val.setText("Downloaded " + downloadedSize +
"KB / " + totalSize + "KB (" + (int)per + "%)" );
}
});
}
//close the output stream when complete //
fileOutput.close();
runOnUiThread(new Runnable() {
public void run() {
// pb.dismiss(); // if you want close it..
}
});
} catch (final MalformedURLException e) {
showError("Error : MalformedURLException " + e);
e.printStackTrace();
} catch (final IOException e) {
showError("Error : IOException " + e);
e.printStackTrace();
}
catch (final Exception e) {
showError("Error : Please check your internet connection " +
e);
}
}
void showError(final String err){
runOnUiThread(new Runnable() {
public void run() {
Toast.makeText(DownloadFileDemo1.this, err,
Toast.LENGTH_LONG).show();
}
});
}
void showProgress(String file_path){
dialog = new Dialog(DownloadFileDemo1.this);
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
dialog.setContentView(R.layout.myprogressdialog);
dialog.setTitle("Download Progress");
TextView text = (TextView) dialog.findViewById(R.id.tv1);
text.setText("Downloading file from ... " + file_path);
cur_val = (TextView) dialog.findViewById(R.id.cur_pg_tv);
cur_val.setText("Starting download...");
dialog.show();
pb = (ProgressBar)dialog.findViewById(R.id.progress_bar);
pb.setProgress(0);
pb.setProgressDrawable(
getResources().getDrawable(R.drawable.green_progress));
}
}
I am adding another answer for other solution I am using now because Android Query is so big and unmaintained to stay healthy. So i moved to this https://github.com/amitshekhariitbhu/Fast-Android-Networking.
AndroidNetworking.download(url,dirPath,fileName).build()
.setDownloadProgressListener(new DownloadProgressListener() {
public void onProgress(long bytesDownloaded, long totalBytes) {
bar.setMax((int) totalBytes);
bar.setProgress((int) bytesDownloaded);
}
}).startDownload(new DownloadListener() {
public void onDownloadComplete() {
...
}
public void onError(ANError error) {
...
}
});
You can observer the progress of the download manager using LiveData and coroutines, see the gist below
https://gist.github.com/FhdAlotaibi/678eb1f4fa94475daf74ac491874fc0e
data class DownloadItem(val bytesDownloadedSoFar: Long = -1, val totalSizeBytes: Long = -1, val status: Int)
class DownloadProgressLiveData(private val application: Application, private val requestId: Long) : LiveData<DownloadItem>(), CoroutineScope {
private val downloadManager by lazy {
application.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
}
private val job = Job()
override val coroutineContext: CoroutineContext
get() = Dispatchers.IO + job
override fun onActive() {
super.onActive()
launch {
while (isActive) {
val query = DownloadManager.Query().setFilterById(requestId)
val cursor = downloadManager.query(query)
if (cursor.moveToFirst()) {
val status = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS))
Timber.d("Status $status")
when (status) {
DownloadManager.STATUS_SUCCESSFUL,
DownloadManager.STATUS_PENDING,
DownloadManager.STATUS_FAILED,
DownloadManager.STATUS_PAUSED -> postValue(DownloadItem(status = status))
else -> {
val bytesDownloadedSoFar = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR))
val totalSizeBytes = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES))
postValue(DownloadItem(bytesDownloadedSoFar.toLong(), totalSizeBytes.toLong(), status))
}
}
if (status == DownloadManager.STATUS_SUCCESSFUL || status == DownloadManager.STATUS_FAILED)
cancel()
} else {
postValue(DownloadItem(status = DownloadManager.STATUS_FAILED))
cancel()
}
cursor.close()
delay(300)
}
}
}
override fun onInactive() {
super.onInactive()
job.cancel()
}
}
Important
AsyncTask is deprecated in Android 11.
For more information please checkout following posts
Android AsyncTask API deprecating in Android 11.What are the alternatives?
https://developer.android.com/reference/android/os/AsyncTask
Probably should move to concorency Framework as suggested by google
private class DownloadTask extends AsyncTask<String, Integer, String> {
private PowerManager.WakeLock mWakeLock;
String onlinePathBundle, onlinePathMusic, offlinePathBundle, offlinePathMusic;
CircleProgressBar progressBar;
RelativeLayout rl_progress;
ImageView btn_download;
TextView tv_progress;
public DownloadTask(String onlinePathBundle, String onlinePathMusic, String offlinePathBundle, String offlinePathMusic, CircleProgressBar progressBar, RelativeLayout rl_progress, ImageView btn_download,
TextView tv_progress) {
this.offlinePathBundle = offlinePathBundle;
this.offlinePathMusic = offlinePathMusic;
this.progressBar = progressBar;
this.rl_progress = rl_progress;
this.btn_download = btn_download;
this.tv_progress = tv_progress;
this.onlinePathBundle = onlinePathBundle;
this.onlinePathMusic = onlinePathMusic;
}
#Override
protected void onPreExecute() {
super.onPreExecute();
progressBar.setMax(100);
rl_progress.setVisibility(View.VISIBLE);
btn_download.setVisibility(View.GONE);
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, getClass().getName());
mWakeLock.acquire();
}
#Override
protected String doInBackground(String... sUrl) {
String firstResult = downTask(onlinePathBundle, 1, offlinePathBundle);
String seondResult = downTask(onlinePathMusic, 2, offlinePathMusic);
if (firstResult != null) {
Toast.makeText(SplashActivity.this, "Download error: " + firstResult, Toast.LENGTH_LONG).show();
return firstResult;
} else {
return seondResult;
}
}
#Override
protected void onProgressUpdate(Integer... progress) {
super.onProgressUpdate(progress);
progressBar.setProgress(progress[0]);
tv_progress.setText(progress[0] + "%");
}
#Override
protected void onPostExecute(String result) {
super.onPostExecute(result);
rl_progress.setVisibility(View.GONE);
btn_download.setVisibility(View.VISIBLE);
if (result != null) {
Toast.makeText(SplashActivity.this, "Download error: " + result, Toast.LENGTH_LONG).show();
} else {
rl_progress.setVisibility(View.GONE);
btn_download.setVisibility(View.GONE);
}
}
public String downTask(String onlineNewPath, int numberOfTask, String offlineNewPath) {
InputStream input = null;
OutputStream output = null;
HttpURLConnection connection = null;
try {
URL url = new URL(onlineNewPath);
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(offlineNewPath);
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;
// publishing the progress....
if (fileLength > 0) {// only if total length is known
if (numberOfTask == 1) {
publishProgress((int) (total * 50 / fileLength));
} else if (numberOfTask == 2) {
publishProgress(((int) (total * 50 / fileLength)) + 50);
}
}
output.write(data, 0, 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;
}
}
I have came across a Simple File Download library
Fetch and importantly it has Storage Access Framework, Content Provider and URI support. If anyone is still searching might found it helpful.
implementation "androidx.tonyodev.fetch2:xfetch2:3.1.6" //AndroidX
implementation "com.tonyodev.fetch2:fetch2:3.0.12" //Support lib
Required permissions in case your are not using app specific directory.
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
Internet permission
<uses-permission android:name="android.permission.INTERNET"/>
private Fetch fetch;
FetchConfiguration fetchConfiguration = new FetchConfiguration.Builder(this)
.setDownloadConcurrentLimit(3)//Concurrent Download limit
.build();
fetch = Fetch.Impl.getInstance(fetchConfiguration);
String url = "http:www.example.com/test.txt";//URL of file
String file = "/downloads/test.txt";//Path of file
final Request request = new Request(url, file);
request.setPriority(Priority.HIGH);
request.setNetworkType(NetworkType.ALL);//Preferred network type
request.addHeader("clientKey", "SD78DF93_3947&MVNGHE1WONG");//Auth header if any
fetch.enqueue(request, updatedRequest -> {
//Request was successfully enqueued for download.
}, error -> {
//An error occurred enqueuing the request.
});
}
Listen for update and progress
FetchListener fetchListener = new FetchListener() {
#Override
public void onQueued(#NotNull Download download, boolean waitingOnNetwork) {
if (request.getId() == download.getId()) {
showDownloadInList(download);
}
}
#Override
public void onCompleted(#NotNull Download download) {
}
#Override
public void onError(#NotNull Download download) {
Error error = download.getError();
}
#Override
public void onProgress(#NotNull Download download, long etaInMilliSeconds, long downloadedBytesPerSecond) {
if (request.getId() == download.getId()) {
updateDownload(download, etaInMilliSeconds);
}
int progress = download.getProgress();
}
#Override
public void onPaused(#NotNull Download download) {
}
#Override
public void onResumed(#NotNull Download download) {
}
#Override
public void onCancelled(#NotNull Download download) {
}
#Override
public void onRemoved(#NotNull Download download) {
}
#Override
public void onDeleted(#NotNull Download download) {
}
};
fetch.addListener(fetchListener);
//Remove listener when done.
fetch.removeListener(fetchListener);
This example code is taken from the owner page all credit goes to Tonyo Francis
im building an android application that recive images from arduino uno in order to show them continously as a video , i write an asyncTask that reads image and show it in image view , how can i invoke this method every seconed automatically .
here is my asyncTask
I made a button that invoke the async task , but how to make it invoked continously
class myAsyncTask extends AsyncTask<Void, Void, Void>
{
#Override
protected Void doInBackground(Void... params) {
// TODO Auto-generated method stub
mmInStream = tmpIn;
int byteNo;
try {
byteNo = mmInStream.read(buffer);
if (byteNo != -1) {
//ensure DATAMAXSIZE Byte is read.
int byteNo2 = byteNo;
int bufferSize = 7340;
int i = 0;
while(byteNo2 != bufferSize){
i++;
bufferSize = bufferSize - byteNo2;
byteNo2 = mmInStream.read(buffer,byteNo,bufferSize);
if(byteNo2 == -1){
break;
}
byteNo = byteNo+byteNo2;
}
}
}
catch (Exception e) {
// TODO: handle exception
}
return null;
}
protected void onPostExecute(Void result) {
super.onPostExecute(result);
bm1 = BitmapFactory.decodeByteArray(buffer, 0, buffer.length);
image.setImageBitmap(bm1);
}
}
If it's from a background thread, one possibility is to use an unbounded for loop. For example, suppose the AsyncTask currently does:
private class MyTask extends AsyncTask<T1, Void, T3>
{
protected T3 doInBackground(T1... value)
{
return longThing(value);
}
protected void onPostExecute(T3 result)
{
updateUI(result);
}
}
then rewrite it as something like:
private class MyTask extends AsyncTask<T1, T3, T3>
{
protected T3 doInBackground(T1... value)
{
for (;;)
{
T3 result = longThing(value);
publishProgress(result);
Thread.sleep(1000);
}
return null;
}
protected void onProgressUpdate(T3... progress)
{
updateUI(progress[0]);
}
}
Of course, you should have a check to break the loop (for example when the Activity is paused or destroyed).
Another option is to create a Handler instance and call postDelayed() repeatedly.
Handler h = new Handler();
h.postDelayed(r, DELAY_IN_MS);
Runnable r = new new Runnable() {
public void run() {
// Do your stuff here
h.postDelayed(this, DELAY_IN_MS);
}
}
In the following code I want don't save incomplete file and delete it, if download was cancelled (e.g. press back or close app).
How can I do it?
This is part of my code:
class DownloadFileFromURL extends AsyncTask<String, String, String> {
#Override
protected void onPreExecute() {
super.onPreExecute();
showDialog(progress_bar_type);
}
#Override
protected String doInBackground(String... f_url) {
int count;
try {
URL url = new URL(f_url[0]);
URLConnection conection = url.openConnection();
conection.connect();
int lenghtOfFile = conection.getContentLength();
InputStream input = new BufferedInputStream(url.openStream(), 8192);
OutputStream output = new FileOutputStream("/sdcard/EBKH/samat.mp3");
byte data[] = new byte[1024];
long total = 0;
while ((count = input.read(data)) != -1) {
total += count;
publishProgress(""+(int)((total*100)/lenghtOfFile));
output.write(data, 0, count);
}
output.flush();
output.close();
input.close();
} catch (Exception e) {
Log.e("Error: ", e.getMessage());
}
return null;
}
protected void onProgressUpdate(String... progress) {
pDialog.setProgress(Integer.parseInt(progress[0]));
}
#Override
protected void onPostExecute(String file_url) {
dismissDialog(progress_bar_type);
}
}
Note that, if all is well, an AsyncTask does not get cancelled when a user presses a back button. If I remember correctly, it also does not get cancelled automatically if the activity is destroyed, but only gets cancelled when cancel() is called on the task.
If you want to delete your file if the cancel was called, please don't add unnecessary logic, just override AsyncTask.onCancelled():
#Override
protected void onCancelled(Object Result) {
// If the background task was cancelled, delete the mp3
File file= new File("/sdcard/EBKH/samat.mp3");
file.delete();
}
#Override
protected void onCancelled() {
// Implement this one if you also support API levels lower than 11
}
You could add a isCanceled flag that you will check every time you write to the file :
while ((count = input.read(data)) != -1 && isCanceled)
Also add a :
catch (Exception e) {
Log.e("Error: ", e.getMessage());
}
finaly{
if(isCanceled)
{
// delete file code here
}
}
You will set this flag to true when you want/need to cancel.
set global Boolean flag boolean downloadComplete = false;and in onPostExecute change it to downloadComplete = true;
and overrideonDestroy()method.
then check flag inonDestroy()` whether file is download or not
if(!downloadComplete)
{
//then delete file
}
and when you cancel the download then onPostExecute() will not be called so your flag value remain false
Write below code
#Override
public void onBackPressed() {
super.onBackPressed();
if(DownloadFileFromURL!=null){
if(DownloadFileFromURL.getStatus()==Status.RUNNING||DownloadFileFromURL.getStatus()==Status.PENDING){
DownloadFileFromURL.cancel(true);
DownloadFileFromURL=null;
File file= new File("/sdcard/EBKH/samat.mp3");
file.delete();
}
}
}
I my application i am going to implement the Progress dialog till its getting loading. . .
The code is like:
copyFileFromAssetsToSDCard("Alone.mp4");
intent = new Intent(this,VideoPlayerController.class);
startActivity(intent);
So how to implement it ??
Thanks.
Code for the function is:
public void copyFileFromAssetsToSDCard(String fileFromAssets){
AssetManager is = this.getAssets();
InputStream fis;
try {
fis = is.open(fileFromAssets);
FileOutputStream fos;
fos = new FileOutputStream(new File(Environment.getExternalStorageDirectory(), fileFromAssets));
byte[] b = new byte[8];
int i;
while ((i = fis.read(b)) != -1) {
fos.write(b, 0, i);
}
fos.flush();
fos.close();
fis.close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
public class MainActivity extends Activity {
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
new progressTask().execute("");
}
public void copyFileFromAssetsToSDCard(String fileFromAssets){
AssetManager is = this.getAssets();
InputStream fis;
try {
fis = is.open(fileFromAssets);
FileOutputStream fos;
fos = new FileOutputStream(new File(Environment.getExternalStorageDirectory(), fileFromAssets));
byte[] b = new byte[8];
int i;
while ((i = fis.read(b)) != -1) {
fos.write(b, 0, i);
}
fos.flush();
fos.close();
fis.close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
private class progressTask extends AsyncTask<String, Void, Void> {
ProgressDialog pd = new ProgressDialog(MainActivity.this);
#Override
protected void onPreExecute() {
pd.show();
}
protected Long doInBackground(String... params) {
copyFileFromAssetsToSDCard("Alone.mp4");
}
protected void onPostExecute(Void result) {
pd.dismiss();
}
}
}
I have made a arrangement of your all tasks together. Try it. Hope it will help you out.
You can do like this,
Create an ArrayList<String> files = new ArrayList<String>(); and add all the files that you can to copy to SDCard like the below.
files.add("test.png");
files.add("icon.png");
new myAsyncTask().execute(files);
Then you can just use this ArrayList object to pass to AsynTask and get it done.
private class myAsyncTask extends AsyncTask<ArrayList<String>, Void, Void>
{
ArrayList<String> files;
ProgressDialog dialog;
#Override
protected void onPreExecute() {
dialog = ProgressDialog.show(mainact.this, "Loading...", "");
}
#Override
protected Void doInBackground(ArrayList<String>... params) {
files = params[0];
for (int i = 0; i < files.size(); i++) {
copyFileFromAssetsToSDCard(files.get(i));
}
return null;
}
#Override
protected void onPostExecute(Void result) {
dialog.dismiss();
}
}
try to move the copy code to a thread and show the dialog, and after thread ends, remove the dialog launch the new activity.
Something like this :
showDialog(10);//10 is the dialog id
new Thread(new Runnable(){
public void run(){
copyFileFromAssetsToSDCard("Alone.mp4");
handler.sendEmptyMessage(1);
}
}).start();
Make a handler to receive the message:
Handler handler=new Handler(){
void handleMessage(Message msg){
switch(msg.what){
case 1:
removeDialog(10);
intent = new Intent(this,VideoPlayerController.class);
startActivity(intent);
break;
}
};
};
try this code for Displaying Progress Dialog Box,
ProgressDialog progDailog = ProgressDialog.show(this,"Process ", "please wait....",false,true);
new Thread ( new Runnable()
{
public void run()
{
// Put Intent your code here
}
}
I think you want to implement Copy operation in background thread(during that you want to display progress bar or dialog) and once copy operation is completed then you want to start another activity, if this is the case then i would suggest you to implement AsyncTask and this is the best practice to implement threading process in Android i think, because we developer don't need to take care about Thread Management if we have implemented this AsyncTask.
For your case:
private class myBackgroundTask extends AsyncTask<Void, Void, Void>
{
#Override
protected void onPreExecute()
{
// Show Progress bar or dialog
}
protected Long doInBackground(Void... void) {
// write your Copy from Assets to SDcard code here
}
protected void onPostExecute(Long result)
{
if(dialog.isShowing()) // if progress dialog is showing
{
dialog.dismiss();
}
// And now Write Start Activity code here
}
}
Update:
FYI, just a quick update AsyncTask is known as Painless Threading in Android, go through this article and i am sure you will sure come to know about the importance of this concept as compared to general threading implementation.
I am running remote audio-file-fetching and audio file playback operations in a background thread using AsyncTask. A Cancellable progress bar is shown for the time the fetch operation runs.
I want to cancel/abort the AsyncTask run when the user cancels (decides against) the operation. What is the ideal way to handle such a case?
Just discovered that AlertDialogs's boolean cancel(...); I've been using everywhere actually does nothing. Great.
So...
public class MyTask extends AsyncTask<Void, Void, Void> {
private volatile boolean running = true;
private final ProgressDialog progressDialog;
public MyTask(Context ctx) {
progressDialog = gimmeOne(ctx);
progressDialog.setCancelable(true);
progressDialog.setOnCancelListener(new OnCancelListener() {
#Override
public void onCancel(DialogInterface dialog) {
// actually could set running = false; right here, but I'll
// stick to contract.
cancel(true);
}
});
}
#Override
protected void onPreExecute() {
progressDialog.show();
}
#Override
protected void onCancelled() {
running = false;
}
#Override
protected Void doInBackground(Void... params) {
while (running) {
// does the hard work
}
return null;
}
// ...
}
If you're doing computations:
You have to check isCancelled() periodically.
If you're doing a HTTP request:
Save the instance of your HttpGet or HttpPost somewhere (eg. a public field).
After calling cancel, call request.abort(). This will cause IOException be thrown inside your doInBackground.
In my case, I had a connector class which I used in various AsyncTasks. To keep it simple, I added a new abortAllRequests method to that class and called this method directly after calling cancel.
The thing is that AsyncTask.cancel() call only calls the onCancel function in your task. This is where you want to handle the cancel request.
Here is a small task I use to trigger an update method
private class UpdateTask extends AsyncTask<Void, Void, Void> {
private boolean running = true;
#Override
protected void onCancelled() {
running = false;
}
#Override
protected void onProgressUpdate(Void... values) {
super.onProgressUpdate(values);
onUpdate();
}
#Override
protected Void doInBackground(Void... params) {
while(running) {
publishProgress();
}
return null;
}
}
Simple: don't use an AsyncTask. AsyncTask is designed for short operations that end quickly (tens of seconds) and therefore do not need to be canceled. "Audio file playback" does not qualify. You don't even need a background thread for ordinary audio file playback.
The only way to do it is by checking the value of the isCancelled() method and stopping playback when it returns true.
This is how I write my AsyncTask
the key point is add Thread.sleep(1);
#Override protected Integer doInBackground(String... params) {
Log.d(TAG, PRE + "url:" + params[0]);
Log.d(TAG, PRE + "file name:" + params[1]);
downloadPath = params[1];
int returnCode = SUCCESS;
FileOutputStream fos = null;
try {
URL url = new URL(params[0]);
File file = new File(params[1]);
fos = new FileOutputStream(file);
long startTime = System.currentTimeMillis();
URLConnection ucon = url.openConnection();
InputStream is = ucon.getInputStream();
BufferedInputStream bis = new BufferedInputStream(is);
byte[] data = new byte[10240];
int nFinishSize = 0;
while( bis.read(data, 0, 10240) != -1){
fos.write(data, 0, 10240);
nFinishSize += 10240;
**Thread.sleep( 1 ); // this make cancel method work**
this.publishProgress(nFinishSize);
}
data = null;
Log.d(TAG, "download ready in"
+ ((System.currentTimeMillis() - startTime) / 1000)
+ " sec");
} catch (IOException e) {
Log.d(TAG, PRE + "Error: " + e);
returnCode = FAIL;
} catch (Exception e){
e.printStackTrace();
} finally{
try {
if(fos != null)
fos.close();
} catch (IOException e) {
Log.d(TAG, PRE + "Error: " + e);
e.printStackTrace();
}
}
return returnCode;
}
Our global AsyncTask class variable
LongOperation LongOperationOdeme = new LongOperation();
And KEYCODE_BACK action which interrupt AsyncTask
#Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
LongOperationOdeme.cancel(true);
}
return super.onKeyDown(keyCode, event);
}
It works for me.
I don't like to force interrupt my async tasks with cancel(true) unnecessarily because they may have resources to be freed, such as closing sockets or file streams, writing data to the local database etc. On the other hand, I have faced situations in which the async task refuses to finish itself part of the time, for example sometimes when the main activity is being closed and I request the async task to finish from inside the activity's onPause() method. So it's not a matter of simply calling running = false. I have to go for a mixed solution: both call running = false, then giving the async task a few milliseconds to finish, and then call either cancel(false) or cancel(true).
if (backgroundTask != null) {
backgroundTask.requestTermination();
try {
Thread.sleep((int)(0.5 * 1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
if (backgroundTask.getStatus() != AsyncTask.Status.FINISHED) {
backgroundTask.cancel(false);
}
backgroundTask = null;
}
As a side result, after doInBackground() finishes, sometimes the onCancelled() method is called, and sometimes onPostExecute(). But at least the async task termination is guaranteed.
With reference to Yanchenko's answer on 29 April '10:
Using a 'while(running)' approach is neat when your code under 'doInBackground' has to be executed multiple times during every execution of the AsyncTask. If your code under 'doInBackground' has to be executed only once per execution of the AsyncTask, wrapping all your code under 'doInBackground' in a 'while(running)' loop will not stop the background code (background thread) from running when the AsyncTask itself is cancelled, because the 'while(running)' condition will only be evaluated once all the code inside the while loop has been executed at least once. You should thus either
(a.) break up your code under 'doInBackground' into multiple 'while(running)' blocks or
(b.) perform numerous 'isCancelled' checks throughout your 'doInBackground' code, as explained under "Cancelling a task" at https://developer.android.com/reference/android/os/AsyncTask.html.
For option (a.) one can thus modify Yanchenko's answer as follows:
public class MyTask extends AsyncTask<Void, Void, Void> {
private volatile boolean running = true;
//...
#Override
protected void onCancelled() {
running = false;
}
#Override
protected Void doInBackground(Void... params) {
// does the hard work
while (running) {
// part 1 of the hard work
}
while (running) {
// part 2 of the hard work
}
// ...
while (running) {
// part x of the hard work
}
return null;
}
// ...
For option (b.) your code in 'doInBackground' will look something like this:
public class MyTask extends AsyncTask<Void, Void, Void> {
//...
#Override
protected Void doInBackground(Void... params) {
// part 1 of the hard work
// ...
if (isCancelled()) {return null;}
// part 2 of the hard work
// ...
if (isCancelled()) {return null;}
// ...
// part x of the hard work
// ...
if (isCancelled()) {return null;}
}
// ...