Android中服务(Service)的基本用法
一、定义服务
1.在Android Studio中定义服务非常简单
如下:
- 新建一个项目Demo。
- 右击com.example.demo -> New -> Service -> Service.
- 在弹出的对话框中填写服务名称。其中,Exported属性表示是否允许其他程序访问此服务;Enabled 属性表示是否启用这个服务。
- 点击Finish完成创建
代码如下:
public class MyService extends Service {
public MyService() {
}
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
}
通常需要重写的方法有:
- onCreate():服务第一次创建时调用。
- onStartCommand():每次服务启动时调用。
- onDestroy():服务销毁时调用。
注意:Service与Activity的比较
- 每一个服务都需要像Activity一样在AndroidManifest.xml文件中进行注册。
- Service与Activity的区别在于:Service一直在后台运行,没有界面,所以不会到前台来,一旦Service被启动,它与Activity一样,也是有自己的生命周期的。
- Service与Activity都是从Context派生出来的,因为都可以调用Context里定义的方法,如:getResources(),getContentResolver()等方法。
2.在 AndroidManifest.xml 中进行配置
定义了 Service 之后,接下来应该是在 AndroidManifest.xml 中进行配置。
虽然在Android Studio中使用菜单创建的服务(Service),会自动配置 AndroidManifest.xml,但有这种配置只是简单的,并不能满足更多的需求,要想实现更多的功能,则需要对 AndroidManifest.xml 中的配置方法做进一步的了解。
<service> 的常用属性:
- name:指定Service的实现类名。
- exported:指定该 Service 是否能被其他 APP 启动。如果在配制该 Service 时指定了 <intent-filter> 子元素,则该属性默认为 true 。
- permission:指定启动该 Service 所需的权限。
- process:指定该 Service 所处的进程,该 Service 组件默认处于该 APP 所在的进程中。实际上,Android 的四大组件都可以通过该属性指定进程。
二、启动和停止服务
在活动(Activity)中调用startService()和stopService()方法,分别用来启动和停止服务,因为这两个方法是定义在Context类中的,所以这里可以直接拿来使用。
也可以在服务中通过调用stopSelf()方法来让服务停止。
三、活动和服务进行通信
启动和停止服务的方法已经知道了,虽然之前的服务是在活动里启动的,但在启动了服务之后,活动与服务基本就没有什么关系了。确实如此,我们在 活动里调用了 startService()方法来启动 MyService 这个服务,然后 MyService 的 onCreate()和 onStartCommand()方法就会得到执行。之后服务会一直处于运行状态,但具体运行的是什么逻辑,活动就控制不了了。
那么怎么才能让活动和服务的关系更紧密一些呢?这就需要借助onBind()方法了。
比如说目前我们希望在 MyService 里提供一个下载功能,然后在活动中可以决定何时开始下载,以及随时查看下载进度等。实现这个功能的思路是创建一个专门的 Binder 对象来对下载功能进行管理,修改 MyService 中的代码,如下所示:
public class MyService extends Service {
private DownloadBinder mBinder = new DownloadBinder();
class DownloadBinder extends Binder {
public void startDownload() {
Log.d("MyService", "startDownload executed");
}
public int getProgress() {
Log.d("MyService", "getProgress executed");
return 0;
}
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
……
}
- 可以看到,这里我们新建了一个 DownloadBinder 类,并让它继承自 Binder,然后在它的内部提供了开始下载以及查看下载进度的方法。
- 接着,在 MyService 中创建了 DownloadBinder 的实例,然后在 onBind()方法里返回了这个实例,这样 MyService 中的工作就全部完成了。
下面就要看一看,在活动中如何去调用服务里的这些方法了。首先需要在布局文件里新 增两个按钮,修改 activity_main.xml 中的代码,如下所示:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<Button android:id="@+id/bind_service"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Bind Service" />
<Button android:id="@+id/unbind_service"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Unbind Service" />
</LinearLayout>
这两个按钮分别是用于绑定服务和取消绑定服务的,那到底谁需要去和服务绑定呢?当然就是活动了。当一个活动和服务绑定了之后,就可以调用该服务里的 Binder 提供的方法了。
MainActivity 中的代码,如下所示:
public class MainActivity extends Activity implements OnClickListener {
private Button startService;
private Button stopService;
private Button bindService;
private Button unbindService;
private MyService.DownloadBinder downloadBinder;
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
downloadBinder = (MyService.DownloadBinder) service;
downloadBinder.startDownload();
downloadBinder.getProgress();
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
……
bindService = (Button) findViewById(R.id.bind_service);
unbindService = (Button) findViewById(R.id.unbind_service);
bindService.setOnClickListener(this);
unbindService.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
……
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;
default:
break;
}
}
}
可以看到,这里我们首先创建了一个 ServiceConnection 的匿名类,在里面重写了 onServiceConnected()方法和 onServiceDisconnected()方法,这两个方法分别会在活动与服务成功绑定以及解除绑定的时候调用。在 onServiceConnected()方法中,我们又通过向下转型得到了 DownloadBinder 的实例,有了这个实例,活动和服务之间的关系就变得非常紧密了。 现在我们可以在活动中根据具体的场景来调用 DownloadBinder 中的任何 public 方法,即实现了指挥服务干什么,服务就去干什么的功能。这里仍然只是做了个简单的测试,在 onServiceConnected() 方法中调用了 DownloadBinder 的 startDownload() 和 getProgress() 方法。
当然,现在活动和服务其实还没进行绑定呢,这个功能是在 Bind Service 按钮的点击事 件里完成的。可以看到,这里我们仍然是构建出了一个 Intent 对象,然后调用 bindService() 方法将 MainActivity 和 MyService 进行绑定。bindService()方法接收三个参数,第一个参数就是刚刚构建出的 Intent 对象,第二个参数是前面创建出的 ServiceConnection 的实例,第三个参数则是一个标志位,这里传入 BIND_AUTO_CREATE 表示在活动和服务进行绑定后自动创建服务。这会使得 MyService 中的 onCreate() 方法得到执行,但 onStartCommand() 方法不会执行。
然后如果我们想解除活动和服务之间的绑定该怎么办呢?调用一下 unbindService()方法就可以了,这也是 Unbind Service 按钮的点击事件里实现的功能。
可以看到,首先是 MyService 的 onCreate() 方法得到了执行,然后 startDownload() 和getProgress()方法都得到了执行,说明我们确实已经在活动里成功调用了服务里提供的方法了。
另外需要注意,任何一个服务在整个应用程序范围内都是通用的,即 MyService 不仅可以和 MainActivity 绑定,还可以和任何一个其他的活动进行绑定,而且在绑定完成后它们都可以获取到相同的 DownloadBinder 实例。
四、服务的生命周期
和活动(Activity)类似,服务(Service)也有自己的生命周期,也可以使用onCreate()、onStartCommand()、onBind()和onDestroy()等方法。
一旦在项目的任何位置调用了Context 的startService()方法,相应的服务就会启动起来,并回调onStartCommand()方法。如果这个服务之前还没有创建过,onCreate()方法会先于onStartCommand()方法执行。服务启动了之后会一直保持运行状态,直到stopService()或stopSelf()方法被调用。注意虽然每调用一次startService()方法,onStartCommand()就会执行一次,但实际上每个服务都只会存在一个实例。所以不管你调用了多少次startService()方法,只需调用一次stopService()或stopSelf()方法,服务就会停止下来了。
另外,还可以调用Context 的bindService()来获取一个服务的持久连接,这时就会回调服务中的onBind()方法。类似地,如果这个服务之前还没有创建过,onCreate()方法会先于onBind()方法执行。之后,调用方可以获取到onBind()方法里返回的IBinder 对象的实例,这样就能自由地和服务进行通信了。只要调用方和服务之间的连接没有断开,服务就会一直保持运行状态。
当调用了startService()方法后,又去调用stopService()方法,这时服务中的onDestroy()方法就会执行,表示服务已经销毁了。类似地,当调用了bindService()方法后,又去调用unbindService()方法,onDestroy()方法也会执行,这两种情况都很好理解。但是需要注意,我们是完全有可能对一个服务既调用了startService()方法,又调用了bindService()方法的,这种情况下该如何才能让服务销毁掉呢?根据Android 系统的机制,一个服务只要被启动或者被绑定了之后,就会一直处于运行状态,必须要让以上两种条件同时不满足,服务才能被销毁。所以,这种情况下要同时调用stopService()和unbindService()方法,onDestroy()方法才会执行。
生命周期的状态及方法总结
1.生命周期状态
onCreate():首次创建服务时,系统将调用此方法。如果服务已在运行,则不会调用此方法,该方法只调用一次。
onStartCommand():当另一个组件通过调用startService()请求启动服务时,系统将调用此方法。
onDestroy():当服务不再使用且将被销毁时,系统将调用此方法。
onBind():当另一个组件通过调用bindService()与服务绑定时,系统将调用此方法。
onUnbind():当另一个组件通过调用unbindService()与服务解绑时,系统将调用此方法。
onRebind():当旧的组件与服务解绑后,另一个新的组件与服务绑定,onUnbind()返回true时,系统将调用此方法。
2.生命周期方法
在Service的生命周期里,常用的方法有:
1.手动调用的方法:
- startService() 启动服务
- stopService() 关闭服务
- bindService() 绑定服务
- unbindService() 解绑服务
2.自动调用的方法:
- onCreat() 创建服务
- onStartCommand() 开始服务
- onDestroy() 销毁服务
- onBind() 绑定服务
- onUnbind() 解绑服务
五、其他技巧
1.使用前台服务
服务的系统优先级比较低,当系统出现内存不足的情况时,就有可能会回收掉正在后台运行的服务。如果希望服务可以一直保持运行状态,而不会由于系统内存不足的原因导致被回收,就可以考虑使用前台服务。
前台服务和普通服务的最大区别就在于:前台服务会一直有一个正在运行的图标在系统的状态栏显示,下拉状态栏后可以看到更加详细的信息,非常类似于通知的效果。实例:
@Override
public void onCreate() {
super.onCreate();
Log.d("MyService","onCreate executed");
Intent intent = new Intent(this,MainActivity.class);
//PendingIntent倾向于在某个合适的时机去执行某个动作,简单理解为延迟的Intent
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();
/* 第一个参数是通知的id,第二个参数是构建出的Notification对象,
* 调用 startForeground()方法后就会让MyService变成一个前台服务,并在系统状态栏显示出来
*/
startForeground(1,notification);
}
2.使用IntentService
Android中服务的代码默认是在主线程中运行的,如果直接在服务里去处理一些耗时操作,很容出现ANR(Application Not Responding)的情况。所以这时需要用到多线程技术,在服务中开启一个子线程来处理耗时逻辑。但这种服务一旦启动就会一直处于运行状态,必须调用stopService()或者stopSelf()方法才能让服务停止下来。
为了避免忘记开启线程或者忘记调用stopSelf()方法,Android专门提供了IntentService类。