服务
1.服务是什么
服务(Service)是Android中实现程序后台运行的解决方案它非常适合用于去执行那些不需要和用户交互而且还要长期运行的任务。服务的运行不依赖于任何用户界面,即使当程序切换到后台,或者用户打开了另外一个应用程序,服务仍然能够保持正常运行。
需要注意的是,服务默认不是运行在一个独立的进程中,而是依赖于创建服务时所在的应用程序进程。当某个应用程序进程被杀掉时,所有依赖于此进程的服务也会停止运行。要想服务运行于独立的进程,需要在Manifest文件中配置android:process属性。
2.Android多线程编程
2.1 线程的基本用法
- 继承Thread
线程类:
private class MyThread extends Thread {
@Override
public void run() {
//to do
}
}
调用:
new MyThread().start();
- 实现Runnable接口
线程类:
private class MyThread2 implements Runnable {
@Override
public void run() {
//to do
}
}
调用:
MyThread2 thread2 = new MyThread2();
new Thread(thread2).start();
- 匿名内部类
new Thread(new Runnable() {
@Override
public void run() {
//to do
}
}).start();
2.2 在子线程更新UI
2.2.1 Handler+Thread
2.2.2 AsyncTask
3.服务的基本用法
3.1 定义一个服务
新建一个MyService类继承Service,然后在Manifest文件中注册,代码如下:
public class MyService extends Service {
private static final String TAG = "MyService";
/**
* 服务创建的时候调用
*/
@Override
public void onCreate() {
super.onCreate();
Log.i(TAG,"onCreate");
}
/**
* 每次服务启动的时候调用
* @param intent
* @param flags
* @param startId
* @return
*/
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i(TAG,"onStartCommand");
return super.onStartCommand(intent, flags, startId);
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
/**
* 服务销毁的时候调用
*/
@Override
public void onDestroy() {
super.onDestroy();
Log.i(TAG,"onDestroy");
}
}
注册:
<service android:name=".MyService"/>
3.2 启动和停止服务
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_start:
//启动服务
Intent startIntent = new Intent(this,MyService.class);
startService(startIntent);
break;
case R.id.btn_stop:
//停止服务
Intent stopIntent = new Intent(this,MyService.class);
stopService(stopIntent);
break;
}
}
启动服务
日志打印:
01-30 16:03:43.678 26654-26654/? I/MyService﹕ onCreate
01-30 16:03:43.678 26654-26654/? I/MyService﹕ onStartCommand
停止服务
日志打印:
01-30 16:05:02.694 26654-26654/? I/MyService﹕ onDestroy
重复启动
日志打印:
01-30 16:06:00.859 26654-26654/? I/MyService﹕ onStartCommand
3.2 Activity和Service进行通信
1 创建Binder对象
在Service创建一个内部类并继承Binder,MyBinder类中两个方法用于设置文本和获取文本,如下所示:
public class MyService extends Service {
private static final String TAG = "MyService";
private MyBinder mBinder = new MyBinder();
public class MyBinder extends Binder {
public void setText(String text) {
Log.i(TAG,"set: " + text);
}
public String getText() {
return "Hello,World";
}
}
/**
* 返回一个Binder对象,供客户端调用
* @param intent
* @return
*/
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
}
2 客户端绑定服务,并实现一个ServiceConnection的子类
public class MyActivity extends Activity implements View.OnClickListener {
private Button mStartBtn;
private Button mStopBtn;
private MyService.MyBinder mBinder;
/**
* 建立Activity与Service的通信
*/
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//得到mBinder对象实例
mBinder = (MyService.MyBinder) service;
//调用mBinder对象的方法
mBinder.setText("Just do it");
mBinder.getText();
}
@Override
public void onServiceDisconnected(ComponentName name) {
//连接断开时调用
}
};
/**
* Called when the activity is first created.
*/
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mStartBtn = (Button) findViewById(R.id.btn_start);
mStopBtn = (Button) findViewById(R.id.btn_stop);
mStartBtn.setOnClickListener(this);
mStopBtn.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_start:
//绑定服务,BIND_AUTO_CREATE表示在Activity于Service进行绑定后自动创建服务,这会是的Service中的onCreate方法执行,但onStartCommand方法不会执行
Intent bindIntent = new Intent(this,MyService.class);
bindService(bindIntent,mConnection,BIND_AUTO_CREATE);
break;
case R.id.btn_stop:
//取消绑定
unbindService(mConnection);
break;
}
}
}
- 绑定服务
打印日志:
01-30 16:25:09.045 32406-32406/? I/MyService﹕ onCreate
01-30 16:25:09.050 32406-32406/? I/MyService﹕ setText(): Just do it
01-30 16:25:09.050 32406-32406/? I/MyService﹕ getText
日志可以看出,onCreate方法得到了执行,MyBinder中的setText和getText方法得到了执行,但不执行onStartCommand()方法,可见此方法只用于startService启动服务模式中
- 解绑服务
日志打印:
01-30 16:29:27.178 32406-32406/? I/MyService﹕ onDestroy
4.服务的声明周期
调用Context的startService方法,相应的服务就会启动起来,并回调onStartCommand方法执行。如果这个服务之前还没有被创建过,onCreate方法会先于onStartCommand方法执行。服务启动了之后会一直保持保持运行状态,直到stopService或stopSelf方法被调用。注意:虽然每调用一次startService方法,onStartCommand就会执行一次,但实际上每个服务只会存在一个实例。所有不管调用了多少次startService方法,只需要调用一次stopService或stopSelf方法,服务就会停止。
另外,还可以调用Context的bindService来获取一个服务的持久连接,这时就会回调服务中的onBind方法。如果这个服务之前还没有被创建过,onCreate方法会先于onBind方法执行。之后,调用方可以获取到onBind方法里返回的IBinder对象的实例,这样就能自由地和服务进行通信了。只要调用方和服务之间的连接没有断开,服务就会一直保持运行状态了。
需要注意的是,如果对一个服务既调用了startService,又调用了bindService方法,这种情况该如何才能服务销毁呢?根据Android系统的机制,一个服务只要被启动或者被绑定了之后,就会一直处于运行状态,必须要让以上两种条件同时不满足,服务才能被销毁。所有,这种情况要同时调用stopService和unBindService方法,onDestroy方法才会执行。
5.服务的更多技巧
5.1 前台服务
1.简介
服务的系统优先级还是比较低的,当系统出现内存不足的情况下,就有可能回收掉正在后台运行的服务。如果你希望服务可以一直保持运行状态,而不会由于系统内存不足导致被回收,就可以考虑使用前台服务。
前台服务和普通服务最大的区别在于,它会一直有一个正在运行的图标在系统的状态栏显示,下拉状态栏后可以看到更加详细的信息,类似于通知的效果。
2.创建前台服务
public class ForegroudService extends Service {
@Override
public void onCreate() {
super.onCreate();
Notification notification = new Notification(R.drawable.ic_launcher,"Notification comes",System.currentTimeMillis());
Intent intent = new Intent(this,MyActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this,0,intent,0);
//点击通知的响应事件
notification.setLatestEventInfo(this,"This is Title","context",pendingIntent);
startForeground(1,notification); //创建前台服务,让Service变成一个前台服务,并在系统状态栏显示出来
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
5.2 使用IntentService
Service中的代码默认都是运行在主线程,如果在服务中直接去处理一些耗时的逻辑,就很容易出现ANR。
这种情况我们就要用到多线程编程技术,一个比较标准的耗时服务可以写成如下形式:
@Override
public IBinder onBind(Intent intent) {
return null;
}
@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);
}
上述写法并不复杂,但是总会有工程师忘记开启线程或是忘记调用stopSelf方法。为了可以简单地创建一个异步的、会自动停止的服务,Android专门提供了一个IntentService类,实现如下:
public class MyIntentService extends IntentService {
public MyIntentService() {
super("MyIntentService"); //为HandThread指定名称
}
@Override
protected void onHandleIntent(Intent intent) {
//在线程中执行,执行完毕后会销毁服务
}
@Override
public void onDestroy() {
super.onDestroy();
}
}
6.服务的最佳实践-后台执行的定时任务
6.1 定时实现方法
Android中的定时任务一般由两种实现方式,一种是使用Java API中的Timer类,另一种是使用Android中的Alarm机制。两种方式在多数情况下都能实现同样的效果,但Timer类有一个缺点,它并不太适用于那些需要长期在后台运行的定时任务。Android手机一般会在长时间不操作的情况下自动让CPU进入到睡眠状态,这有可能导致Timer中的定时任务无法正常运行。而Alarm不存在这种情况,它具有唤醒CPU的功能,即可以保证每次需要执行定时任务的时候CPU都能正常工作。
Alarm机制主要是借助了AlarmManager类来实现的。
6.2 AlarmManager使用
AlarmManager manager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
long triggerTime = SystemClock.elapsedRealtime() + 10 * 1000;
//设置任务在10秒后执行
manager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,triggerTime,pendingIntent);
或者
long triggerTime = SystemClock.currentThreadTimeMillis() + 10 * 1000;
//设置任务在10秒后执行
manager.set(AlarmManager.RTC_WAKEUP,triggerTime,pendingIntent);
AlarmManager的set方法中有三个参数:
第一个参数int型,指定AlarmManager的工作类型,有四种值选择
- ELAPSED_REALTIME:表示让定时任务的触发时间从系统开机开始算起,但不会唤醒CPU
- ELAPSED_REALTIME_WAKEUP:表示让定时任务的触发时间从系统开机开始算起,但会唤醒CPU
- RTC: 表示让定时任务的触发时间从从1970年1月1日0点开始算起,但不会唤起CPU
- RTC_WAKEUP: 表示让定时任务的触发时间从1970年1月1日0点开始算起,但会唤起CPU
- SystemClock.elapsedRealtime()可以获取系统开机至今所经历的毫秒数
- SystemClock.currentThreadTimeMillis()可以获取到1970年1月1日0点至今所经历时间的毫秒数
第二个参数long型,表示定时任务触发的时间,以毫秒为单位,如果第一个参数传入ELAPSED_REALTIME或ELAPSED_REALTIME_WAKEUP,则这里传入开机至今的时间再加上延迟执行的时间;如果第一个参数传入RTC或者RTC_WAKEUP,则这里传入1970年1月1日0点至今所经历时间再加上延迟执行的时间。
第三个参数是一个PendingIntent。这里一般调用getBroadcast方法来获取一个能够执行广播的PendingIntent。
6.3 后台执行的定时任务实例
1. 创建一个Service
public class LongRunningService extends Service {
@Override
public void onCreate() {
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
new Thread(new Runnable() {
@Override
public void run() {
//执行具体的耗时任务
}
}).start();
//设置定时任务
AlarmManager manager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
int hour = 60 * 60 * 1000; //1小时的毫秒数
long triggerTime = SystemClock.elapsedRealtime() + hour;
Intent intent = new Intent(this,AlarmReceiver.class);
PendingIntent pendingIntent = PendingIntent.getBroadcast(this,0,intent,0);
manager.set(AlarmManager.ELAPSED_REALTIME,triggerTime,pendingIntent);
return super.onStartCommand(intent, flags, startId);
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
2. 创建处理事件的BroadcastReceiver
public class AlarmReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
//开启服务
Intent service = new Intent(context,LongRunningService.class);
context.startService(service);
}
}
需要注意的是,从Android4.4版本开始,Alarm任务的触发时间将变得不准确,有可能会延迟一段时间后任务才能得到执行。这不是个bug,而是系统在耗电性方面进行的优化。系统会自动检测目前有多少Alarm任务存在,然后将触发时间相近的几个任务放在一起执行,这就可以大幅度地减少CPU被唤醒的次数,从而有效地延长电池的使用时间。
当然如果你要求Alarm的任务的执行时间必须准确无误,Android仍然提供了解决方案,使用AlarmManager的setExact方法替代set方法,就可以保证任务准确执行了。