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这样的软件是如何实现的定时器。