各种android定时器验证

时间:2023-01-17 23:24:13

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);
    }

}



Manifest.xml文件增加以下内容

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