android中如何下载文件并显示下载进度

seannell 8年前

 最近开发中遇到需要下载文件的问题,对于一般的下载来说不用考虑断点续传,不用考虑多个线程,比如下载一个apk之类的,这篇文章讨论的就是这种情形。

这里主要讨论三种方式:AsyncTask、Service和使用DownloadManager。

一、使用AsyncTask并在进度对话框中显示下载进度

这种方式的优势是你可以在后台执行下载任务的同时,也可以更新UI(这里我们用progress bar来更新下载进度)

下面的代码是使用的例子

// 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);      }  });

DownloadTask继承自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,这是执行后台任务的代码块,不能在这里做任何的UI操作,但是onProgressUpdate和onPreExecute是运行在UI线程中的,所以我们应该在这两个方法中更新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();  }

注意需要添加如下权限:

<uses-permission android:name="android.permission.WAKE_LOCK" />

 

二、在service中执行下载

在service中执行下载任务的麻烦之处在于如何通知activity更新UI。下面的代码中我们将用ResultReceiver和IntentService来实现下载。ResultReceiver允许我们接收来自service中发出的广播,IntentService继承自service,这IntentService中我们开启一个线程开执行下载任务(service和你的app其实是在一个线程中,因此不想阻塞主线程的话必须开启新的线程)。

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 {              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());              OutputStream output = new FileOutputStream("/sdcard/BarcodeScanner-debug.apk");              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);              }              output.flush();              output.close();              input.close();          } catch (IOException e) {              e.printStackTrace();          }          Bundle resultData = new Bundle();          resultData.putInt("progress" ,100);          receiver.send(UPDATE_PROGRESS, resultData);      }  }

注册DownloadService

<service android:name=".DownloadService"/>

activity中这样调用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接收来自DownloadService的下载进度通知

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");              mProgressDialog.setProgress(progress);              if (progress == 100) {                  mProgressDialog.dismiss();              }          }      }  }

 

2.1使用 Groundy library

Groundy 可以帮助你在后台service中运行一些代码,其实也是基于刚刚用到的 ResultReceiver,下面是使用Groundy的大致代码:

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的定义如下:

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;          }      }  }

但是请记住要在activity中注册了相关service才行:

<service android:name="com.codeslap.groundy.GroundyService"/>

 

三、使用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) {      try {          if (Build.VERSION.SDK_INT < Build.VERSION_CODES.GINGERBREAD) {              return false;          }          Intent intent = new Intent(Intent.ACTION_MAIN);          intent.addCategory(Intent.CATEGORY_LAUNCHER);          intent.setClassName("com.android.providers.downloads.ui", "com.android.providers.downloads.ui.DownloadList");          List<ResolveInfo> list = context.getPackageManager().queryIntentActivities(intent,                  PackageManager.MATCH_DEFAULT_ONLY);          return list.size() > 0;      } catch (Exception e) {          return false;      }  }

如果能,那么只需要这样就可以开始下载一个文件了:

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);

下载的进度会在消息通知中显示。

总结

前两种方法需要你考虑的东西很多,除非是你想完全控制下载的整个过程,否则用最后一种比较省事。