使用AsyncTask在Android中将文件下载到内部存储。
使用AsyncTask在Android中将文件下载到内部存储。
我正在尝试编写一个简单的应用程序,该应用程序会得到更新。为此,我需要一个可以下载文件并在 ProgressDialog
中显示当前进度的简单函数。我知道如何使用 ProgressDialog
,但我不确定如何显示当前进度以及如何首先下载文件。
有很多方法可以下载文件。接下来我会发布最常见的方式;选择哪种方法更适合您的应用程序取决于您。
- 使用
AsyncTask
并在对话框中显示下载进度
=============================================================
这种方法将允许您执行一些后台进程并同时更新UI(在这种情况下,我们将更新进度条)。
导入:
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{ 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
)总是在后台线程上运行。您不应在那里执行任何UI任务。另一方面,onProgressUpdate
和onPreExecute
在UI线程上运行,所以您可以在那里更改进度条:
@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权限。
- 从服务下载
========================
这里的大问题是:如何从服务更新我的活动?在下一个示例中,我们将使用两个您可能不知道的类:ResultReceiver
和IntentService
。ResultReceiver
将允许我们从服务更新我们的线程;IntentService
是 Service
的子类,它产生一个线程来做后台工作(您应该知道 Service
实际上运行在您的应用程序的同一线程上;当您扩展 Service
时,您必须手动生成新的线程来运行CPU阻塞操作)。
下载服务可以像这样:
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); } }
将服务添加到清单:
然后活动将看起来像这样:
// 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; } } }; }
Groundy用于下载文件并显示进度的GroundyTask
实现:
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; } } }
只需将其添加到清单中:
我认为这是再简单不过的了。只需从Github获取最新的jar包,你就可以开始了。请记住,Groundy的主要目的是在后台服务中调用外部REST API并轻松地将结果发布到UI中。如果您的应用程序正在执行此类操作,这可能非常有用。
2.2 使用https://github.com/koush/ion
- 使用
DownloadManager
类(仅适用于及更高版本)
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这样的进度指示器来替代使用这个类,它可以嵌入到你的应用程序的UI中。另外,你也可以使用通知来通知用户任务的进展。更多细节请参考链接