Android开发基础10-探究服务(下)

**本篇文章主要介绍以下几个知识点:

  • 服务的实践应用。

10.5 服务的最佳实践——完整版的下载示例

  本节来综合运用一下,实现一个在服务中经常使用到的功能——下载。效果如下:

  首先添加网络请求依赖库:

1
compile 'com.squareup.okhttp3:okhttp:3.5.0'

  接下来需要定义一个回调接口,用于对下载过程中的各种状态进行监听和回调,新建 DownloadListener 接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* Function:回调接口,对下载过程中的各种状态进行监听和回调
* Author:kxwon on 2017/1/22 19:09
* Email:kxwonder@163.com
*/

public interface DownloadListener {

void onProgress(int progress); // 下载进度

void onSuccess(); // 下载成功

void onFailed(); // 下载失败

void onPaused(); // 下载暂停

void onCanceled(); // 下载取消
}

  回调接口定义好后,下面就开始编写下载功能了,这里用 AsyncTask 来实现,新建一个 DownloadTask 继承自 AsyncTask:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
public class DownloadTask extends AsyncTask<String,Integer,Integer>{

public static final int TYPE_SUCCESS = 0; // 下载成功
public static final int TYPE_FAILED = 1; // 下载失败
public static final int TYPE_PAUSED = 2; // 下载暂停
public static final int TYPE_CANCELED = 3; // 下载取消

private DownloadListener listener;

private boolean isCanceled = false;

private boolean isPaused = false;

private int lastProgress;//上一次的下载进度

public DownloadTask(DownloadListener listener){
this.listener = listener;
}

/**
* 用于在后台执行具体的下载逻辑
* @param params
* @return
*/
@Override
protected Integer doInBackground(String... params) {
InputStream is = null;
RandomAccessFile savedFile = null;
File file = null;
try{
long downloadedLength = 0;// 记录已下载的文件长度
String downloadUrl = params[0];//获取到下载的URL地址
String fileName = downloadUrl.substring(downloadUrl.lastIndexOf("/"));//下载的文件名
String directory = Environment.getExternalStoragePublicDirectory
(Environment.DIRECTORY_DOWNLOADS).getPath();//将文件下载到指定目录
file = new File(directory + fileName);
if (file.exists()){
downloadedLength = file.length();
}
long contentLength = getContentLength(downloadUrl);//获取待下载文件的总长度
if (contentLength == 0){
return TYPE_FAILED;
}else if (contentLength == downloadedLength){
// 已下载字节和文件总字节相等,说明已经下载完成了
return TYPE_SUCCESS;
}
// 发送网络请求,从网络读取数据写入到本地,直到文件下载完
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
// 断点下载,指定从哪个字节开始下载
.addHeader("RANGE","bytes=" + downloadedLength + "-")
.url(downloadUrl)
.build();
Response response = client.newCall(request).execute();
if (response != null){
is = response.body().byteStream();
savedFile = new RandomAccessFile(file,"rw");
savedFile.seek(downloadedLength);//跳过已下载的字节
byte[] b = new byte[1024];
int total = 0;
int len;
while ((len = is.read(b)) != -1){
if (isCanceled){
return TYPE_CANCELED;
}else if (isPaused){
return TYPE_PAUSED;
}else {
total += len;
savedFile.write(b,0,len);
// 计算已下载的百分比
int progress = (int)((total + downloadedLength) * 100/contentLength);
publishProgress(progress);//通知当前的下载进度
}
}
response.body().close();
return TYPE_SUCCESS;
}
}catch (Exception e){
e.printStackTrace();
}finally {
try {
if (is != null){
is.close();
}
if (savedFile != null){
savedFile.close();
}
if (isCanceled && file != null){
file.delete();
}
}catch (Exception e){
e.printStackTrace();
}
}
return TYPE_FAILED;
}

/**
* 用于在界面上更新当前的下载速度
* @param values
*/
@Override
protected void onProgressUpdate(Integer... values) {
int progress = values[0];//当前的下载进度
if (progress > lastProgress){
listener.onProgress(progress);//通知下载进度更新
lastProgress = progress;
}
}

/**
* 用于通知最终的下载结果
* @param integer
*/
@Override
protected void onPostExecute(Integer integer) {
switch (integer){
case TYPE_SUCCESS:
listener.onSuccess();//下载成功
break;
case TYPE_FAILED:
listener.onFailed();//下载失败
break;
case TYPE_PAUSED:
listener.onPaused();//下载暂停
break;
case TYPE_CANCELED:
listener.onCanceled();//下载取消
break;
default:
break;
}
}

/**
* 暂停
*/
public void pauseDownload(){
isPaused = true;
}

/**
* 取消
*/
public void cancelDownload(){
isCanceled = true;
}

/**
* 获取待下载文件的总长度
* @param downloadUrl
* @return
*/
private long getContentLength(String downloadUrl) throws IOException{
OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder()
.url(downloadUrl)
.build();
Response response = client.newCall(request).execute();
if (response != null && response.isSuccessful()){
long contentLength = response.body().contentLength();
response.close();
return contentLength;
}
return 0;
}
}

  以上就把具体的下载功能完成了,下面为了保证 DownloadTask 可以一直在后台运行,还需创建一个下载的服务。新建 DownloadService 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
