Android第一行代码--服务

服务

  • 服务(Service)用于Android实现程序后台运行。适合不需要与用户交互且长期运行的任务。
  • 服务并不是运行在独立进程中,而是依赖于创建服务所在的进程中,若该进程被杀死,服务也会停止。

Android多线程编程

线程基本用法

通过类继承

1
2
3
4
5
6
7
class MyThread extends Thread{
public void run(){
//处理具体的逻辑
}
}
//启动
new MyThread().start();

通过实现接口

1
2
3
4
5
6
7
8
class MyThread implements Runnable{
public void run(){
//处理具体的逻辑
}
}
//启动
MyThread myThread = new MyThread();
new Thread(myThread).start();

匿名类

1
2
3
4
5
new Thread(new Runnable(){
public void run(){
//处理具体的逻辑
}
}).start();

子线程中更新UI

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.change_text:
new Thread(new Runnable() {
@Override
//在子线程中对UI进行修改
public void run() {
text.setText("Nice to meet ou");
}
}).start();
break;
default:
break;
}

运行会出现以下报错,这是由于Android系统不允许子线程修改UI界面

image-20220118171011196

使用Android提供的异步消息处理机制,就可以实现子线程中进行UI操作。在子线程中只实现了发送消息,而消息处理的过程是在主线程中完成的。

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
....
public static final int UPDATE_TEXT = 1;
private Handler handler = new Handler(){
public void handleMessage(Message msg){//重写消息处理方法
switch (msg.what){
case UPDATE_TEXT:
text.setText("Nice to meet you");
break;
default:
break;
}
}
};
....
public void onClick(View v) {
switch (v.getId()){
case R.id.change_text:
new Thread(new Runnable() {
@Override
public void run() {
//利用消息处理进行子线程对UI修改
Message message = new Message();
message.what = UPDATE_TEXT;
handler.sendMessage(message);
}
}).start();
break;
default:
break;
}

}

异步消息处理机制

Android中的异步消息处理由4部分组成:MessageHandlerMessageQueueLooper

  • Message:是线程之间传递的消息,用于在不同线程之间交换数据。

  • Handler:是用于发送和处理消息的

  • MessageQueue:消息队列,主要用于存放通过Handle发送的消息。这部分消息会一直存在于消息队列中,等待被处理。每个线程都有一个MessageQueue对象

  • Looper:管理消息队列,每当发现队列中存在消息就会将消息取出,传递给Handler进行处理,每个线程都有一个Looper对象

图片来自Android第一行代码

AsyncTask

1
2
3
4
class DownloadTask extends AsyncTask<Void, Integer, Boolean>
{
///
}

AsyncTask是一个抽象类,因此想要使用需要创建一个子类去继承它。AsyncTask类指定3个泛型参数

  • 参数一:Params,在执行AsyncTask时需要传入的参数,用于在后台任务中使用。
  • 参数二:Progress,后台任务执行时,在界面显示当前的进度
  • 参数三:Result,任务执行完毕后,如果需要对结果进行返回,则是要该泛型值进行返回。

AsyncTask需要重写的方法

  • onPreExecute():该方法在后台任务开始执行前调用,用于界面上的初始化。
  • doInBackground(Params...):需要在线程运行的代码需要放在这个方法中,但是不能进行UI处理
  • onProgressUpdate(Progress...):这个方法可以对UI进行操作,参数通过publishProgress(Progress...)传递过来
  • onPostExecute(Result):后台任务执行完毕并通过return语句返回时会执行该方法

服务的基本用法

启动服务

1
2
Intent startIntent = new Intent(this,MyService.class);
startService(startIntent); //启动服务

关闭服务

1
2
3
case R.id.stop_service:
Intent stopIntent = new Intent(this,MyService.class);
stopService(stopIntent);

活动和服务进行通信

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
    private DownloadBinder mBinder = new DownloadBinder(); //建立Binder对象
class DownloadBinder extends Binder{
public void startDownload(){
Log.d("MyService", "startDownload: ");
}
public int getProgress(){
Log.d("MyService", "getProgress: ");
return 0;
}
}
...
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
return mBinder;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
    private  MyService.DownloadBinder downloadBinder;//Binder对象
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
downloadBinder = (MyService.DownloadBinder)service;
downloadBinder.startDownload();
downloadBinder.getProgress();
}

@Override
public void onServiceDisconnected(ComponentName name) {

}
};
...
case R.id.bind_service:
Intent bindIntent = new Intent(this,MyService.class);
bindService(bindIntent,connection,BIND_AUTO_CREATE);
break;
case R.id.unbind_service:
unbindService(connection);
break;
  • bindService()方法用于服务与活动建立连接
    • 参数一:intent对象
    • 参数二:Serviceconnect实例
    • 参数三:BIND_AUTO_CREATE表示在活动和服务进行绑定后自动创建服务

服务的生命周期

  • 服务生命周期内可能回调的方法有onCreate()onStartCommand()onBind()onDestory()

  • 当调用了startService()方法,相应的服务就会被启动且会回调onStartCommand()方法,若服务此前没有被调用则会调用onCreate()方法。stopService()stopSelf()方法可以停止服务。

  • bindService()方法用来获取服务的持久性连接,这时会回调onBind()方法,onBind()方法会返回IBinder对象的实例,使用unbindService()方法用于销毁服务。

  • 当服务销毁时会回调onDestory()

服务的更多技巧

前台服务

