转载标明出处:http://blog.csdn.net/u012861467/article/details/51646935
Service 是Android四大组件之一,通常是在后台运行的,执行一些耗时的操作。
对于Service 我们需要掌握的知识点有:
1、Service 的生命周期
2、Service 的创建
3、远程服务的AIDL 跨进程通讯
4、提高 Service 的生存率的一些方法
下面我们来一步步学习。
一、Service 的生命周期
Service 的启动方式有两种,下面来分析一下这两种的区别:
(1)通过 startService 启动
这种方式启动的服务跟启动它的Activity是没有交互的,即使启动它的Activity被destory了,该服务还可以继续运行。那什么时候销毁呢?调用stopServive或该服务所在进程被销毁了,这个服务才可能销毁。
(2)通过 bindService 启动
这种方式启动的服务跟启动它的Activity是可以交互的,依靠的是Binder进程间通讯机制,启动它的Activity可以通过binder对象调用Service的一些方法,这个并不是直接调用Service内部的方法,只是先得到一个映射,再通过映射找Service内部的方法。因为这种方式启动的Service跟启动它的Activity 进行了绑定,所以它会受到Activity的生命周期的影响,在Activity销毁的时候,绑定的Service也会销毁。注意Activity 销毁的时候记得解除绑定,不然会报错。
我们来看看不解除绑定的情况下打印出来的日志信息:
可以看出MainActivity可以正常的回调onDestroy方法,MyService也能回调onDestroy方法,但是会报错。
错误信息如下:
E/ActivityThread: Activity com.example.servertest.MainActivity has leaked ServiceConnection com.example.servertest.MainActivity$1@b0ff6638 that was originally bound here
注意:
(1)无论使用那种方式启动的Service,Service 都会执行一次完整的逻辑,它不能被stopService调用强制停止当前进行的操作。
(2)Service 跟线程没有什么必然的联系,不要混淆。
(3)Service 默认是跟Activity在同一个进程里(主线程),所以在Service里执行耗时的操作需要另开新线程,避免ANR。
二、Service 的创建
创建一个Service 是比较简单的,我们需要继承Service这个类,重写里面的一些方法完成我们需要的逻辑。
下面是一个例子:
public class MyService extends Service {
public static final String TAG = "MyService";
private MyBinder mBinder = new MyBinder();
@Override
public void onCreate() {
super.onCreate();
Log.d(TAG, "onCreate() executed");
Log.d(TAG, "MyService thread id is " + Thread.currentThread().getId());
}
@Override
public void onDestroy() {
super.onDestroy();
// 销毁一些不再使用的资源
Log.d(TAG, "onDestroy() executed");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "onStartCommand() executed");
//因为当前服务跟Activity是在同一个进程里,耗时操作应该另起线程处理,避免ANR
new Thread(new Runnable() {
@Override
public void run() {
Log.d(TAG, "startDownload() executed");
Log.d(TAG, "Download thread id is " + Thread.currentThread().getId());
// 执行具体的下载任务
}
}).start();
return super.onStartCommand(intent, flags, startId);
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
class MyBinder extends Binder {
public void startDownload() {
new Thread(new Runnable() {
@Override
public void run() {
Log.d(TAG, "startDownload() executed");
Log.d(TAG, "Download thread id is " + Thread.currentThread().getId());
// 执行具体的下载任务
}
}).start();
}
}
}
Service里面创建了一个Binder子类对象,该对象有个startDownload方法,将作为onBind 方法返回对象。onBind 方法只有使用bindService开启服务的时候才会被调用,它返回的IBinder对象用于跟Service通讯用的。
(1) 使用startService开启服务
Intent intent = new Intent(MainActivity.this,MyService.class);
startService(intent);
(2) 使用bindService 开启服务
Intent intent = new Intent(MainActivity.this,MyService.class);
bindService(intent,serviceConnection,BIND_AUTO_CREATE);
bindService方法里面的参数serviceConnection是一个实现 ServiceConnection接口的对象,我们可以这样创建它:
serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
// 简单服务
myBinder = (MyService.MyBinder) iBinder;
myBinder.startDownload();
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
}
};
当Service绑定之后会回调 onServiceConnected 方法,传入的参数有一个IBinder 对象,我们就是通过这个对象跟Service通讯。这里的例子代码就是调用了服务的startDownload方法。
三、远程服务的跨进程通讯
上面例子我们创建的是本地服务,本地服务依附的是主进程,它并没有运行在另外的进程,还有一种服务叫远程服务,它是运行在独立的进程。这里为什么要明确是运行在主进程还是独立进程呢?,因为本地服务依附的是主进程,并不需要使用aidl跨进程通讯,操作起来相对简单,而远程服务需要使用aidl暴露自己的接口供客户端调用。
为什么要使用aidl呢?打个比方,有一台陌生的机器(远程服务对你来说是陌生的,你不知道它提供了什么给你),它会提供一些功能性的服务,而你对它并不了解不知怎样让它为你服务,这时候aidl就相当于是用户手册,它列举了这台机器的所有功能的操作指引,你(客户端)只需按照它的步骤(方法)就可以获得相应的服务功能,而不需要知道机器是怎么做的。
把本地服务转成远程服务可以在清单文件中为Service添加 android:process=":remote" 属性,该服务就会运行在独立进程里。
下面来了解一下怎样使用AIDL实现跨进程通讯
(1)先定义一个aidl文件,在里面定义我们需要的功能声明,有点类似定义接口的语法。
我使用的是Android studio,点击工程的根目录右键选择 “NEW”--> “AIDL” --> “AIDL File”,编辑aidl文件的名称,点击“OK”,软件自动帮你创建aidl文件,aidl文件放在跟java同级目录下的aidl文件夹下,路径跟工程的包名一致。
IMyAidlInterface.aidl 文件里面的内容:
package com.example.servertest;
interface IMyAidlInterface {
int plus(int a, int b);
String toUpperCase(String str);
}
自己声明了两个方法,一个是加法操作,另一个是字符串字母转大写。
定义好aidl文件之后,点击make,软件会自动生成aidl对应的java接口文件,不用去修改它和编辑它。
(2)创建一个远程服务 RemoteService,在服务里面实现定义好的接口(就是由aidl文件自动生成的接口文件)
private IMyAidlInterface.Stub mBinder = new IMyAidlInterface.Stub() {
@Override
public int plus(int a, int b) throws RemoteException {
return a+b;
}
@Override
public String toUpperCase(String str) throws RemoteException {
return str.toUpperCase();
}
};
这个 mBinder 对象就作为Service 的onBind方法返回的IBinder对象。也就是我们在 onServiceConnected 方法参数中获取的IBinder对象,有了这个对象,Activity就可以跟远程服务通讯了。Stub 是Binder的子类。
(3)Activity 绑定远程服务
Intent intent = new Intent(MainActivity.this,RemoteService.class);
bindService(intent,serviceConnection,BIND_AUTO_CREATE);
创建实现 ServiceConnection 接口的对象
serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
//AIDL 跨进程通讯
iMyAidlService = IMyAidlInterface.Stub.asInterface(iBinder);
try {
int result = iMyAidlService.plus(3, 5);
String upperStr = iMyAidlService.toUpperCase("hello world");
Log.d("iMyAidlService", "result is " + result);
Log.d("iMyAidlService", "upperStr is " + upperStr);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
}
};
通过IMyAidlInterface.Stub.asInterface(iBinder); 可以获取远程服务的Binder对象。有了这个Binder对象就可以调用远程服务的方法了。
AIDL对Java类型的支持:
1.AIDL支持Java原始数据类型。
2.AIDL支持String和CharSequence。
3.AIDL支持传递其他AIDL接口,但你引用的每个AIDL接口都需要一个import语句,即使位于同一个包中。
4.AIDL支持传递实现了android.os.Parcelable接口的复杂类型,同样在引用这些类型时也需要import语句。
怎样在另外一个应用里调用这个服务呢?
在清单文件里为服务添加 intent-filter 标签,增加该服务可以处理的action,这里自定义了一个action。
把aidl文件拷贝到另外一个应用程序里,包括aidl所在的包目录结构。
在另外的应用Activity里使用隐式 Intent 绑定远程服务
Intent intent = new Intent("com.example.servicetest.MyAIDLService");
bindService(intent, serviceConnection, BIND_AUTO_CREATE);
serviceConnection 创建跟上面一样。
四、提高 Service 的生存率的一些方法
要做杀不死服务,就要知道怎样提高Service存活率,因为在Android 的高版本中没有操作常驻后台的服务是会被系统杀掉的。
目前见过的方法有:
(1)提升server进程优先级
在AndroidManifest.xml文件中为 Service 的 intent-filter 添加android:priority = "1000"属性,1000是最高值,如果数字越小则优先级越低,同时适用于广播。
(2)前台服务
使用startForeground(int, Notification)方法来将service设置为前台服务
public class ForegroundService extends Service {
public static final String TAG = "ForegroundService";
@Override
public void onCreate() {
super.onCreate();
// 设置了点击通知后就打开MainActivity
Intent notificationIntent = new Intent(ForegroundService.this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
Notification notification = null;
// // 低于 API 11 写法
// if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
// notification = new Notification(R.mipmap.ic_launcher, "有通知到来", System.currentTimeMillis());
// notification.setLatestEventInfo(this, "这是通知的标题", "这是通知的内容", pendingIntent);
// }
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB && Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN){
// 高于 API 11 低于 API 16
Notification.Builder builder = new Notification.Builder(this)
.setAutoCancel(true)
.setContentTitle("有通知到来")
.setContentText("describe")
.setContentIntent(pendingIntent)
.setSmallIcon(R.mipmap.ic_launcher)
.setWhen(System.currentTimeMillis())
.setOngoing(true);
notification = builder.getNotification();
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
// 高于 API 16
notification = new Notification.Builder(this)
.setAutoCancel(true)
.setContentTitle("有通知到来")
.setContentText("describe")
.setContentIntent(pendingIntent)
.setSmallIcon(R.mipmap.ic_launcher)
.setWhen(System.currentTimeMillis())
.setOngoing(true)
.build();
}
// 调用startForeground()方法就可以让MyService变成一个前台Service
startForeground(1, notification);
}
......
}
(3)为 Service 中的onStartCommand方法指定START_STICKY标志
public class ForegroundService extends Service {
...
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "onStartCommand() executed");
flags = START_STICKY;
return super.onStartCommand(intent, flags, startId);
}
...
}
onStartCommand 的标志有:
START_STICKY | 如果service进程被kill掉,保留service的状态为开始状态,但不保留递送的intent对象。随后系统会尝试重新创建service,由于服务状态为开始状态,所以创建服务后一定会调用onStartCommand(Intent,int,int)方法。如果在此期间没有任何启动命令被传递到service,那么参数Intent将为null。 |
START_NOT_STICKY | “非粘性的”。使用这个返回值时,如果在执行完onStartCommand后,服务被异常kill掉,系统不会自动重启该服务。 |
START_REDELIVER_INTENT | 重传Intent。使用这个返回值时,如果在执行完onStartCommand后,服务被异常kill掉,系统会自动重启该服务,并将Intent的值传入。 |
START_STICKY_COMPATIBILITY | START_STICKY的兼容版本,但不保证服务被kill后一定能重启。 |
(4)service +broadcast 方式
新建一个广播接收器,接收各种广播,接收到广播之后检查服务是否开启,没有开启就开启服务。
<receiver android:name=".MyBroadcastReceiver" >
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.USER_PRESENT" />
<action android:name="com.ForegroundService.destroy" />
</intent-filter>
</receiver>
也可以在Service的onDestory 方法里通过发送自定义广播,由广播接收器重新启动服务或直接在onDestory方法里启动服务。
public class ForegroundService extends Service {
...
@Override
public void onDestroy() {
super.onDestroy();
// 销毁一些不再使用的资源
Log.d(TAG, "onDestroy() executed");
// 注意在onDestroy里还需要stopForeground(true);
stopForeground(true);
// 销毁时发送广播,重新启动Server
Intent intent = new Intent("com.ForegroundService.destroy");
sendBroadcast(intent);
super.onDestroy();
}
...
}
(5)清单文件的 Application 标签添加 android:persistent="true" 属性
在系统启动之时,AMS的systemReady()会加载所有persistent为true的应用,并不是设置persistent 属性为true就可以了,这种方式必须同时符合FLAG_SYSTEM(app需放在/system/app目录,即系统目录)及FLAG_PERSISTENT(android:persistent="true"),单设置该属性无效。
(6)AlarmManager 定时开启服务
使用 AlarmManager 的闹钟功能定时开启服务,可以添加在服务的onCreate 方法内
Intent intent = new Intent(getApplicationContext(), ForegroundService.class);
mAlarmManager = (AlarmManager)getSystemService(ALARM_SERVICE);
//服务开启模式要设置为Intent.FLAG_ACTIVITY_NEW_TASK,避免重复创建Service
mPendingIntent = PendingIntent.getService(this, 0, intent, Intent.FLAG_ACTIVITY_NEW_TASK);
long now = System.currentTimeMillis();
//60 s 间隔
mAlarmManager.setInexactRepeating(AlarmManager.RTC, now, 60000, mPendingIntent);
源码的地址放在我的Github地址上,里面有详细的解释
转载标明出处:http://blog.csdn.net/u012861467/article/details/51646935
例子源码地址: