执行网络任务
在当今99%的移动应用中网络都是必不可缺的一部分:总是需要连接远程服务器来检索App需要的信息。
作为网络访问的第一个案例,我们将创建下面这样一个场景:
  • 加载一个进度条。
  • 用一个按钮开始文件下载。
  • 下载过程中更新进度条。
  • 下载完后开始视频播放。
我们的用户界面非常简单,我们只需要一个有趣的进度条和一个下载按钮。
首先,我们创建mDownloadProgress
1
private PublishSubject<Integer> mDownloadProgress = PublishSubject.create();
Copied!
这个主题我们用来管理进度的更新,它和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
}
Copied!
上面的这段代码将会触发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
}
Copied!
现在我们需要触发下载操作,点击下载按钮:
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
9
@Override
10
public void onCompleted() {
11
App.L.debug("Completed");
12
}
13
14
@Override
15
public void onError(Throwable e) {
16
App.L.error(e.toString());
17
}
18
19
@Override
20
public void onNext(Integer progress) {
21
mArcProgress.setProgress(progress);
22
}
23
});
24
25
String destination = "sdcardsoftboy.avi";
26
obserbableDownload("http://archive.blender.org/fileadmin/movies/softboy.avi", destination)
27
.subscribeOn(Schedulers.io())
28
.observeOn(AndroidSchedulers.mainThread())
29
.subscribe(success -> {
30
resetDownloadButton();
31
Intent intent = new Intent(android.content.Intent.ACTION_VIEW);
32
File file = new File(destination);
33
intent.setDataAndType(Uri.fromFile(file),"video/avi");
34
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
35
startActivity(intent);
36
}, error -> {
37
Toast.makeText(getActivity(), "Something went south", Toast.LENGTH_SHORT).show();
38
resetDownloadButton();
39
});
40
}
Copied!
我们使用Butter Knife的注解@OnClick来绑定按钮的方法并更新按钮信息和点击状态:我们不想让用户点击多次从而触发多次下载事件。
然后,我们创建一个subscription来观察下载进度并相应的更新进度条。很明显,我们订阅在主线程是因为进度条是UI元素。
1
obserbableDownload("http://archive.blender.org/fileadmin/movies/softboy.avi", "sdcardsoftboy.avi";)
Copied!
这是一个下载Observable。网络调用是一个I/O任务,理应使用I/O调度器。当下载完成,就会在onNext() 中启动视频播放器,并且播放器将会在目标路径找到下载的文件.。
下图展示了下载进度和视频播放器选择对话框:
Copy link