Існує багато способів завантаження файлів. Слідом я розміщу найпоширеніші способи; вирішувати, який метод краще для вашої програми, вирішувати саме вам.
1. Використовуйте AsyncTask
та показуйте хід завантаження у діалоговому вікні
Цей метод дозволить одночасно виконати деякі фонові процеси та оновити інтерфейс користувача (у цьому випадку ми оновимо панель прогресу).
Імпорт:
import android.os.PowerManager;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.FileOutputStream;
import java.net.HttpURLConnection;
Це приклад коду:
// 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
}
});
AsyncTask
Буде виглядати наступним чином :
// 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;
}
Метод вище ( doInBackground
) працює завжди на фоновому потоці. Тут не слід виконувати жодні завдання інтерфейсу. З іншого боку, onProgressUpdate
і onPreExecute
запустіть на потоці інтерфейсу користувача, щоб там ви могли змінити панель прогресу:
@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();
}
Щоб це запустилося, вам потрібен дозвіл WAKE_LOCK.
<uses-permission android:name="android.permission.WAKE_LOCK" />
2. Завантажити зі служби
Основне питання тут: як я можу оновлювати свою діяльність зі служби? . У наступному прикладі ми будемо використовувати два класи, про які ви можете не знати: ResultReceiver
і IntentService
. ResultReceiver
це той, який дозволить нам оновити наш потік із сервісу; IntentService
це підклас, Service
який створює потік для виконання фонових робіт звідти (ви повинні знати, що Service
запуски фактично в тій самій нитці вашої програми; коли ви розширюєтесь Service
, ви повинні вручну створити нові потоки для запуску операцій блокування процесора).
Служба завантаження може виглядати так:
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);
}
}
Додайте послугу до свого маніфесту:
<service android:name=".DownloadService"/>
А діяльність буде виглядати приблизно так:
// 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);
Ось було ResultReceiver
прийде грати:
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 Використовуйте бібліотеку Groundy
Groundy - це бібліотека, яка в основному допомагає запускати фрагменти коду у фоновому сервісі, і базується наResultReceiver
концепції, показаній вище. Наразіця бібліотека застаріла . Ось яквиглядатиме весь код:
Дія, де ви показуєте діалогове вікно ...
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;
}
}
};
}
GroundyTask
Реалізація використовується Groundy , щоб завантажити файл і показати прогрес:
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;
}
}
}
І просто додайте це до маніфесту:
<service android:name="com.codeslap.groundy.GroundyService"/>
Я не можу бути простішим, думаю. Просто візьміть останню банку від Github і ви готові їхати. Майте на увазі, що головна мета Groundy - здійснювати дзвінки на зовнішній REST apis у фоновому режимі та легко розміщувати результати в інтерфейсі. Якщо ви робите щось подібне у своєму додатку, це може бути дуже корисним.
3. Використовуйте DownloadManager
клас ( GingerBread
і лише новіші)
GingerBread запропонував нову функцію, DownloadManager
яка дозволяє легко завантажувати файли та делегувати важку роботу з обробкою ниток, потоків тощо у системі.
Спочатку розглянемо корисний метод:
/**
* @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;
}
Назва методу пояснює все це. Як тільки ви впевнені, що DownloadManager
це доступно, ви можете зробити щось подібне:
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);
Хід завантаження відображатиметься на панелі сповіщень.
Заключні думки
Перший і другий методи - це лише верхівка айсберга. Є багато речей, про які потрібно пам’ятати, якщо ви хочете, щоб ваш додаток був надійним. Ось короткий список:
- Ви повинні перевірити, чи є у користувача доступ до Інтернету
- Переконайтеся, що у вас є правильні дозволи (
INTERNET
і WRITE_EXTERNAL_STORAGE
); також ACCESS_NETWORK_STATE
якщо ви хочете перевірити наявність Інтернету.
- Переконайтесь, що каталог, у якому ви збираєтеся завантажувати файли, існує та має права на запис.
- Якщо завантаження занадто велике, ви можете скористатися способом відновлення завантаження, якщо попередні спроби не вдалися.
- Користувачі будуть вдячні, якщо ви дозволите їм перервати завантаження.
Якщо вам не потрібен детальний контроль над процесом завантаження, тоді подумайте про використання DownloadManager
(3), оскільки він вже обробляє більшість перерахованих вище елементів.
Але також врахуйте, що ваші потреби можуть змінитися. Наприклад, DownloadManager
не кешується відповідь . Він буде сліпо завантажувати один і той же великий файл кілька разів. Немає простого способу виправити це після факту. Якщо ви починаєте з базового HttpURLConnection
(1, 2), тоді все, що вам потрібно, - це додати HttpResponseCache
. Тож початкові зусилля щодо вивчення основних, стандартних інструментів можуть стати хорошою інвестицією.
Цей клас був застарілий на рівні API 26. ProgressDialog - це модальний діалог, який не дозволяє користувачеві взаємодіяти з додатком. Замість використання цього класу слід використовувати індикатор прогресу, як ProgressBar, який можна вбудувати в інтерфейс програми. Крім того, ви можете використовувати сповіщення, щоб повідомити користувача про хід виконання завдання. Для більш детальної інформації Посилання