public class DownloadService extends Service {

private DownloadTask downloadTask;

private String downloadUrl;

private DownloadListener listener = new DownloadListener() {
@Override
public void onProgress(int progress) {
getNotificationManager().notify(1,getNotification("Downloading ...",progress));
}

@Override
public void onSuccess() {
downloadTask = null;
// 下载成功时将前台服务通知关闭,并创建一个下载成功的通知
stopForeground(true);
getNotificationManager().notify(1,getNotification("Download Success",-1));
ToastUtils.showShort("下载成功");
}

@Override
public void onFailed() {
downloadTask = null;
// 下载失败时将前台服务通知关闭,并创建一个下载失败的通知
stopForeground(true);
getNotificationManager().notify(1,getNotification("Download Failed",-1));
ToastUtils.showShort("下载失败");
}

@Override
public void onPaused() {
downloadTask = null;
ToastUtils.showShort("下载暂停");
}

@Override
public void onCanceled() {
downloadTask = null;
stopForeground(true);
ToastUtils.showShort("下载取消");
}
};

public DownloadService() {
}

private DownloadBinder mBinder = new DownloadBinder();

class DownloadBinder extends Binder{
/**
* 开始下载
* @param url
*/
public void startDownload(String url){
if (downloadTask == null){
downloadUrl = url;
downloadTask = new DownloadTask(listener);
downloadTask.execute(downloadUrl);
startForeground(1,getNotification("Downloading ...",0));
ToastUtils.showShort("Downloading ...");
}
}

/**
* 暂停下载
*/
public void pauseDownload(){
if (downloadTask != null){
downloadTask.pauseDownload();;
}
}

/**
* 取消下载
*/
public void cancelDownload(){
if (downloadTask != null){
downloadTask.cancelDownload();
}else {
if (downloadUrl != null){
// 取消下载时需要将文件删除,并且通知关闭
String fileName = downloadUrl.substring(downloadUrl.lastIndexOf("/"));
String directory = Environment.getExternalStoragePublicDirectory
(Environment.DIRECTORY_DOWNLOADS).getPath();
File file = new File(directory + fileName);
if (file.exists()){
file.delete();
}

}
}
}
}

@Override
public IBinder onBind(Intent intent) {
return mBinder;
}

private NotificationManager getNotificationManager(){
return (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
}

/**
* 构建通知方法
*/
private Notification getNotification(String title,int progress){
Intent intent = new Intent(this,DownloadActivity.class);
PendingIntent pi = PendingIntent.getActivity(this,0,intent,0);
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
builder.setSmallIcon(R.mipmap.ic_launcher);
builder.setLargeIcon(BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher));
builder.setContentIntent(pi);
builder.setContentTitle(title);
if (progress > 0){
// 当progress大于或等于0时才需要显示下载进度
builder.setContentText(progress + "%");
// 三个参数:通知的最大进度、通知的当前进度、是否使用模糊进度条
builder.setProgress(100,progress,false);
}
return builder.build();
}
}

  现在下载的服务已实现,后端的工作基本完成,接下来就开始编写前端的部分。在布局中放置3个按钮,分别用于开始下载、暂停下载、取消下载,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
public class DownloadActivity extends AppCompatActivity implements View.OnClickListener {

private DownloadService.DownloadBinder downloadBinder;

private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
downloadBinder = (DownloadService.DownloadBinder) service;
}

@Override
public void onServiceDisconnected(ComponentName name) {

}
};

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_download);

Button start_download = (Button) findViewById(R.id.start_download);
Button pause_download = (Button) findViewById(R.id.pause_download);
Button cancel_download = (Button) findViewById(R.id.cancel_download);
start_download.setOnClickListener(this);
pause_download.setOnClickListener(this);
cancel_download.setOnClickListener(this);

Intent intent = new Intent(this,DownloadService.class);
startService(intent);//启动服务
bindService(intent,connection,BIND_AUTO_CREATE);//绑定服务
if (ContextCompat.checkSelfPermission(DownloadActivity.this, android.Manifest.
permission.WRITE_EXTERNAL_STORAGE)!= PackageManager.PERMISSION_GRANTED){
ActivityCompat.requestPermissions(DownloadActivity.this,new
String[]{android.Manifest.permission.WRITE_EXTERNAL_STORAGE},1);
}
}

@Override
public void onClick(View v) {
if (downloadBinder == null){
return;
}
switch (v.getId()){
// 开始下载
case R.id.start_download:
String url = "https://raw.githubusercontent.com/guolindev/eclipse/" +
"master/eclipse-inst-win64.exe";
downloadBinder.startDownload(url);
break;

// 暂停下载
case R.id.pause_download:
downloadBinder.pauseDownload();
break;

// 取消下载
case R.id.cancel_download:
downloadBinder.cancelDownload();
break;

default:
break;
}
}

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
switch (requestCode){
case 1:
if (grantResults.length > 0 && grantResults[0] != PackageManager.PERMISSION_GRANTED){
ToastUtils.showShort("拒绝权限将无法使用程序");
finish();
}
break;
default:
}
}

@Override
protected void onDestroy() {
super.onDestroy();
unbindService(connection);//解绑服务
}
}

  最后,别忘了声明权限:

1
2
3
4
<!-- 访问SD卡权限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!-- 访问网络权限 -->
<uses-permission android:name="android.permission.INTERNET" />

  以上,就是整个的下载示例。

  本章完结。

Jason Xu wechat
欢迎您扫一扫上面的微信公众号,订阅我的博客!
坚持原创技术分享,您的支持将鼓励我继续创作!