android系统升级到4.4以上,由于存在一个定时器对齐执行(好像是这么个名字),造成定时器每5分钟才能执行一次,很多场景下需要定时执行的任务都必须5分钟才执行一次,典型的场景比如socket长连接的心跳,为了维持socket长连接,必须每隔固定时间由app向server端发送一个心跳包,以便让server知道该socket还是正常的,无论设置是40秒还是多少秒,心跳定时器都会被合并成5分钟执行一次。以下是能想到的可以做为定时器的方法在android4.4以上的不同表现:
1)线程定时器:
亮屏情况下:定时器严格按照设置的心跳时间执行
黑屏情况下:不锁cpu,定时器基本不可控,执行时间不确定,锁cpu,严格按照定时器时间执行
2)handler定时器:
亮屏情况下:定时器严格按照设置的心跳时间执行
黑屏情况下:不锁cpu,定时器基本不可控,执行时间不确定,锁cpu,严格按照定时器时间执行
3)AlarmManager定时器:
亮屏情况下:定时器严格按照设置的心跳时间执行
黑屏情况下:定时器被合并到每5分钟执行一次
4)系统广播定时器:接收系统电量变化广播和时间变化广播
亮屏情况下:电量变化广播是20秒一次,时间变化广播是1分钟一次
黑屏情况下:时间不定,2/3/5/7分钟都可能接收到一次广播
5)服务器端主动检测app端是否在线,服务器在长时间没有收到app端发送的心跳时,通过socket长连接向app推送一个数据包,app收到该数据包后向服务器反馈一个心跳包,该实现思路主要是利用了android系统在收到网络数据时会唤醒cpu一段时间,以便处理收到的数据包,该实现思路在绝大多数手机上是没有问题的,有个别特别低配的手机上也不会及时的处理收到的数据包。
测试代码如下:
package com.efrobot.mydemo;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Build;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.os.PowerManager;
import android.util.Log;
import com.efrobot.robot.socket.ConncetionManager;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Date;
/**
* Created by tallboy on 2016/7/2.
*/
public class Timer {
private static final String TAG = "Timer";
public static final int TIMER_HANDLER_ACTION = 101;
public static final int TIMER_ALARM_ACTION = 102;
public static final int TIMER_BROADCAST_ACTION = 103;
//定时器时间间隔
public static final int TIMER_INTERVAL = 40 * 1000;
public static final String RECEIVER_ACTION = "com.efrobot.robot.mydemo.action.CheckTimeout";
private Context context;
private PowerManager.WakeLock wakeLock;
public static Handler handler;
private Thread thread;
private AlarmManager am;
private PendingIntent hbSender;
private BatteryReceiver br;
private boolean isRun = true;
public ConncetionManager connManager;
public Timer(Context context) {
this.context = context;
}
public void startTimer() {
// lockCPU();
// startThreadTimer();
// startHandlerTimer();
// startAlarmTimer();
startBroadCastTimer();
}
public void stopTimer() {
isRun = false;
stopAlarmTimer();
stopBroadCastTimer();
unLockCPU();
}
/**
* 线程定时器
* 亮屏情况下:定时器严格按照设置的心跳时间执行
* 黑屏情况下:不锁cpu,定时器基本不可控,执行时间不确定
* 锁cpu,严格按照定时器时间执行
*/
private void startThreadTimer() {
thread = new Thread() {
@Override
public void run() {
while (isRun) {
sendNetMessage("Thread Timer");
try {
Thread.sleep(TIMER_INTERVAL);
} catch (InterruptedException ie) {
Log.e(TAG, "threadtimer error", ie);
}
}
}
};
thread.start();
}
/**
* 初始化handler定时器
*
*/
private void initHandler() {
handler = new Handler() {
@Override
public void handleMessage(Message message) {
switch (message.what) {
case TIMER_HANDLER_ACTION:
sendNetMessage("Handler Timer");
if (isRun) {
startHandlerTimer();
}
break;
case TIMER_ALARM_ACTION:
sendNetMessage("Alarm Timer");
if (isRun) {
startAlarmTimer();
}
break;
case TIMER_BROADCAST_ACTION:
sendNetMessage("BroadCast Timer");
}
}
};
}
/**
* 启动handler定时器
* 亮屏情况下:定时器严格按照设置的心跳时间执行
* 黑屏情况下:不锁cpu,定时器基本不可控,执行时间不确定
* 锁cpu,严格按照定时器时间执行
*/
private void startHandlerTimer() {
initHandler();
handler.sendEmptyMessageDelayed(TIMER_HANDLER_ACTION, TIMER_INTERVAL);
}
/**
* 启动AlarmManager定时器
* 亮屏情况下:定时器严格按照设置的心跳时间执行
* 黑屏情况下:定时器被合并到每5分钟执行一次
*/
private void startAlarmTimer() {
initHandler();
am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(context, HBReceiver.class);
intent.setAction(RECEIVER_ACTION);
hbSender = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
long firstime = System.currentTimeMillis();
//此写法只支持在android4.4以上测试
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
Log.i(TAG, "setWindow");
am.setWindow(AlarmManager.RTC_WAKEUP, firstime + TIMER_INTERVAL, TIMER_INTERVAL, hbSender);
}
}
private void stopAlarmTimer() {
if (am != null) {
if (hbSender != null) {
am.cancel(hbSender);
hbSender = null;
Log.i(TAG, "stopAlarmTimer");
}
}
}
/**
* 系统广播定时器
* 接收系统电量变化广播和时间变化广播
* 亮屏情况下:电量变化广播是20秒一次,时间变化广播是1分钟一次
* 黑屏情况下:时间不定,2/3/5/7可能接收到一次广播
*/
private void startBroadCastTimer() {
initHandler();
//动态注册:创建一个IntentFilter的实例
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("android.intent.action.BATTERY_CHANGED");
intentFilter.addAction("android.intent.action.TIME_TICK");
intentFilter.addAction("com.efrobot.robot.action.CheckTimeout");
br = new BatteryReceiver();
context.registerReceiver(br, intentFilter);
}
private void stopBroadCastTimer() {
if (br != null) {
context.unregisterReceiver(br);
Log.i(TAG, "stopBroadCastTimer");
}
}
/**
* 收到服务器向客户端发送的检测数据包时调用该方法,向服务器发送心跳数据包
*/
public void startNetTimer() {
sendNetMessage("Net Timer");
}
private void lockCPU() {
if (wakeLock == null || !wakeLock.isHeld()) {
PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, this.getClass().getSimpleName());
wakeLock.acquire();
Log.i(TAG, "lock");
}
}
private void unLockCPU() {
Log.i(TAG, "unLock");
if (wakeLock == null) {
return;
}
if (wakeLock.isHeld()) {
wakeLock.release();
}
wakeLock = null;
}
/**
* 通过socket向服务器发送数据
* @param message
*/
private void sendNetMessage(String message) {
//通过socket发送心跳数据
if (connManager != null) {
connManager.sendMessage(message.getBytes());
}
writeLog(message);
}
private void writeLog(String log) {
Date date = new Date();
String text = date.toString() + " " + log;
Log.i(TAG, text);
File f = Environment.getExternalStorageDirectory();//获取SD卡目录
File fileDir = new File(f, "timerlog.txt");
try {
FileWriter fileWriter = new FileWriter(fileDir, true);
fileWriter.write(text + "\r\n");
fileWriter.flush();
fileWriter.close();
} catch (IOException e) {
Log.e(TAG, "writeLogFile error", e);
}
}
}package com.efrobot.mydemo;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
/**
* Created by tallboy on 2016/7/2.
* 接收AlarmManager定时器广播
*/
public class HBReceiver extends BroadcastReceiver {
private static final String TAG = HBReceiver.class.getSimpleName();
@Override
public void onReceive(Context context, Intent intent) {
Log.i(TAG, "onReceiver action=" + intent.getAction());
Timer.handler.sendEmptyMessage(Timer.TIMER_ALARM_ACTION);
}
}package com.efrobot.mydemo;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
/**
* Created by tallboy on 2016/6/3.
* 接收系统的电量广播、时间变化广播
*/
public class BatteryReceiver extends BroadcastReceiver {
private static final String TAG = BatteryReceiver.class.getSimpleName();
public BatteryReceiver() {
}
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
Log.i(TAG, "onReceive " + action);
Timer.handler.sendEmptyMessage(Timer.TIMER_BROADCAST_ACTION);
}
}
<receiver
android:name="com.efrobot.mydemo.HBReceiver" >
<intent-filter>
<action android:name="com.efrobot.robot.mydemo.action.CheckTimeout" />
</intent-filter>
</receiver>总结:
以上各种方法都存在一定问题,加了cpu锁,某些手机上耗电会明显增加,不加cpu锁又不能保证定时器按时执行,通过server向app发送检测心跳包,又会增加服务器端压力
根据目前掌握的知识来看确实没有完美的解决方案,不知道微信、手机QQ这样的软件是如何实现的定时器。
本文探讨了Android 4.4及以上版本中定时器的执行问题,特别是在屏幕关闭时定时任务会被合并到每5分钟执行一次的现象。文章对比了线程定时器、Handler定时器、AlarmManager定时器等不同实现方式,并提供了测试代码。
119

被折叠的 条评论
为什么被折叠?



