Android 推送和统计最优轮循(心跳策略)探究实践

时间:2023-03-09 05:17:41
Android 推送和统计最优轮循(心跳策略)探究实践

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

当然性能方面第二种是最优的,功能全面来说第三种最优

自定义统计SDK:

http://www.jianshu.com/p/cd83e81b78aa