Service知识点总结
更新2020年05月11日10:07:56:加入onStartCommand于2.2.1中。
前言
参考项目:https://github.com/wodongx123/ServiceDemo
正文的代码源码。
1. Service 简介
Service作为Android四大组件之一,最大的特点就是它不像Activity一样需要交互窗口,如果有需要在后台执行的任务的话,就可以用Service来进行。
- Service的方法均由主线程来执行,所以不能执行太过耗时的操作。
2. Service的启动方式
在开始之前,我new了一个Project,并且自带一个 Empty Activity,然后不做任何操作。
2.1 startService()
正常的启动服务的形式,通过调用contextWrapper的startService的方法直接启动服务。 一般情况下我们只要在Activity中调用startService方法就好。
-
new一个继承Service的类,然后重写其中的方法。
public class MyService extends Service { private static final String TAG = "MyService"; public MyService() { } @Override public void onCreate() { Log.i(TAG, "onCreate: " + toString()); super.onCreate(); } @Override public IBinder onBind(Intent intent) { return null; } @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.i(TAG, "onStartCommand: " + toString()); return super.onStartCommand(intent, flags, startId); } @Override public void onDestroy() { Log.i(TAG, "onDestroy: "); super.onDestroy(); }
-
在Activity中调用Intent,然后加上Service,最后启动。
public class MainActivity extends AppCompatActivity implements View.OnClickListener { Button btnStartService; Button btnStopService; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //这里为了方便,就随意的加了一些按钮来控制,xml文件也就这些按钮 btnStartService = findViewById(R.id.btn_start); btnStopService = findViewById(R.id.btn_stop); btnStartService.setOnClickListener(this); btnStopService.setOnClickListener(this); } @Override public void onClick(View v) { switch (v.getId()){ case R.id.btn_start: Intent start = new Intent(this, MyService.class); //startService是ContextWrapper的方法,但是Activity有继承它,可以直接调用 startService(start); break; case R.id.btn_stop: Intent stop = new Intent(this, MyService.class); stopService(stop); break; } } }
-
最后,如果你是通过new -> Service -> Service来创建Service的话就没必要,但是如果你是普通的new一个Java Class手写代码的话,你需要在AndroidManifest.xml中再添加我们的Service。
-
如果哪天你发现你的代码都没问题,但是就是运行不了Service,就检查一下AndroidManifest.xml中添加了没有!
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.servicedemo"> <application ...... > ...... <service android:name=".MyService" android:enabled="true" android:exported="true"></service> </application> </manifest>
name指向我们的Service类,enable表示是否可以使用,exported表示是否可以被其他应用访问。
- 最后运行程序,多次点击startService按钮后,再点击stopService按钮,得到如下结果。
可以发现,多次点击startService按钮后,onCreate方法没有重复执行,onStartCommand却会多次执行,且后面的toString所带的内存地址相同,表示是同一个对象。
2.2.1 onStartCommand()
在startService启动服务的方法中,主要逻辑都在onStartCommand方法中实现。接下来就分析onStartCommand这个方法。
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return super.onStartCommand(intent, flags, startId);
}
传入参数:
- intent:启动时附带的意图,可以在里面封装数据。
- flags:表明启动请求时是否有附带内容,可选值为0,START_FLAG_REDELIVERY,START_FLAG_RETRY。
- 0:一般情况下传入值为0。
- START_FLAG_REDELIVERY:onStartCommand的返回值确定为START_REDELIVER_INTENT,且服务在被kill之前,会调用stopSelf()。
- START_FLAG_RETRY:如果onStartCommand后一直没有返回值,会重新调用onStartCommand。
- startId:指明当前服务的唯一id,与停止服务时的stopSelfResult(int startId)配合使用。
返回值,主要是用于判断服务在异常退出时是否重启,何时重启的问题,有以下四个取值:
- START_STICKY:如果服务在启动后被killed(因为内存不足等各种原因),系统就会在稍后重启这个服务,但是重启的时候不保留intent,所以onStartCommand方法中intent为null。
- START_NOT_STICKY:如果服务在启动后被killed,不会重启。
- START_START_STICKY_COMPATIBILITY:START_STICKY的兼容版本,但是不能保证一定会重启。
- START_REDELIVER_INTENT:START_STICKY的传入intent版本,intent为之前运行时的值。
2.2 bindService()
startService的缺点就是,Service和Activity之间的交互和通信太少,除了startService和stopService以外,不能通过其他的方法对Service进行操作。这个时候就要用到BindService。BindService通过Binder实现对Service的操作。
- 实现Service内部的onBind方法,为此,需要先定义一个Binder类。为了让这个Binder稍微有点现实意义,我让他做一个从0数到100的操作。
public class MyService extends Service { private static final String TAG = "MyService"; //把int定义在Service中 int i=0; private CountBinder countBinder = new CountBinder(); //省略若干方法 ..... @Override public IBinder onBind(Intent intent) { Log.i(TAG, "onBind: " + toString()); return countBinder; } @Override public boolean onUnbind(Intent intent) { Log.i(TAG, "onUnbind: "); return super.onUnbind(intent); } class CountBinder extends Binder{ //由于Service是由主线程来执行,所以要开一个子线程来计算 Thread countThread = new Thread(new Runnable() { @Override public void run() { for (; i<100; i++){ Log.i(TAG, "startCount: " + Thread.currentThread() + " " + i); try { //数一下停一下,不然一下就数完了 Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } } }); public void startCount(){ countThread.start(); } } }
- 在Activity中,创建一个ServiceConnection,这个就是BindService最重要的类,最后BindService。
//省略了代码中一些无关内容 public class MainActivity extends AppCompatActivity implements View.OnClickListener { private static final String TAG = "MainActivity"; Button btnBindService; Button btnUnbindService; private ServiceConnection conn = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { Log.i(TAG, "onServiceConnected: "); MyService.CountBinder binder = (MyService.CountBinder)service; binder.startCount(); } @Override public void onServiceDisconnected(ComponentName name) { Log.i(TAG, "onServiceDisconnected: "); } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //我在xml文件中又加了两个按钮 btnBindService = findViewById(R.id.btn_bind); btnUnbindService = findViewById(R.id.btn_unbind); btnBindService.setOnClickListener(this); btnUnbindService.setOnClickListener(this); } @Override public void onClick(View v) { switch (v.getId()){ case R.id.btn_bind: Intent bind = new Intent(this, MyService.class); //bindService需要传入ServiceConnection bindService(bind, conn, BIND_AUTO_CREATE); break; case R.id.btn_unbind: unbindService(conn); break; } } }
- 运行,点击一次BindService按钮后,再点击一次unBindService,结果如下:
虽然服务是关了,但是线程没有关掉,如果要关的话,就要写在onDestroy()里面,就可以关了,这里先不写。
2.3 Service的生命周期
根据不同的使用方法,Service有不同的生命周期。
3. IntentService
刚刚有说到,Service的主要方法都是在主线程中执行,那么对于Service而言不能执行耗时过长的任务,这样就有点限制Service的可用性了。虽然也可以主动在Service内部new Thread来使用,不过Android提供了IntentService这个类。
IntentService继承Service,内部正好就封装了HandlerThread和Handler,可以将任务分发给子线程来完成。
- IntentService内部有一个HandlerThread和Handler,这两个搭配将任务发给子线程完成。
- onStartCommand方法被内部实现了,我们只需要将主逻辑实现在onHandleIntent()即可。
- onBind方法内部返回是return null,如果我们手动实现功能的话,就不会执行多线程了。
-
还是以从0数到100为例,先实例化一个类继承IntentService。
public class MyIntentService extends IntentService { private static final String TAG = "MyIntentService"; int i; public MyIntentService() { super("111"); //super()中的参数就是IntentService内部线程的名字 Log.i(TAG, "MyIntentService: "); } //主要逻辑都放在这里处理 @Override protected void onHandleIntent(@Nullable Intent intent) { Log.i(TAG, "onHandleIntent: " + Thread.currentThread().getName()); for (; i<100; ){ i++; Log.i(TAG, "onHandleIntent: " + i); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } } @Override public void onDestroy() { Log.i(TAG, "onDestroy: "); super.onDestroy(); } }
-
由于IntentService继承Service,调用方法也同Service。
public class MainActivity extends AppCompatActivity implements View.OnClickListener { private static final String TAG = "MainActivity"; Button btnStartIntentService; Button btnStopIntentService; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); btnStartIntentService = findViewById(R.id.btn_start_intentservice); btnStopIntentService = findViewById(R.id.btn_stop_intentservice); btnStartIntentService.setOnClickListener(this); btnStopIntentService.setOnClickListener(this); } @Override public void onClick(View v) { switch (v.getId()){ case R.id.btn_start_intentservice: Intent startIntent = new Intent(this, MyIntentService.class); startService(startIntent); break; case R.id.btn_stop_intentservice: Intent stopIntent = new Intent(this, MyIntentService.class); stopService(stopIntent); break; } } }
-
最后在AndroidManifest.xml中补上Service。
<application ...... > ...... <service android:name=".MyIntentService" android:enabled="true" android:exported="true"></service> </application>
-
先点击startService按钮,过一会点击stopService按钮,结果这样。
其他
为什么说bindService比startService容易通信
在Java中,如果我们要访问一个对象,就必须要持有这个对象的引用,反过来说如果没有这个对象的引用,就不能访问这个对象。
Object o = new Object();
o.func();
但是对于startService而言,这是个void方法,没有返回Service的对象,所以你无法去访问Service的内部数据(除了onStartCommand以外)。
MyService myService = null;
Intent intent = new Intent(MainActivity.this, MyService.class);
//没法给myService赋值,无法持有引用
startService(intent);
但是bindService不同于startService的是,它使用了ServiceConnection这个类,看它其中的onServiceConnected方法。
private ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.i(TAG, "onServiceConnected: ");
MyService.CountBinder binder = (MyService.CountBinder)service;
binder.startCount();
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.i(TAG, "onServiceDisconnected: ");
}
};
这个方法传入了一个IBinder对象,而这个IBinder对象就是Service内部的那个Binder对象。当他传入了这个对象时,我们就可以持有这个对象的引用。同时也可以访问其内部的方法。
我以正文的从0数到100为例,现在添加一个获取当前的数字的功能。
-
在Binder内部增加一个获取值的方法。
public class MyService extends Service { int i=0; private CountBinder countBinder = new CountBinder(); @Override public IBinder onBind(Intent intent) { return countBinder; } class CountBinder extends Binder{ ...... public int getI(){ return i; } } }
-
在MainActivity中添加一个MyService.CountBinder的对象,在onServiceConnected的时候赋值。
public class MainActivity extends AppCompatActivity implements View.OnClickListener { MyService.CountBinder binder; private ServiceConnection conn = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { Log.i(TAG, "onServiceConnected: "); binder = (MyService.CountBinder)service; binder.startCount(); } @Override public void onServiceDisconnected(ComponentName name) { Log.i(TAG, "onServiceDisconnected: "); } }; }
-
最后在MainActivity中调用binder的getI方法。
public class MainActivity extends AppCompatActivity implements View.OnClickListener { private static final String TAG = "MainActivity"; Button btnGetI; MyService.CountBinder binder; ....... @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); btnGetI = findViewById(R.id.btn_get_i); btnGetI.setOnClickListener(this); } @Override public void onClick(View v) { switch (v.getId()){ ...... case R.id.btn_get_i: if (binder != null) binder.getI(); break; } } }
-
通过这样的方法,就可以访问Service的内部方法和变量了,所以这是为什么BindService()比startService()容易通信的原因。
参考材料
Service由浅到深——AIDL的使用方式 - CNblog
https://www.cnblogs.com/huangjialin/p/7738104.html
Service 生命周期 - CNBlog
https://www.cnblogs.com/huihuizhang/p/7623760.html
Android IntentService使用介绍以及原理分析 - CSDN
https://www.jianshu.com/p/8c4181049564
Service-服务(二)onStartCommand()详解 - 简书
https://www.jianshu.com/p/d484e1ef3639