执行网络任务

在当今99%的移动应用中网络都是必不可缺的一部分:总是需要连接远程服务器来检索App需要的信息。

作为网络访问的第一个案例,我们将创建下面这样一个场景:

  • 加载一个进度条。
  • 用一个按钮开始文件下载。
  • 下载过程中更新进度条。
  • 下载完后开始视频播放。

我们的用户界面非常简单,我们只需要一个有趣的进度条和一个下载按钮。

执行网络任务 - 图1

首先,我们创建mDownloadProgress

  1. private PublishSubject<Integer> mDownloadProgress = PublishSubject.create();

这个主题我们用来管理进度的更新,它和download函数协同工作。

  1. private boolean downloadFile(String source, String destination) {
  2. boolean result = false;
  3. InputStream input = null;
  4. OutputStream output = null;
  5. HttpURLConnection connection = null;
  6. try {
  7. URL url = new URL(source);
  8. connection = (HttpURLConnection) url.openConnection();
  9. connection.connect();
  10. if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
  11. return false;
  12. }
  13. int fileLength = connection.getContentLength();
  14. input = connection.getInputStream();
  15. output = new FileOutputStream(destination);
  16. byte data[] = new byte[4096];
  17. long total = 0;
  18. int count;
  19. while ((count = input.read(data)) != -1) {
  20. total += count;
  21. if (fileLength >0) {
  22. int percentage = (int) (total * 100 / fileLength);
  23. mDownloadProgress.onNext(percentage);
  24. }
  25. output.write(data, 0, count);
  26. }
  27. mDownloadProgress.onCompleted();
  28. result = true;
  29. } catch (Exception e) {
  30. mDownloadProgress.onError(e);
  31. } finally {
  32. try {
  33. if (output != null) {
  34. output.close();
  35. }
  36. if (input != null) {
  37. input.close();
  38. }
  39. } catch (IOException e) {
  40. mDownloadProgress.onError(e);
  41. }
  42. if (connection != null) {
  43. connection.disconnect();
  44. mDownloadProgress.onCompleted();
  45. }
  46. }
  47. return result;
  48. }

上面的这段代码将会触发NetworkOnMainThreadException异常。我们可以创建RxJava版本的函数进入我们挚爱的响应式世界来解决这个问题:

  1. private Observable<Boolean> obserbableDownload(String source, String destination) {
  2. return Observable.create(subscriber -> {
  3. try {
  4. boolean result = downloadFile(source, destination);
  5. if (result) {
  6. subscriber.onNext(true);
  7. subscriber.onCompleted();
  8. } else {
  9. subscriber.onError(new Throwable("Download failed."));
  10. }
  11. } catch (Exception e) {
  12. subscriber.onError(e);
  13. }
  14. });
  15. }

现在我们需要触发下载操作,点击下载按钮:

  1. @OnClick(R.id.button_download)
  2. void download() {
  3. mButton.setText(getString(R.string.downloading));
  4. mButton.setClickable(false);
  5. mDownloadProgress.distinct()
  6. .observeOn(AndroidSchedulers.mainThread())
  7. .subscribe(new Observer<Integer>() {
  8. @Override
  9. public void onCompleted() {
  10. App.L.debug("Completed");
  11. }
  12. @Override
  13. public void onError(Throwable e) {
  14. App.L.error(e.toString());
  15. }
  16. @Override
  17. public void onNext(Integer progress) {
  18. mArcProgress.setProgress(progress);
  19. }
  20. });
  21. String destination = "sdcardsoftboy.avi";
  22. obserbableDownload("http://archive.blender.org/fileadmin/movies/softboy.avi", destination)
  23. .subscribeOn(Schedulers.io())
  24. .observeOn(AndroidSchedulers.mainThread())
  25. .subscribe(success -> {
  26. resetDownloadButton();
  27. Intent intent = new Intent(android.content.Intent.ACTION_VIEW);
  28. File file = new File(destination);
  29. intent.setDataAndType(Uri.fromFile(file),"video/avi");
  30. intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
  31. startActivity(intent);
  32. }, error -> {
  33. Toast.makeText(getActivity(), "Something went south", Toast.LENGTH_SHORT).show();
  34. resetDownloadButton();
  35. });
  36. }

我们使用Butter Knife的注解@OnClick来绑定按钮的方法并更新按钮信息和点击状态:我们不想让用户点击多次从而触发多次下载事件。

然后,我们创建一个subscription来观察下载进度并相应的更新进度条。很明显,我们订阅在主线程是因为进度条是UI元素。

  1. obserbableDownload("http://archive.blender.org/fileadmin/movies/softboy.avi", "sdcardsoftboy.avi";)

这是一个下载Observable。网络调用是一个I/O任务,理应使用I/O调度器。当下载完成,就会在onNext() 中启动视频播放器,并且播放器将会在目标路径找到下载的文件.。

下图展示了下载进度和视频播放器选择对话框:

执行网络任务 - 图2