Service是Android四大组件中与Activity最相似的组件,它们都代表可执行的程序,Service与Activity的区别是:Service一直在后台运行,它没有用户界面,所以绝不会到前台运行。如果某个程序组件需要在运行时向用户呈现某种界面,或者该程序需要与用户交互,就需要使用Activity;否则就应该考虑使用Service了,最后通过使用Service实现定时更换壁纸。
定时更换壁纸效果图:
Service简介
Service是一个可长期在后台运行的应用组件,并且不提供用户界面。
- Service不是一个单独的进程。
- Service不是一个线程。
创建、配置Service
开发Service分为两步:
- 定义一个继承Service的子类。
- 在AndroidManifest.xml文件中配置该Service。
补充:需要在AndroidManifest.xml中声明的有:
- activity:活动
- activity-alias:活动别名
- service:服务
- provider:内容提供者
- receiver:广播接收者
- meta-data:数据格式
- uses-library:第三方类库
Service中定义了一系列生命周期方法:
- IBinder onBind(Intent intent):该方法是Service子类必须实现的方法。该方法返回一个IBinder对象,应用程序可通过该对象与Service组件通信。
- void onCreate():在Service第一次被创建后立即回调该方法。
- void onDestroy():在Service被关闭之前回调该方法。
- void onStartCommand(Intent intent, int flags, int startId):该方法的早期版本是onStart(Intent intent, int startId),每次客户端调用startService(Intent)方法启动Service时都会回调该方法。
- boolean onUnbind(Intent intent):当该Service上绑定的所有客户端都断开连接时将会回调该方法。
定义一个Service组件如下:
public class FirstService extends Service {
private static final String TAG = FirstService.class.getSimpleName();
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
Log.d(TAG, "onCreate");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "onStartCommand");
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy");
}
}
上面的Service只是重写了Service组件的onCreate()、onStartCommand()、onDestroy()、onBind()等方法,重写这些方法时只是打印了一个字符串。
在Android系统中运行Service有两种方式:
- 通过Context的startService()方法:通过该方法启动Service,访问者与Service之间没有关联,即使访问者退出了,Service也仍然运行。
- 通过Context的bindService()方法:通过该方法启动Service,访问者与Service绑定在一起,访问者一旦退出,Service也就终止了。
启动和停止Service——startService()方式启动
使用Activity作为Service的访问者,该Activity中包含两个按钮,一个用于启动Service,一个用于关闭Service。代码如下:
public class MainActivity extends AppCompatActivity implements View.OnClickListener{
private Button mStartService;
private Button mStopService;
private Intent intent;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mStartService = (Button) findViewById(R.id.start_service);
mStopService = (Button) findViewById(R.id.stop_service);
intent = new Intent(MainActivity.this, FirstService.class);
mStartService.setOnClickListener(this);
mStopService.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.start_service:
startService(intent);
break;
case R.id.stop_service:
stopService(intent);
break;
}
}
}
启动、关闭Service十分简单,调用Context的startService()、stopService()方法即可启动、关闭Service。
注意:Android5.0开始,Google要求必须使用显示的Intent启动Service组件。
运行该程序,点击启动按钮启动Service,再点击停止按钮关闭Service,在Logcat面板可以看到如下输出:
如果在不关闭Service的情况下,连续点击三次启动Service按钮,程序会连续启动三次Service,在Logcat面板可以看到如下输出:
从上图可以看出,每当Service被创建时会回调onCreate()方法,每次Service被启动时都会回调onStartCommand()方法;多次启动一个已有的Service不会再回调onCreate()方法,但每次启动时都会回调onStartCommand()方法。
绑定本地Service并与之通信——bindService()方式启动
如果Service和访问者之间需要进行方法调用或者交换数据,则应该使用bindService()和unbindService()方法启动、关闭Service。
Context的bindService()方法包含三个参数,分别如下:
- service:该参数通过Intent指定要启动的Service。
- conn:该参数是一个ServiceConnection对象,该对象用于监听访问者与Service之间的连接情况。当访问者与Service之间连接成功时回调该ServiceConnection对象的onServiceConnected(ComponentName name, IBinder service)方法;当Service所在的宿主进程由于异常中止或者其他原因终止,导致该Service与访问者之间断开连接时回调该ServiceConnection对象的onServiceDisconnected(ComponentName name)方法。
- onServiceConnected(ComponentName name, IBinder service)方法中的IBinder即可实现与被绑定Service之间的通信。
- flags:指定绑定时是否自动创建Service(如果Service还未创建)。该参数可指定为0(不自动创建)或者BIND_AUTO_CREATE(自动创建)。
实际开发时通常会采用继承Binder(IBinder的实现类)的方式实现自己的IBinder对象。
下面程序示范了如何在Activity中绑定Service,并获取Service的运行状态。该程序的Service类需要真正实现onBind()方法,并让该方法返回一个有效的IBinder对象。Service类代码如下:
public class BindService extends Service {
private static final String TAG = BindService.class.getSimpleName();
private int count;
private boolean quit;
//定义onBinder方法所返回的对象
private MyBinder binder = new MyBinder();
//通过继承Binder实现IBinder类
public class MyBinder extends Binder{
public int getCount(){
return count;
}
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.d(TAG, "onBind");
return binder;
}
@Override
public void onCreate() {
super.onCreate();
Log.d(TAG, "onCreate");
//启动一条线程,动态修改count状态值
new Thread(){
@Override
public void run() {
while (!quit){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count++;
}
}
}.start();
}
@Override
public boolean onUnbind(Intent intent) {
Log.d(TAG, "onUnbind");
return true;
}
@Override
public void onDestroy() {
super.onDestroy();
this.quit = true;
Log.d(TAG, "onDestroy");
}
}
Service类实现了onBind()方法,该方法返回一个可访问该Service状态数据(count)的IBinder对象,该对象将被传给该Service的访问者。
接下来定义一个Activity来绑定该Service,并在Activity中通过MyBinder对象访问Service的内部状态。该Activity包含三个按钮,分别为绑定Service、解绑Service、获取Service的运行状态。
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private static final String TAG = MainActivity.class.getSimpleName();
private Button mBindService;
private Button mUnbindService;
private Button mGetServiceState;
private Intent intent;
//保持所启动的Service的IBinder对象
BindService.MyBinder binder;
private ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.d(TAG, "onServiceConnected");
binder = (BindService.MyBinder) service;//①
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mBindService = (Button) findViewById(R.id.bind_service);
mUnbindService = (Button) findViewById(R.id.unbind_service);
mGetServiceState = (Button) findViewById(R.id.get_service_state);
intent = new Intent(MainActivity.this, BindService.class);
mBindService.setOnClickListener(this);
mUnbindService.setOnClickListener(this);
mGetServiceState.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.bind_service:
bindService(intent, conn, BIND_AUTO_CREATE);
break;
case R.id.unbind_service:
unbindService(conn);
break;
case R.id.get_service_state:
Toast.makeText(MainActivity.this, "Service的count值为:" + binder.getCount(), Toast.LENGTH_SHORT).show();//②
break;
}
}
}
上面①号代码用于在Activity与Service连接成功时获取Service的onBind()方法返回的MyBinder对象,②号代码可以通过MyBinder对象访问Service的运行状态。
点击绑定Service按钮,在Logcat面板可以看到如下输出:
点击获取Service状态按钮,可以看到如下图所示的输出:
点击解绑Service按钮,在Logcat面板可以看到如下输出:
如上图所示,当程序调用unbindService()方法解除对某个Service的绑定时,系统会先调用该Service的onUnbind()方法,然后再回调onDestroy()方法。
与多次调用startService()方法启动不同的是,多次调用bindService()方法并不会执行重复绑定。
Service的生命周期
随着应用程序启动Service方式不同,Service的生命周期也略有差异,如下图:
如果应用程序通过startService()方法来启动Service,Service的生命周期如上图左半部分所示。如果应用程序通过bindService()方法来启动Service,Service的生命周期如上图右半部分所示。
当Activity调用bindService()绑定一个已通过startService()启动的Service时,系统只是把Service内部IBinder对象传给Activity,并不会把该Service生命周期完全绑定到该Activity,因而当Activity调用UNBindService()方法取消与该Service的绑定时,也只是切断该Activity与Service之间的关联,并不能停止该Service组件。要停止该Service组件,还需调用stopService()方法。
IntentService
首先看一下Service本身存在的两个问题。
- Service不会专门启动一个单独的进程,Service与它所在应用位于同一个进程中。
- Service不是一个新的线程,因此不应该在Service中直接处理耗时的任务。
IntentService正好弥补了Service的不足:IntentService会使用队列来管理请求Intent,每当客户端代码通过Intent请求启动IntentService时,IntentService会将该Intent加入队列,然后开启一条新的worker线程来处理该Intent。对于异步的startService()请求,IntentService会按次序依次处理队列中的Intent,该线程保证同一时刻只处理一个Intent。由于IntentService使用新的worker线程处理Intent请求,因此IntentService不会阻塞主线程,所以IntentService自己就可以处理耗时任务。
IntentService的特征:
- IntentService会创建单独的worker线程来处理所有的Intent请求。
- IntentService会创建单独的worker线程来处理onHandleIntent()方法实现的代码,因此开发者无须处理多线程问题。
- 当所有请求处理完成后,IntentService会自动停止,因此开发者无须调用stopSelf()方法来停止该Service。
- 为Service的onBind()方法提供了默认实现,默认实现的onBind()方法返回null。
- 为Service的onStartCommand()方法提供了默认实现,该实现会将请求Intent添加到队列中。
扩展IntentService实现Service无须重写onBind()、onStartCommand()方法,只要重写onHandleIntent()方法即可。
定时更换壁纸
通过AlarmManager周期性调用某个Service,从而让系统实现定时更换壁纸的功能。
该程序界面有两个按钮,一个用于启动定时更换壁纸,一个用于关闭定时更换壁纸,代码如下:
public class MainActivity extends AppCompatActivity {
private Button mStart;
private Button mStop;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mStart = (Button) findViewById(R.id.start);
mStop = (Button) findViewById(R.id.stop);
//指定启动ChangeService组件
Intent intent = new Intent(MainActivity.this, ChangeService.class);
//创建PendingIntent对象
final PendingIntent pi = PendingIntent.getService(MainActivity.this, 0, intent, 0);
mStart.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//获取AlarmManager对象
AlarmManager alarmManager = (AlarmManager) getSystemService(Service.ALARM_SERVICE);
//设置每隔2秒执行pi所代表的组件一次
alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, 0, 2000, pi);
mStart.setEnabled(false);
mStop.setEnabled(true);
Toast.makeText(MainActivity.this, "壁纸定时更换启动成功啦", Toast.LENGTH_SHORT).show();
}
});
mStop.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mStart.setEnabled(true);
mStop.setEnabled(false);
//获取AlarmManager对象
AlarmManager alarmManager = (AlarmManager) getSystemService(Service.ALARM_SERVICE);
//取消对pi的调度
alarmManager.cancel(pi);
}
});
}
}
上面程序代码指定程序每2秒执行一次pi所代表的组件。程序中pi代表了ChangeService组件,ChangeService代码如下:
public class ChangeService extends Service {
//定义定时更换的壁纸资源
int[] wallpapers = new int[]{
R.drawable.author,
R.drawable.girl,
R.drawable.life
};
//定义系统的壁纸管理服务
WallpaperManager wallpaperManager;
//定义当前所显示的壁纸
int current = 0;
@Override
public void onCreate() {
super.onCreate();
//初始化WallPaperManager
wallpaperManager = WallpaperManager.getInstance(this);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
//如果到了最后一张,系统重新开始
if (current >= 3) {
current = 0;
}
try {
//改变壁纸
wallpaperManager.setResource(wallpapers[current++]);
} catch (IOException e) {
e.printStackTrace();
}
return START_STICKY;
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
重写Service的onStartCommand()方法,也就是每次启动该Service时都会执行onStartCommand()方法中的代码,更换壁纸的代码就放在该方法中。
为了允许该程序改变壁纸,还需在AndroidManifest.xml中添加权限:
<uses-permission android:name="android.permission.SET_WALLPAPER" />
运行该程序,点击开始,返回桌面即可看到系统壁纸每2秒更换一次,效果图如下:
网友评论