Android基础 - Service实例深入理解
Service生命周期
上篇学习了Service一些基础知识,知道了在Service做耗时任务(网络请求、IO等等)需开启一个线程,否则会导致ANR,接下来通过实例加深Service的使用理解。
Service种类及特点
Service几种典型实例
下面以模拟下载文件为例子
1、不可交互的后台服务
不可交互的后台服务即是普通的Service,是通过startService方法启动,其生命周期顺序是:onCreate -> onStartCommand -> onDestroy
/**
* author:hzw on 2018/7/4
* desc: 模拟下载 - 不可交互
*/
public class DownloadService extends Service {
private static final String TAG = "DownloadService";
public static final String FILE_SIZE="file_size";
private Thread mDownloadThread;
@Override
public void onCreate() {
super.onCreate();
Log.i(TAG, "onCreate: ");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
int fileSize = intent.getIntExtra(FILE_SIZE, 0);
Log.e(TAG, "onStartCommand: "+fileSize);
//创建线程,模拟下载
DownloadRunnable download = new DownloadRunnable(fileSize);
mDownloadThread = new Thread(download);
mDownloadThread.start();
return super.onStartCommand(intent, flags, startId);
}
/**
* 定义一个下载线程
*/
private class DownloadRunnable implements Runnable{
//文件总大小
private int fileSize;
//下载进度大小
private int downloadSize=0;
DownloadRunnable(int fileSize) {
this.fileSize=fileSize;
}
@Override
public void run() {
try {
//模拟下载
while (downloadSize<fileSize){
downloadSize +=100;
Log.d(TAG, "文件总大小:"+fileSize+" 已下载了:"+downloadSize);
Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onDestroy() {
super.onDestroy();
Log.i(TAG, "onDestroy: ");
}
}
在AndroidMainfest文件中声明Service
<service android:name=".service.DownloadService"/>
注:
Service若没有在Mainfest文件中声明,并不会像Activity报崩溃异常,
对于显式Intent启动的Service,在mainfest中未声明,会提示”Unable to start service Intent”错误信息,
所以在开发中使用时需记住这点,少走弯路。
启动与停止Service
public void onClick(View view) {
Intent intent = new Intent(this, DownloadService.class);
intent.putExtra(DownloadService.FILE_SIZE,1000);
switch (view.getId()){
case R.id.start_service:
startService(intent);
break;
case R.id.stop_service:
stopService(intent);
break;
}
}
最后输出结果:
注:
在Service关闭销毁中,如果没有在onDestroy中停止关闭线程,线程执行操作会一直执行下去,也就是下载任务会一直存在,
因此在我们使用了Service启动了子线程执行一些耗时任务时,在服务结束onDestroy中必须停止线程关闭任务的执行。
是否需要在销毁停止线程,具体还是要根据业务需求而定,如下:
@Override
public void onDestroy() {
super.onDestroy();
Log.i(TAG, "onDestroy: ");
if (!mDownloadThread.isInterrupted())mDownloadThread.interrupt();
}
我们在使用onStartCommand方法时,它有一个返回参数,那么这个返回值究竟有什么作用呢,接下来看看源码:
public @StartResult int onStartCommand(Intent intent, @StartArgFlags int flags, int startId) {
onStart(intent, startId);
return mStartCompatibility ? START_STICKY_COMPATIBILITY : START_STICKY;
}
其中有三个参数
- intent:显式启动意图
- flags:默认情况下是0,对应的常量名为START_STICKY_COMPATIBILITY
- startId:startService请求标识,在多次调用startService方法情况下,startId的值会呈现递增(1,2,3…)
mStartCompatibility默认是false,说明onStartCommand默认返回值是START_STICKY,onStartCommand返回值有三种情况:
- START_STICKY:当系统内存不足时导致进程杀死,接下来未来的某个时间内,Service会尝试重建,此时在onStartCommand方法中的Intent会为空,比较适合没有参数的服务。
- START_NOT_STICKY:当Service因为内存不足而被系统进程杀死后,接下来未来的某个时间内,即使系统内存足够可用,系统也不会尝试重新创建此Service。
- START_REDELIVER_INTENT:与START_STICKY类似,唯一不同是当重建Service时,onStartCommand方法中的Intent是非空的,比较适合一些紧急恢复数据的场景。 -
2、可交互的后台服务
所谓可交互的后台服务就是使用bindService启动服务,然后通过ServiceConnection获取IBinder代理对象,通过代理对象就可以得到Service的执行结果。其生命周期:onCreate -> onBind -> onUnbind ->onDestroy ,其中ServiceConnection的onServiceConnected方法会在onBind之后被调用。
Service代码:
**
* author:hzw on 2018/7/4
* desc: 模拟下载 - 可交互的服务
*/
public class DownloadService2 extends Service {
private static final String TAG = "DownloadService";
public static final String FILE_SIZE = "file_size";
@Override
public void onCreate() {
super.onCreate();
Log.i(TAG, "onCreate: ");
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
int fileSize = intent.getIntExtra(FILE_SIZE, 0);
Log.e(TAG, "onBind: " + fileSize);
return new DownloadBinder(fileSize);
}
@Override
public boolean onUnbind(Intent intent) {
Log.i(TAG, "onUnbind: ");
return super.onUnbind(intent);
}
@Override
public void onDestroy() {
super.onDestroy();
Log.i(TAG, "onDestroy: ");
}
/**
* 用于监听下载
*/
public interface OnDownloadListener{
void onDownloadProgressUpdate(int progress);
void onDownloadComplete();
}
public class DownloadBinder extends Binder {
private int fileSize;
private DownloadTask mDownloadTask;
DownloadBinder(int fileSize) {
this.fileSize=fileSize;
}
public void startDownload(OnDownloadListener onDownloadListener){
//创建异步任务
mDownloadTask = new DownloadTask(onDownloadListener);
//执行任务
mDownloadTask.execute(fileSize);
}
/**
* 注意在onDestroy中取消异步操作,否则Service任务会一直存在
*/
public void stopDownload(){
mDownloadTask.cancel(true);
}
}
private class DownloadTask extends AsyncTask<Integer, Integer, String> {
private OnDownloadListener mOnDownloadListener;
public DownloadTask(OnDownloadListener onDownloadListener) {
this.mOnDownloadListener=onDownloadListener;
}
@Override
protected String doInBackground(Integer... integers) {
int fileSize = integers[0];
int downloadSize = 0;
String progress = "0%";
try {
//模拟下载
while (downloadSize < fileSize) {
downloadSize += 200;
//发布进度
publishProgress(downloadSize);
progress = downloadSize / fileSize * 100 + "%";
Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return progress;
}
@Override
protected void onProgressUpdate(Integer... values) {
if (mOnDownloadListener!=null){
mOnDownloadListener.onDownloadProgressUpdate(values[0]);
}
super.onProgressUpdate(values);
}
@Override
protected void onPostExecute(String s) {
super.onPostExecute(s);
if (mOnDownloadListener!=null){
mOnDownloadListener.onDownloadComplete();
}
}
}
}
Activity代码:
public class MainActivity extends AppCompatActivity implements DownloadService2.OnDownloadListener {
private static final String TAG = "DownloadService";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void onClick(View view) {
Intent intent = new Intent(this, DownloadService2.class);
intent.putExtra(DownloadService2.FILE_SIZE,1000);
switch (view.getId()){
case R.id.bind_service:
bindService(intent,mServiceConnection,BIND_AUTO_CREATE);
break;
case R.id.unbind_service:
unbindService(mServiceConnection);
break;
}
}
private ServiceConnection mServiceConnection=new ServiceConnection(){
/**
* 服务绑定成功被调用
* @param name 已绑定服务的组件名
* @param service 已绑定的Binder
*/
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.i(TAG, "onServiceConnected: ");
DownloadService2.DownloadBinder binder=(DownloadService2.DownloadBinder) service;
binder.startDownload(MainActivity.this);
}
/**
* 服务断开连接会调用,一般是进程被杀死的情况下。
* @param name 已绑定服务的组件名
*/
@Override
public void onServiceDisconnected(ComponentName name) {
Log.e(TAG, "onServiceDisconnected: ");
}
};
@Override
public void onDownloadProgressUpdate(int progress) {
Log.i(TAG, "onDownloadProgressUpdate: "+progress);
}
@Override
public void onDownloadComplete() {
Log.i(TAG, "onDownloadComplete: ");
}
}
从绑定服务到下载完成,最后结果解绑的过程:
用bindService方式启动服务,其生命周期会依附在Content的启动组件上,就是组件被销毁了,Service也会随之销毁。
3、前台服务
所谓前台服务,就是把进程的优先级提高了,一般是不可见状态变成了可见状态,前台服务会有一个一直运行在状态栏的图标显示,类似消息通知栏的效果。
前台服务的优点就是提高Service进程优先级,我们知道后台服务,在系统内存不足的情况,很容易杀死进程,而前台服务一定程度上可以弥补后台服务的缺陷,在Service中一个是通过startForeground方法来设置前台服务。
startForeground方法有两个参数:
public final void startForeground(int id, Notification notification) {
try {
mActivityManager.setServiceForeground(
new ComponentName(this, mClassName), mToken, id,
notification, 0);
} catch (RemoteException ex) {
}
}
- id:通知的标识符
- notification:显示的通知
还是以上面的例子进行设置,把后台服务提升为前台服务。
@Override
public void onCreate() {
super.onCreate();
Log.i(TAG, "onCreate: ");
Intent intent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
//处理在Android8.0不显示问题
Notification.Builder builder = new Notification.Builder(this);
Notification mNotification;
if (Build.VERSION.SDK_INT>=Build.VERSION_CODES.O){
NotificationChannel channel = new NotificationChannel("notification_channel", "huawei", NotificationManager.IMPORTANCE_LOW);
assert notificationManager != null;
notificationManager.createNotificationChannel(channel);
builder.setChannelId("notification_channel");
mNotification = builder.setSmallIcon(R.mipmap.ic_launcher_round)
.setTicker("hello world")
.setWhen(System.currentTimeMillis())
.setContentText("模拟下载")
.setContentTitle("正在下载中……")
.setContentIntent(pendingIntent).build();
}else {
mNotification = builder.setSmallIcon(R.mipmap.ic_launcher_round)
.setTicker("hello world")
.setWhen(System.currentTimeMillis())
.setContentText("模拟下载")
.setContentTitle("正在下载中……")
.setContentIntent(pendingIntent).build();
}
startForeground(2, mNotification);
}
注意:1、使用bindService方式启动服务,如果Content的启动组件被销毁了,状态栏的通知也会消失。
2、可以通过stopsForeground方法取消通知栏,即把前台服务降到后台服务。