1
2
3
4
5
6
7
8
9
10
11
Intent intent = new Intent(this,MainActivity.class);
PendingIntent pi = PendingIntent.getActivity(this,0,intent,0);
Notification notification = new NotificationCompat.Builder(this)
.setContentTitle("This is content title")
.setContentText("This is content Text")
.setWhen(System.currentTimeMillis())
.setSmallIcon(R.mipmap.ic_launcher)
.setLargeIcon(BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher))
.setContentIntent(pi)
.build();
startForeground(1,notification);
  • PendingIntentIntent的封装,满足某些条件或触发某些事件后执行指定的行为

    • 参数一:Context上下文
    • 参数二:requestCode请求码
    • 参数三:Intent意图
    • 参数四:flags
  • Notification是状态通知栏

IntentService

由于服务中的代码都是默认在主线程当中运行,因此需要将处理耗时的逻辑放在子线程中处理。

1
2
3
4
5
6
7
8
9
10
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d("MySerivce","onStartCommand: ");
new Thread(new Runnable() {
@Override
public void run() {
//处理具体的逻辑
}
}).start();
return super.onStartCommand(intent, flags, startId);
}

服务一旦启动就会处于运行状态,因此需要在子线程中加上停止服务的操作

1
2
3
4
5
6
7
8
9
10
11
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d("MySerivce","onStartCommand: ");
new Thread(new Runnable() {
@Override
public void run() {
//处理具体的逻辑
stopSelf();
}
}).start();
return super.onStartCommand(intent, flags, startId);
}

防止程序员忘记启动线程或者在子线程中写停止服务的操作,Android提供了IntentService

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class MyIntentService extends IntentService {
/**
* Creates an IntentService. Invoked by your subclass's constructor.
*
* @param name Used to name the worker thread, important only for debugging.
*/
public MyIntentService(String name) {
super(name);
}

@Override
//操作都在子线程中进行
protected void onHandleIntent(@Nullable Intent intent) {
Log.d("MyIntentService", "Thread id is : "+Thread.currentThread().getId());
}

@Override
public void onDestroy() {
super.onDestroy();
Log.d("MyIntentService", "onDestroy: ");
}
}

服务的实践

DownloadListener

DownloadListener接口用于下载操作

1
2
3
4
5
6
7
public interface DownloadListener {
void onProgress(int progress);
void onSuccess();
void onFailed();
void onPaused();
void onCanceled();
}

DownloadService

下载的服务,主要实现了接口的方法,以及活动与服务绑定时返回的Binder对象,以及消息通知栏的实现。

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
package com.example.servicebestpractice;

import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.os.Binder;
import android.os.Environment;
import android.os.IBinder;
import android.widget.Toast;

import androidx.core.app.NotificationCompat;

import java.io.File;

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));
Toast.makeText(DownloadService.this, "Download Success", Toast.LENGTH_SHORT).show();
}

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

@Override
public void onPaused() {
downloadTask = null;
Toast.makeText(DownloadService.this, "Paused", Toast.LENGTH_SHORT).show();
}

@Override
public void onCanceled() {
downloadTask = null;
stopForeground(true);
Toast.makeText(DownloadService.this, "Canceled", Toast.LENGTH_SHORT).show();
}

};
//完成绑定对象的实例化
private DownloadBinder mBinder = new DownloadBinder();

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

class DownloadBinder extends Binder {

public void startDownload(String url) {
//downloadTask用于处理下载任务
if (downloadTask == null) {
downloadUrl = url;
downloadTask = new DownloadTask(listener);
//将下载地址传入,并启动任务
downloadTask.execute(downloadUrl);
//消息通知栏
startForeground(1, getNotification("Downloading...", 0));
Toast.makeText(DownloadService.this, "Downloading...", Toast.LENGTH_SHORT).show();
}
}

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();
}
getNotificationManager().cancel(1);
stopForeground(true);
Toast.makeText(DownloadService.this, "Canceled", Toast.LENGTH_SHORT).show();
}
}
}

}

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

private Notification getNotification(String title, int progress) {
Intent intent = new Intent(this, MainActivity.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();
}

@Override
public void onCreate() {
super.onCreate();
}
}

DownloadTask

这里利用okhttp对文件进行下载操作,并且由于下载的操作需要耗时,因此放在子线程中运行,但是这里注意修改UI的操作不能再子线程中,因此需要使用publishProgress()方法传递参数实时修改UI效果。采用AsyncTask实现多线程。

AsyncTask执行顺序doInBackground()->onProgressUpdate()->onPostExecute()

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
...
@Override
protected Integer doInBackground(String... params) {
InputStream is = null;
RandomAccessFile savedFile = null;
File file = null;
try {
long downloadedLength = 0; // 记录已下载的文件长度
String downloadUrl = params[0];
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;
}

@Override
protected void onProgressUpdate(Integer... values) {
int progress = values[0];
if (progress > lastProgress) {
listener.onProgress(progress);
lastProgress = progress;
}
}

@Override
protected void onPostExecute(Integer status) {
switch (status) {
case TYPE_SUCCESS:
listener.onSuccess();
break;
case TYPE_FAILED:
listener.onFailed();
break;
case TYPE_PAUSED:
listener.onPaused();
break;
case TYPE_CANCELED:
listener.onCanceled();
default:
break;
}
}

public void pauseDownload() {
isPaused = true;
}


public void cancelDownload() {
isCanceled = true;
}

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

}

Android第一行代码--服务
https://h0pe-ay.github.io/Android第一行代码--服务/
作者
hope
发布于
2023年6月27日
许可协议