博客概述
在android项目中,使用activity与service的场景非常多,但是根据android的现有设计,如何实现这两种组件协作的最佳实践呢?本博客就根据博主在项目中的使用经验来记录个人理解的项目最佳实践。
关键技术点
activity
activity直译为活动,是android的四大组件之一,也是最重要用的最多的。他提供一个能让用户操作并与之交互的界面。一个应用有多个界面,也就有多个activity。打电话,发短信,拍照都是通过activity来做的。个人理解:activity就类似于servlet+jsp的组合体,xml是界面显示,代码具体控制行为。
service
service是一个应用组件,他用来在后台完成一个时间跨度比较大的工作任务,且不关联任何的界面。类似mysql服务,不依赖任何界面。同理,activity没了,没有界面了,但是服务还在。如果后台关了,服务就完犊子了。服务并不是在分线程里面运行,而是在后台运行。service的生命周期方法是在主线程里面运行的。应用虽然退出(退到后台),但是service仍然在运行。后台指的是不跟页面关联。
一个service可以完成以下工作:播放音乐,完成io操作,访问网络,大数据量的数据库操作。
服务的特点:service在后台运行,不用与用户进行交互。即使应用退出(后台),服务也不会停止。需要注意的是,service中的耗时操作一定要放在分线程。
最佳实践
从service的的生命周期方法中,有一个关键的方法onStartCommand(),这个方法在service的生命周期中十分关键。每次activity或者其他组件调用startService()方法的时候,该方法(onStartCommand)都会被触发。针对这种机制,可以很好套用某种设计模式的思路(命令模式),来实现service组件对外界指很好的执行,方便功能扩展。
对于具体应用,对某个规固定service的操作是有穷的,也就是说可以通过枚举来指定service可以接受的指令,然后通过广播把指令的执行结果进行返回,这里需要对执行结果做一下封装。需要注意的是,这种广播的方式可以针对通讯频率不太高的场景(0.5s以上),对于高频率的通讯场景,博主没有做测试。
最佳实践的案例代码
此处以蓝牙service为例,给出最佳实践的思路代码。
蓝牙指令
/*
此对象用于activity发的命令
*/
public enum BluetoothCommand {
FIRST__LOGIN("FIRST__LOGIN"), //第一用户登录
FIRST__LOGOUT("FIRST__LOGOUT"), //第一用户退出
SECOND__LOGIN("SECOND__LOGIN"), //第二用户登录
SECOND__LOGOUT("SECOND__LOGOUT");//第二用户退出
private String value = "";
private BluetoothCommand(String value) { //必须是private的,否则编译错误
this.value = value;
}
public static BluetoothCommand getEnumByString(String value) { //手写的从string到enum的转换函数
switch (value) {
case "FIRST__LOGIN":
return FIRST__LOGIN;
case "FIRST__LOGOUT":
return FIRST__LOGOUT;
case "SECOND__LOGIN":
return SECOND__LOGIN;
case "SECOND__LOGOUT":
return SECOND__LOGOUT;
default:
return null;
}
}
public String value() {
return this.value;
}
}
指令反馈
广播时,把此对象转成json发出。
*
此消息用于蓝牙发广播,在这里定义msgtype,比如用户登录成功,
*/
public class BluetoothMessage {
//心率类型
public static final int HEART_BEAT = 1;
//消息类型
private int msgType;
//字符串附加物
private String attachment;
public int getMsgType() {
return msgType;
}
public void setMsgType(int msgType) {
this.msgType = msgType;
}
public String getAttachment() {
return attachment;
}
public void setAttachment(String attachment) {
this.attachment = attachment;
}
//快速生成心率对象
public static BluetoothMessage heartBeatMsg(String attachment){
BluetoothMessage bluetoothMessage = new BluetoothMessage();
bluetoothMessage.setMsgType(HEART_BEAT);
bluetoothMessage.setAttachment(attachment);
return bluetoothMessage;
}
@Override
public String toString() {
return "BluetoothMessage{" +
"msgType=" + msgType +
", attachment='" + attachment + '\'' +
'}';
}
}
service的指令处理方法
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
LogUtil.d("收到了command");
//不支持BLE蓝牙的设备,下面就都不执行了
// if (!isBluetoothUseful){
//
// return super.onStartCommand(intent, flags, startId);
// }
String command = intent.getStringExtra("command");
if (command == null) return super.onStartCommand(intent, flags, startId); //初始化的时候
LogUtil.d("command != null ");
BluetoothCommand bluetoothCommand = BluetoothCommand.getEnumByString(command);
switch (bluetoothCommand){
case FIRST__LOGIN:
scanDevice();
break;
case FIRST__LOGOUT:
//下线所有设备
disConnect();
break;
case SECOND__LOGIN:
LogUtil.d("onStartCommand-SECOND__LOGIN");
LoginBiz.getInstance().COUNT_DOWN_LATCH.countDown();
break;
}
return super.onStartCommand(intent, flags, startId);
}
总结
- 对于某些耗时操作要在子线程里面完成。因为虽然service不跟任何activity绑定,但是他还是运行在主线程里面的。
- 对于service的执行状态要分情况讨论的场景,可以使用内部枚举类,枚举出service的status状态,然后在执行命令时,判断是否处于合适的状态,如果不处于,那么就不能执行命令。