学之广在于不倦,不倦在于固志。 ——晋·葛洪
(学问的渊博在于学习时不知道厌倦,而学习不知厌倦在于有坚定的目标)
全面到位的一篇Service------强烈推荐 2018/11/1 更新
001.基本介绍:
00a、概念:
Service是Android中实现程序后台运行的解决方案,四大组件之一
00b、适用场景:
执行一些不需要和用户交互而且要求长期运行的任务,比如:听音乐&看电子书;检测SD卡的变化;记录你的地理位置变化;
00c、特点:
服务的进程依赖于创建服务时所在的应用程序的进程,服务的运行不依赖于任何界面,
注意:
服务默认在主线程运行,我们需要在服务内部手动创建子线程,并在这里执行具体的任务
002.服务的启动与销毁:
00a、步骤:
---> 创建一个类继承Service,Service是一个抽象类,必须重写onBind()方法
---> 清单文件注册对应的Service
<service android:name=".service.MyService"/>
---> 开启Service,具体区别看代码部分:
---> startService();
---> bindService();
---> 销毁Service,具体见代码部分:
---> stopService() / stopSelf()
---> unBindService()
003.代码实战:
00a、startService开启服务
在MainActivity中添加两个按钮,一个用于开启服务,一个用于销毁服务,添加几个生命周期的打印信息。注意startService()和stopService()方法都是定义在Context类当中的,所以可以在MainActivity中直接调用这两个方法
public class MyService extends Service {
private static final String TAG = "MyService";
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.i(TAG, "onBind: ");
return null;
}
//创建服务时调用
@Override
public void onCreate() {
super.onCreate();
Log.i(TAG, "onCreate: ");
}
//在服务执行操作
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i(TAG, "onStartCommand: ");
return super.onStartCommand(intent, flags, startId);
}
//销毁服务时调用
@Override
public void onDestroy() {
super.onDestroy();
Log.i(TAG, "onDestroy: ");
}
}
<service android:name=".service.MyService"/>
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//点击开启服务
findViewById(R.id.bt_start).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//构建出了一个Intent对象,并调用startService()方法来启动MyService
Intent startIntent = new Intent(MainActivity.this, MyService.class);
startService(startIntent);
}
});
//点击关闭服务
findViewById(R.id.bt_start).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//构建出了一个Intent对象,并调用stopService()方法来停止MyService
Intent stopIntent = new Intent(MainActivity.this, MyService.class);
startService(stopIntent);
}
});
}
}
点击了Start Service,打印如下,执行了onCreate和onStartCommand
15555-15555/com.test.okamiy.mythread I/MyService: onCreate:
15555-15555/com.test.okamiy.mythread I/MyService: onStartCommand:
我接着连续点击了3下Start Service,打印如下,只执行了onStartCommand
15555-15555/com.test.okamiy.mythread I/MyService: onStartCommand:
15555-15555/com.test.okamiy.mythread I/MyService: onStartCommand:
15555-15555/com.test.okamiy.mythread I/MyService: onStartCommand:
注意:
虽然每次调用一次startService()方法,onstartCommand()方法就会以执行一次,但实际上每个服务都只会存在一个实例。所以不管你调用了多少次startService()方法,只需调用一次stopService()或stopself()方法,服务就会停止
经多次测试得出,onCreate()方法只会在Service第一次被创建的时候调用,而onStartCommand()方法在每次启动服务的时候都会调用
此时我们可以在手机运行的应用程序查看,我们的服务已经运行起来了
点击了Stop Service,打印如下,执行了onDestroy,服务已销毁
15555-15555/com.test.okamiy.mythread I/MyService: onDestroy:
总结:
---> startService开启的服务,停止服务有两种方式:
1> 在Service的内部任何地方调用stopSelf()即可停止服务
2> 在外部调用stopService()即可停止服务
---> startService开启服务,就可以在onCreate()或onStartCommand()方法里去执行一些具体的逻辑了,此时Service和Activity的关系并不大,只是Activity通知了Service一下:“你可以启动了。”然后Service就去忙自己的事情了。但Activity并不知道服务到底去做了什么,以及如何完成。
---> onStartCommand方法执行时,返回的是一个int类型。这个整型可以有三个返回值:
1> START_NOT_STICKY:“非粘性的”。使用这个返回值时,如果在执行完onStartCommand方法后,服务被异常kill掉,系统不会自动重启该服务
2> START_STICKY:如果Service进程被kill掉,保留Service的状态为开始状态,但不保留递送的intent对象。随后系统会尝试重新创建Service,由于服务状态为开始状态,所以创建服务后一定会调用onStartCommand(Intent,int,int)方法。如果在此期间没有任何启动命令被传递到Service,那么参数Intent将为null。
3> START_REDELIVER_INTENT:重传Intent。使用这个返回值时,系统会自动重启该服务,并将Intent的值传入。
00b、bindService绑定服务
基于上面的代码。在MainActivity中再添加2个按钮,bind_service,unbind_service,添加点击事件
public class MyService extends Service {
private static final String TAG = "MyService";
private DownloadBinder mBinder = new DownloadBinder();
//和Activity通信
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.i(TAG, "onBind: ");
return mBinder;
}
//创建服务时调用
@Override
public void onCreate() {
super.onCreate();
Log.i(TAG, "onCreate: ");
}
//在服务执行操作
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i(TAG, "onStartCommand: ");
return super.onStartCommand(intent, flags, startId);
}
//销毁服务时调用
@Override
public void onDestroy() {
super.onDestroy();
Log.i(TAG, "onDestroy: ");
}
//Binder类,用于模拟下载,保存进度
public class DownloadBinder extends Binder {
public void startDownload() {
Log.i(TAG, "startDownload: " + "开始下载");
}
public int getProgress() {
Log.i(TAG, "getProgress: " + "获取了下载进度");
return 0;
}
}
}
<service android:name=".service.MyService"/>
public class MainActivity extends AppCompatActivity {
private boolean mBind = false; //一开始,并没有和Service绑定.这个参数是用来显示绑定状态
private MyService.DownloadBinder downloadBinder;
//匿名内部类:服务连接对象,有了这个实例我们就和Service联系紧密
//如果当前Activity与服务连接成功后,服务会回调onServiceConnected()方法
private ServiceConnection mConnection = new ServiceConnection() {
//和服务绑定成功后,服务会回调该方法
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
downloadBinder = (MyService.DownloadBinder) service;
downloadBinder.startDownload();
downloadBinder.getProgress();
mBind = true;
}
//当服务异常终止时会调用。注意,解除绑定服务时不会调用
@Override
public void onServiceDisconnected(ComponentName name) {
mBind = false; //服务异常终止时,状态为未绑定
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//点击绑定服务:将MainActivity和MyService进行绑定
findViewById(R.id.bind_start).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(MainActivity.this, MyService.class);
//参数3:标志位,BIND_AUTO_CREATE 表示在Activity和Service建立关联后会自动创建Service
bindService(intent, mConnection, BIND_AUTO_CREATE);
}
});
//点击解绑服务
findViewById(R.id.bind_stop).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mBind) {
unbindService(mConnection);
mBind = false;
}
}
});
//点击开启服务
findViewById(R.id.bt_start).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//构建出了一个Intent对象,并调用startService()方法来启动MyService
Intent startIntent = new Intent(MainActivity.this, MyService.class);
startService(startIntent);
}
});
//点击关闭服务
findViewById(R.id.bt_stop).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//构建出了一个Intent对象,并调用stopService()方法来停止MyService
Intent stopIntent = new Intent(MainActivity.this, MyService.class);
stopService(stopIntent);
}
});
}
}
点击bind Service,打印如下,执行了onCreate、onBind,并执行了startDownload和getProgress方法,说明我们在Activity里面已经调用了Service的方法
21456-21456/com.test.okamiy.mythread I/MyService: onCreate:
21456-21456/com.test.okamiy.mythread I/MyService: onBind:
21456-21456/com.test.okamiy.mythread I/MyService: startDownload: 开始下载
21456-21456/com.test.okamiy.mythread I/MyService: getProgress: 获取了下载进度
点击unbind Service,打印如下,执行了onDestory,解绑了Service
21456-21456/com.test.okamiy.mythread I/MyService: onDestroy:
总结:
---> bindService绑定的服务,需要UnbindService解除绑定
--->bindService绑定服务,首先需要创建ServiceConnection的匿名类,重写了onServiceConnected()方法和onServiceDisconnected()方法,这两个方法分别会在Activity与Service建立关联和解除关联的时候调用;在onServiceConnected()方法中,我们又通过向下转型得到了MyBinder的实例,有了这个实例,我们就实现了Activity指挥Service干什么Service就去干什么的功能
---> Service服务在整个应用程序范围内都是通用的,即MyService不仅可以和MainActivity建立关联,还可以和任何一个Activity建立关联,而且在绑定完成后它们都可以获取到相同的DownloadBinder实例
注意:
---> 在我们点击了bind Service,同时也点击了startService,我们需要分别点击unbindService和stopService才能销毁Service,没有先后顺序,说明,一个Service必须要在既没有和任何Activity关联又处理停止状态的时候才会被销毁(点击Stop Service按钮只会让Service停止,点击Unbind Service按钮只会让Service和Activity解除关联)
---> 记得在Service的onDestroy()方法里去清理掉那些不再使用的资源,防止在Service被销毁后还会有一些不再使用的对象仍占用着内存
startService和bindService区别:
---> 生命周期:
1> 通过started方式开启的服务会一直运行在后台,需要由Service组件本身(stopSelf)或外部组件(stopService)来停止服务,service才会结束运行
2> bind方式绑定的服务,生命周期就要依赖绑定的组件
--->参数传递:
1> started服务可以给启动的服务对象传递参数,但无法获取服务中方法的返回值
2> bind服务可以给启动的服务对象传递参数,也可以通过绑定的业务对象获取返回结果
所以,更多的时候是2种方式配合使用:第一次先使用started方式来启动一个服务,之后可以使用bind的方式绑定服务,从而可以直接调用业务方法获取返回值
00c、创建前台服务:
如果你希望Service可以一直保持运行状态,而不会由于系统内存不足的原因导致被回收,就可以考虑使用前台Service。前台Service和普通Service最大的区别就在于,它会一直有一个正在运行的图标在系统的状态栏显示,下拉状态栏后可以看到更加详细的信息,非常类似于通知的效果
比如:墨迹天气,它的Service在后台更新天气数据的同时,还会在系统状态栏一直显示当前天气的信息
只需要修改上面MyService里面的onCreate方法即可,添加如下代码即可变成前台service点击startService或者bindService就会在通知栏出现我们的通知
//创建服务时调用
@Override
public void onCreate() {
super.onCreate();
//创建通知
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
builder.setSmallIcon(R.mipmap.ic_launcher);
builder.setContentTitle("Notification comes");
builder.setContentText("Okamiy send message");
Notification notification = builder.build();
//让MyService变成前台Service,并在系统状态栏显示 参数1:通知的id,参数2:notification对象
startForeground(1, notification);
Log.i(TAG, "onCreate: ");
}
00d、IntentService:
一个服务中的代码默认运行在主线程中,如果直接在服务里执行一些耗时操作,容易造成ANR异常,所以就需要用到多线程的知识了,因此一个标准的服务如下:
public class MyService extends Service {
private static final String TAG = "MyService";
//和Activity通信
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.i(TAG, "onBind: ");
}
//创建服务时调用
@Override
public void onCreate() {
super.onCreate();
Log.i(TAG, "onCreate: ");
}
//在服务执行操作
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i(TAG, "onStartCommand: ");
new Thread(new Runnable() {
@Override
public void run() {
//处理具体的逻辑
... stopSelf(); //服务执行完毕后自动停止
}
}).start();
return super.onStartCommand(intent, flags, startId);
}
//销毁服务时调用
@Override
public void onDestroy() {
super.onDestroy();
Log.i(TAG, "onDestroy: ");
}
}
虽说上面的这种写法并不复杂,但总会有一些程序猿忘记开启线程,或者忘记调用stopSelf()方法。为了可以简单地创建一个异步的、会自动停止的服务,Android专门提供了一个IntentService类,这个类就很好的解决了上面所提到的两种尴尬。另外,可以启动IntentService多次,而每一个耗时操作会以工作队列的方式在IntentService的onHandleIntent()回调方法中执行,并且每次只会执行一个工作线程,执行完第一个后,再执行第二个,以此类推。
public class MyIntentService extends IntentService {
private static final String TAG = "MyIntentService";
public MyIntentService() {
//调用父类的有参构造,这里我们手动给服务起个名字为:MyIntentService
super("MyIntentService");
}
/**
* 该方法执行在子线程,可以进行耗时操作和处理一些逻辑
*
* @param intent
*/
@Override
protected void onHandleIntent(@Nullable Intent intent) {
for (int i = 0; i < 3; i++) {
try {
Log.i(TAG, "onHandleIntent: " + "IntentService的线程: " + Thread.currentThread().getId());
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@Override
public void onDestroy() {
super.onDestroy();
Log.i(TAG, "onDestroy: ");
}
}
<service android:name=".service.MyIntentService"/>
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//点击启动IntentService
findViewById(R.id.start_IntentService).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.i(TAG, "onClick: " + "MainActivity的线程:" + Thread.currentThread().getId());
Intent intent = new Intent(MainActivity.this, MyIntentService.class);
startService(intent);
}
});
}
}
点击IntentService,打印信息如下,说明:MyIntentService是运行在子线程的,并且在执行完耗时操作或者相应的逻辑之后自动执行了Service的销毁,集开启线程和自动停止于一身
/com.test.okamiy.mythread I/MainActivity: onClick: MainActivity的线程:1
/com.test.okamiy.mythread I/MyIntentService: onHandleIntent: IntentService的线程: 3503 /com.test.okamiy.mythread I/MyIntentService: onHandleIntent: IntentService的线程: 3503
/com.test.okamiy.mythread I/MyIntentService: onHandleIntent: IntentService的线程: 3503 /com.test.okamiy.mythread I/MyIntentService: onDestroy:
00e、Service和Thread:
---> Service和Thread之间没有任何关系!
1> Thread我们大家都知道,是用于开启一个子线程,在这里去执行一些耗时操作就不会阻塞主线程的运行
2> 而Service我们最初理解的时候,总会觉得它是用来处理一些后台任务的,一些比较耗时的操作也可以放在这里运行。但是,Service默认是运行在主线程的,可以自行打印测试;
3> Android中的后台就是指,它的运行是完全不依赖UI的,即使Activity被销毁,或者程序被关闭,只要进程还在,Service就可以继续运行。比如说一些应用程序,始终需要与服务器之间始终保持着心跳连接,就可以使用Service来实现,
4> 在Service里面执行耗时操作,是指在Service里面起一个线程Thread执行的耗时操作。
在Service里面起线程执行耗时操作的原因:
这是因为Activity很难对Thread进行控制,当Activity被销毁之后,就没有任何其它的办法可以再重新获取到之前创建的子线程的实例。而且在一个Activity中创建的子线程,另一个Activity无法对其进行操作。但是Service就不同了,所有的Activity都可以与Service进行关联,然后可以很方便地操作其中的方法,即使Activity被销毁了,之后只要重新与Service建立关联,就又能够获取到原有的Service中Binder的实例。因此,使用Service来处理后台任务,Activity就可以放心地finish,完全不需要担心无法对后台任务进行控制的情况
---> 基于上述一个比较标准的Service就产生了:
public class MyService extends Service {
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
new Thread(new Runnable() {
@Override
public void run() {
// 开始执行后台任务
...
stopSelf();//任务完成之后销毁服务
}
}).start();
return super.onStartCommand(intent, flags, startId);
}
class MyBinder extends Binder {
public void startDownload() {
new Thread(new Runnable() {
@Override
public void run() {
// 执行具体的下载任务
...
stopSelf();//任务完成之后销毁服务
}
}).start();
}
}
}
004.个人总结可能有误,欢迎指正,参考篇:
00a.郭霖大神(基础到进阶): 传送门---郭霖大神
00b.这篇是博客园的(全面而细致):传送门---细致入微
Last:欢迎探讨学习