Service简介
1 服务的介绍
service是在后台运行的,没有界面的android组件之一。
2 进程的分类
进程分为5类,按照级别的高低依次为:
1 前台进程:Foreground Process,正在进行交互的进程,相当于activity执行了onResume()。
2 可视进程:Visible Process 可见不可交互,相当于activity执行了onPause()。
3 服务进程:Service Process 开启了startService()
4 后台进程:Background Process 相当于activity执行了onStop()。
5 空进程:Empty Process 进程中没有任何组件在运行。
3 service的开启方式
2种:startService() + bindService()
3-1 startSercice()方式开启服务,其生命周期的特点
1.方法调用顺序:开启:(Activity)startSercice()–>(Service)onCreate()–>(Service)onStartCommand(){内部调用onStart()};关闭:stopService()–>onDestroy();
2.多次调用startSercice(),onCreate()只执行一次,但onStartCommand()+onStart()执行多次
3.服务一直会在后台运行,除非调用调用stopService或手动停止。
3-2 bindService()方式开启服务,其生命周期的特点
1.方法的调用顺序:bindService()–>onCreate()–>onBind();unBindService()–>onUnbind()–>onDestroy()。
2.多次调用bindService(),onCreate() + onBind()只会执行一次。
3.可以绑定多次,但解绑只有一次,若多次解绑,会报错:没有注册service。
4.service与绑定者是同生共死的关系。
5.通过bindService()开启服务,在手机设置界面是找不到的。
6.如果onBind()方法返回null,那么onServiceConnection()是不执行的。
4 为什么引入bindService()这种方法
为了引用service里面的方法。
Demo验证
验证上面的3-1
startService(intent)方式开启service,可以多次调用startService(intent),service的onCreate()只会执行一次,但onStartCommon()和onStart()会执行多次。stopService(intent)也可以被调用多次,但onDestroy()只会执行一次。
private void initViews() {
btn1.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
startService(intent);
}
});
btn2.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
stopService(intent);
}
});
}
public class DemoService extends Service {
private static final String TAG = "DemoService";
@Override
public void onCreate() {
Log.d(TAG, "onCreate");
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "onStartCommand");
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onStart(Intent intent, int startId) {
Log.d(TAG, "onStart");
super.onStart(intent, startId);
}
@Override
public void onDestroy() {
Log.d(TAG, "onDestroy");
super.onDestroy();
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
源码:https://git.oschina.net/ServiceDemo/StartServiceDemo
验证上面的3-2
bindService(intent):绑定
unBindService(intent):解绑
bindService(intent)方式开启service,可以绑定多次,但解绑只有一次,多次解绑会报错。
多次绑定,onCreate()+onBind()只执行一次。
private void initViews() {
btn1.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
bindService(intent, conn, BIND_AUTO_CREATE);
}
});
btn2.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
unbindService(conn);
}
});
}
private ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
}
};
public class DemoService extends Service {
private static final String TAG = "DemoService";
@Override
public void onCreate() {
Log.d(TAG, "onCreate");
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "onStartCommand");
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onStart(Intent intent, int startId) {
Log.d(TAG, "onStart");
super.onStart(intent, startId);
}
@Override
public void onDestroy() {
Log.d(TAG, "onDestroy");
super.onDestroy();
}
@Override
public IBinder onBind(Intent intent) {
Log.d(TAG, "onBind");
return null;
}
@Override
public boolean onUnbind(Intent intent) {
Log.d(TAG, "onUnbind");
return super.onUnbind(intent);
}
}
源码:https://git.oschina.net/ServiceDemo/BindServiceDemo
怎么判断service1在不在运行中?
/**
* 判断某个服务是否正在运行的方法
* @param mContext
* @param serviceName 是包名+服务的类名(例如:com.cqc.servicedemo01.Service1)
* @return true代表正在运行,false代表服务没有正在运行
*/
public boolean isServiceWork(Context mContext, String serviceName) {
boolean isWork = false;
ActivityManager myAM = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningServiceInfo> myList = myAM.getRunningServices(80);
if (myList.size() <= 0) {
return false;
}
for (int i = 0; i < myList.size(); i++) {
String mName = myList.get(i).service.getClassName().toString();
if (mName.equals(serviceName)) {
isWork = true;
break;
}
}
return isWork;
}
service保活
在实际开发中,我们会遇到一些要求后台运行的service,但是现在Android手机型号太多,而且国内的系统都是各大厂商定制过得,加入了自己的一些优化设置(比如省电方面的)。造成的的问题是:当service在后台运行一段时间后,如果是比较耗电,会被系统kill,service里的逻辑操作也就停止了,对我们的业务造成困扰。那么该如何处理呢?
主要有2种方法:手动设置和代码设置混合使用
手动设置
在手机设置界面中,有锁屏保护和省电管理或者手机管家中的内存优化都要进行处理。
锁屏保护
在360F4手机中,在设置–>电池和省电–>锁屏受保护应用:选中我们自己的app,就想选中微信一样,如果不选中微信的话,锁屏30分钟后,微信也会被kill掉。不同的手机锁屏后kill掉app的时间是不同的,有的没有这一项设置,有的是几分钟,360F4手机是30分钟。见下图:
省电管理
这类App一般都是系统自带的,都是品牌商自己的软件,为了时间省电优化,通常都会把后台运行的app杀死,而且权限还挺高。不过也有用于自己安装的,比如:360手机卫士、腾讯手机管家、猎豹清理大师等,各种app的优化设置不同,这里介绍几个常见的
360手机卫士
我–>设置–>清理加速–>内存加速优化名单–>把我们的app加入优化忽略名单中
OPPO手机管家
待定
代码设置
这也有2种方法:
1. 给serviceA在配置一个守护它的serviceB,当我们的serviceA被杀死后,我们可以serviceB再重新开启A,
2. 注册BroadcastReceiver,锁屏时获取“电源锁”,屏幕解锁是释放“电源锁”
增加一个守护的service
原理:在这个serviceB中开启一个Thread,每个1分钟查找正在运行的service,如果没有serviceA,那么久开启serviceA;如果有serviceA,那么就做处理。
public class MonitorService extends Service {
protected static boolean isCheck = false;
protected static boolean isRunning = false;
private static final String SERVICE_NAME = "com.cqc.walelock01.WakeLockService";
@Override
public void onCreate() {
Log.d("MonitorService2", "MonitorService onCreate");
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d("MonitorService2", "MonitorService onStartCommand");
new Thread() {
@Override
public void run() {
while (isCheck) {
try {
Thread.sleep(5 * 60 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
Log.d("MonitorService2", "thread sleep failed" );
}
if (!isServiceWork(getApplicationContext(), SERVICE_NAME)) {
Log.d("MonitorService2", "WakeLockService轨迹服务已停止,正在重启轨迹服务");
startService(new Intent(MonitorService.this, WakeLockService.class));
} else {
Log.d("MonitorService2", "WakeLockService轨迹服务正在运行");
}
}
}
}.start();
return super.onStartCommand(intent, flags, startId);
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
/**
* 判断某个服务是否正在运行的方法
*
* @param mContext
* @param serviceName 是包名+服务的类名(例如:com.baidu.trace.LBSTraceService)
* @return true代表正在运行,false代表服务没有正在运行
*/
public boolean isServiceWork(Context mContext, String serviceName) {
boolean isWork = false;
ActivityManager myAM = (ActivityManager) mContext
.getSystemService(Context.ACTIVITY_SERVICE);
List<RunningServiceInfo> myList = myAM.getRunningServices(80);
if (myList.size() <= 0) {
return false;
}
for (int i = 0; i < myList.size(); i++) {
String mName = myList.get(i).service.getClassName().toString();
if (mName.equals(serviceName)) {
isWork = true;
break;
}
}
return isWork;
}
}
WakeLock :电源锁
注册广播,当锁屏时获取电源锁,当屏幕解锁时释放电源锁,
public class TrackReceiver extends BroadcastReceiver {
private static final String TAG = "TrackReceiver";
@SuppressLint("Wakelock")
@Override
public void onReceive(final Context context, final Intent intent) {
final String action = intent.getAction();
if (Intent.ACTION_SCREEN_OFF.equals(action)) {
Log.d(TAG,"screen off,acquire wake lock!");
if (null != WakeLockService.wakeLock && !(WakeLockService.wakeLock.isHeld())) {
WakeLockService.wakeLock.acquire();
}
} else if (Intent.ACTION_SCREEN_ON.equals(action)) {
Log.d(TAG,"screen on,release wake lock!");
if (null != WakeLockService.wakeLock && WakeLockService.wakeLock.isHeld()) {
WakeLockService.wakeLock.release();
}
}
}
}
获取电源锁是需要权限的
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.DEVICE_POWER"/>
第二个权限AS会红色提示“这是系统app需要的权限”,这是提示不是报错,添加上就可以了
BroadcastReceiver有2种注册方法:静态注册和动态注册。
我么这里采用动态注册的方法,原因是:
receiver应该跟随serviceA的生命周期,当serviceA被销毁后,将不再获取电源锁;service开启后,再根据屏幕的变化判断是获取还是释放电源锁
public class WakeLockService extends Service {
//cpu保活
private static boolean isRegister = false;
protected static PowerManager pm = null;
public static PowerManager.WakeLock wakeLock = null;
public static TrackReceiver trackReceiver = new TrackReceiver();
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
//动态注册
if (!isRegister) {
if (null == pm) {
pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
}
if (null == wakeLock) {
wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "track upload");
}
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_SCREEN_OFF);
filter.addAction(Intent.ACTION_SCREEN_ON);
registerReceiver(trackReceiver, filter);
isRegister = true;
}
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
if (isRegister) {
try {//销毁广播
unregisterReceiver(trackReceiver);
isRegister = false;
} catch (Exception e) {
}
}
}
}
这个service保活是从百度鹰眼官方Demo中提取的。
网址:http://lbsyun.baidu.com/index.php?title=android-yingyan/guide/tracelive
让service在前台运行
把service设置成前台进程:
Notification notification = new Notification.Builder(this).build();
startForeground(1, notification);
把service的前台进程销毁:
stopForeground(true);
Android进程保活招式大全
Android进程保活招式大全
Demo: http://git.oschina.net/ServiceDemo/WakeLock01
其他相关:
Android的PowerManager和PowerManager.WakeLock用法简析
使用WakeLock使Android应用程序保持后台唤醒
Service在后台长期运行的解决方案
Service与Activity之间的传值
- 把值放在Application中
- 用SharePreferences存取值
- activity中利用intent存值,在service的onStartCommand(…)中取值
intent.putExtra("orderCode", "123456");
startService(intent);
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
String orderCode = intent.getStringExtra("orderCode");
return super.onStartCommand(intent, flags, startId);
}