http://blog.****.net/sk719887916/article/details/51398416 skay亲笔
Android开发中经常会用到周期性执行一个动作的需求,大的场景有推送,统计,即时通讯,小的场景有客户端进行一些小范围的计时器,列入有以下场景。
统计:客户端不断轮询去请求服务器某个接口,上报数据等
1. 统计方案见《 Android 优质精准的用户行为和日志打捞方案》
2. 日志抓取见:《Android全局异常处理(可以做强制退出和carsh日志抓取)》
推送:客户端定时去检测服务器有无新的消息,也有采用socket进行长连接主动推,那么这一类我们可以归类到即时通信中
聊天: 客户端和服务端双向采用轮询机制,业内不叫轮询,称之为心跳机制。客户端定时的连接服务器,服务器轮询去检测客户端是否在线,这叫保证了客户端断线时能及时连接到服务器,服务器也能及时在和客户端掉线时更新状态,
不死进程:话说不死进程我们可以用轮询监测某个服务是否存活,但是一般实现不死进程时候不建议采取轮询机制,一般采用三方互相守护来实现。
常有客户端轮询方案有如下:
一 采用Thread+Service方式
此方式在客户单开启时成功开启一个后台服务,并在服务里启动一个线程,让线程定时去执行应任务,
public class PollService extends Service {
private Boolean isStart = true;
@Override
public IBinder onBind(Intent intent) {
new MyThread().start();
return null;
}
@Override
public void onCreate() {
System.out.println("oncreate()");
MyThread thread = new MyThread();
thread.start();
super.onCreate();
}
private class MyThread extends Thread {
@Override
public void run() {
while (isStart) {
try {
// 每个5秒向服务器发送一次请求
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// do something
}
}
}
@Override
public void onDestroy() {
isStart = false;
super.onDestroy();
}
}
二 采用Handler 进行定时轮询
此方式只采用handler加入到 ThreadLocal中,定时sendMessge和handleMessage,来完成我们的定时功能。
public class StatiPollMgr {
/** 超期消息 */
private static final int MSG_TIMEOUT = 1;
private PaStaticsManagerImpl staticsManagerImpl;
/** 心跳周期 */
private long mCardiacCycle;
/** 默认心跳周期,即初始化而来的周期 */
private long mDefaultCycle;
public StatiPollMgr(){
}
/**
* 开启心跳
*
* @param aCardiacCycle
* 心跳周期
*/
public void start(long aCardiacCycle) {
mDefaultCycle = aCardiacCycle;
mCardiacCycle = aCardiacCycle;
checkDateChanging();
stop();
loop();
}
/**
* 停止心跳
*/
public void stop() {
sPrivateHandler.get().removeMessages(MSG_TIMEOUT);
}
/**
* 循环
*/
private void loop() {
Message msg = sPrivateHandler.get().obtainMessage(MSG_TIMEOUT, this);
sPrivateHandler.get().sendMessageDelayed(msg, mCardiacCycle);
}
/**
* 超期通知
*/
public void onTimeOut() {
// do something
}
/**
* 检测将要跨天时,调整心跳周期;跨天之后,调回默认值
*/
private void checkDateChanging() {
Time time = new Time();
time.setToNow();
int hour = time.hour; //24小时制
int minute = time.minute;
if (hour == 23) { //SUPPRESS CHECKSTYLE
int cycle = 61 - minute; // SUPPRESS CHECKSTYLE 12:01访问
long timeSchedule = cycle * DateUtils.MINUTE_IN_MILLIS;
if (timeSchedule < mCardiacCycle) {
mCardiacCycle = timeSchedule;
}
} else {
if (mCardiacCycle != mDefaultCycle) {
mCardiacCycle = mDefaultCycle;
}
}
}
/**
* Handler
*/
private static final ThreadLocal<Handler> sPrivateHandler = new ThreadLocal<Handler>() {
@Override
protected Handler initialValue() {
return new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_TIMEOUT:
StatiPollMgr schedule = (StatiPollMgr) msg.obj;
if (schedule != null) {
schedule.onTimeOut();
schedule.checkDateChanging();
schedule.loop();
}
break;
default:
break;
}
}
};
}
};
}
三 采用AlarmManager和Broadcast
采用Anrdoid自带的Alarm机制来做定时操作,本API本来用作系统的铃声操作,我们可以借助他来完成定时操作,一般我们在Alarm来启动一个Receiver,在Receiver中去执行我们的需求代码就可以了
Receiver 用来接收消息
public class CoreReceiver extends BroadcastReceiver {
public static final String REPORT_ACTION = "action.base.send_report";
@Override
public void onReceive(Context context, Intent intent) {
if (context == null || intent ==null ) {
return;
}
if (TextUtils.equals(intent.getAction(), REPORT_ACTION)) {
Toast.makeText(context, "send statData", Toast.LENGTH_LONG).show();
//do some 列入去请求网络,或其它更新UI操作等
}
AlarmManager
用来设定时间和触发广播
public class PollUtil {
static CoreReceiver receiver;
/**开启轮询服务
* @param context
* @param seconds
* @param cls
* @param action
*/
public static void startPollingService(Context context, int seconds, Class<?> cls, String action) {
//获取AlarmManager系统服务
AlarmManager manager = (AlarmManager) context
.getSystemService(Context.ALARM_SERVICE);
//包装需要执行Service的Intent
Intent intent = new Intent(context, cls);
intent.setAction(action);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0,
intent, PendingIntent.FLAG_UPDATE_CURRENT);
//触发服务的起始时间
long triggerAtTime = SystemClock.elapsedRealtime();
//使用AlarmManger的setRepeating方法设置定期执行的时间间隔(seconds秒)和需要执行的Service
manager.setRepeating(AlarmManager.ELAPSED_REALTIME, triggerAtTime,
seconds * 1000, pendingIntent);
}
/**
* 停止轮询服务
* @param context
* @param cls
* @param action
*/
public static void stopPollingService(Context context, Class<?> cls, String action) {
AlarmManager manager = (AlarmManager) context
.getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(context, cls);
intent.setAction(action);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0,
intent, PendingIntent.FLAG_UPDATE_CURRENT);
//取消正在执行的服务
manager.cancel(pendingIntent);
}
接下来我们的Activity中直接可以调用轮询工具类的start方法,
PollUtil.stopPollingService(mContext, CoreReceiver.class, CoreReceiver.REPORT_ACTION , 3);
总结
上面三种方法都可以实现本地轮询器,那么哪种方式比较靠谱呢,
显然第一种是开启一个Service,这样消耗显而易见,在app内存持续暴涨情况下这个服务又被kill的可能,我们可以把Service声明成进程,以及提高到前台,也可以采用广播来启动,但逃不过第三方安全软件kill的可能,在保证功能情况下,这种方式也不可取的。我们APP退出时更保证不正常的轮询
第二种采用handler, 性能开销相对很低,我们也可以在app启动时做一些轮询操作,但是当我们的App退出时就无法进行轮询操作的,这种实现方式,我们可以app启动的相对需求中采用,比如去计时去采集app的行为日志,性能开销等,
第三种,借助系统的Api,那么本身的系统就进行闹铃操作,也可以在app退出是进行定时,比如推送功能我们可以借助这种方案,但是会存在手机兼容问题,比如国产的手机已经对我们的Alarm加入权限,第三方应用无法对此操作。
不管那种方式,我们也可以采取多方式配合,比如我们广播启动Service,Service在被杀的时候发送来触发广播,广播竟可能的监听系统多种广播来启动我们的Service, 也可以将Alarm加入进来,来守护某个Service
当然性能方面第二种是最优的,功能全面来说第三种最优