移动应用APP数据统计SDK部分功能实现

Monicat - SDK

简单说明:

生成JAR包或者AAR文件,可直接加入项目中使用

代码示例:

import android.app.Application;
import com.windfallsheng.monicat.action.MonicatManager;
import com.windfallsheng.monicat.base.Configuration;
import com.windfallsheng.monicat.command.UploadStrategy;

public class MySupportApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
         // 使用默认配置参数;
//        MonicatManager.getInstance()
//                .initConfig(new MonicatConfig.Builder(this).build())
//                .monitor();
        // ( 1 ) 初始化配置参数;
        MonicatConfig config = new MonicatConfig.Builder(this)
                // 传入Application的Context实例
                // 设置开启debug模式,输出打印日志,默认为null
                // 没有设置时,会根据外层项目app的模式(debug or release)模式来选择
                .debugEnable(true)
                // 是否关闭统计功能;
                //.enableMonicat(false)
                // 设置是否打开启动次数统计功能,默认为true
                .enableSessionStatistics(true)
                // 设置前后台切换的间隔时间(单位:毫秒)最大值,默认为30s
                .setSessionTimoutMillis(5 * 1000)
                // 设置数据上报策略,默认为INSTANT 即时上报数据
                .setUploadStrategy(UploadStrategy.INSTANT)
                // 设置数据上报策略为定时上报
                //.setUploadStrategy(UploadStrategy.TIMED_TASK)
                // 设置定时上报 时的小时和分钟,小时格式:大于等于0,小于24,
                // 分钟格式:大于等于0,小于60。
                //.setTriggerTime(13,  42)
                // 设置数据上报策略为间隔上报
                //.setUploadStrategy(UploadStrategy.PERIOD)
                // 设置间隔上报 的间隔时间(单位:毫秒),默认为30分钟,最小值为5分钟
                //.setPeriodTime(5  *  60  *  1000)
                // 设置数据上报策略为批量上报
                //.setUploadStrategy(UploadStrategy.BATCH)
                // 设置批量值,默认为50条
                //.setBatchValue(30)
                .build();
        // ( 2 ) 打开Monicat的监控功能;
        MonicatManager.getInstance().initConfig(config).monitor();
        // 配置完成。
    }
}

注意:

如果设置上报策略为TIMED_TASK(定时上报)或者PERIOD(间隔上报)时需要在manifest中注册以下这个服务:

<service android:name="com.windfallsheng.monicat.service.TimedService">
    <intent-filter>
        <action android:name="monicat.service.action.timedservice_timed_upload" />
    </intent-filter>
</service>
  1. 信息说明:

统计策略:(ONLY_WIFI功能实现未完成)

INSTANT

即时上报

实时发送,APP每产生一条消息都会发送到服务器。

APP_LAUNCH

启动上报

只在启动时发送,本次产生的所有数据在下次启动时发送。

PERIOD

间隔上报

启动应用后每隔一段时间,一次性发送到服务器。

BATCH

批量发送

默认当消息数量达到50条时发送一次。

TIMED_TASK

定时上报

指定一天中的某个时间一次性上报所有本地缓存中未上报的数据

ONLY_WIFI

WIFI网络下上报

只在WIFI状态下发送,非WIFI情况缓存到本地

  • 功能说明:
  1. 会话统计:

记录应用启动和退出,包括启动次数,启动和退出的时间,可以统计出来每次使用时长等数据

MonicatManager.getInstance().monitor(); 方法调用后,主要由

SessionStatisticsManager类内部来实现。

  1. 页面统计:

记录某个页面开闭状态,能统计出某个页面访问的次数、时间等,也能监测出部分用户使用应用时各页面之间的跳转轨迹。(数据库本地缓存未完善)

使用方法1:

MonicatManager.getInstance().registerPage(Context context);

或者:

MonicatManager.getInstance().registerPage(Context context, String pageName)

注销对某个页面的开闭状态的记录

MonicatManager.getInstance().unregisterPage(Context context);

使用方法2
标记一次页面访问的开始:
MonicatManager.getInstance().trackBeginPage(Context context);
或者:
MonicatManager.getInstance().trackBeginPage(Context context, String pageName);
标记一次页面访问的结束:
MonicatManager.getInstance().trackEndPage(Context context);
或者:
MonicatManager.getInstance().trackEndPage(Context context, String pageName);

方法1代码示例:

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    @Override
    protected void onResume() {
        super.onResume();
	// 标记一次页面访问的开始
	MonicatManager.getInstance().trackBeginPage(this);
	或者:
        MonicatManager.getInstance().trackBeginPage(this, "主界面");
    }

    @Override
    protected void onPause() {
        super.onPause();
	// 标记一次页面访问的结束
	MonicatManager.getInstance().trackEndPage(this);
	或者:
        MonicatManager.getInstance().trackEndPage(this, "主界面");
    }

}
  1. 事件统计:

包括普通事件、自定义事件统计,可以统计次数,即:统计指定行为被触发的次数;
也可统计时长,即,统计两个指定行为之间的消耗时间,以及其它相关数据。(数据库本地缓存未完善)

普通事件方法:

//标记一次普通事件的开始
MonicatManager.getInstance().trackBeginEvent(Context context, String eventName);
//标记一次普通事件的结束
MonicatManager.getInstance().trackEndEvent(Context context, String eventName);

自定义事件方法:

// properties Key-Value参数对,key和value都是String类型
	Properties prop = new Properties()
        		.addProperty("name", "value")
        		.addProperty("level", "51");
	//标记一次普通事件的开始
	MonicatManager.getInstance().trackCustomBeginEvent(Context context, String eventName, Properties properties);
	//标记一次普通事件的结束
	MonicatManager.getInstance().trackCustomEndEvent(Context context, String eventName, Properties properties);

代码示例:

mButtonLogin.setOnClickListener(new View.OnClickListener() {
    
@Override
    public void onClick(View view) {
                Properties prop = new Properties()
                		.addProperty("key", "value")
                		.addProperty("level", "51");   
                MonicatManager.getInstance().trackCustomBeginEvent(MainActivity.this, "button_click login事件", prop);                                                      
    }
});


  1. Configuration的动态配置问题:

首先保证在项目的Application中,已经配置过Configuration的相关参数,之后如果在其它地方需要修改一些配置参数,可以进行动态的修改,比如:

    Configuration config = MonicatManager.getInstance().getConfig();
    config.
intervalTime = 10 * 1000;
    MonicatManager.getInstance().setConfig(config);

或者(推荐用此方法)按照Application中的配置步骤,再设置一次, 如:

      Configuration config = new Configuration

                .Builder(MonicatManager.getInstance().getContext())

                .setIntervalTime(10 * 1000)

                .build();

        MonicatManager.getInstance().init(config);
  1. Log日志的输入:

Log日志的输入由项目中的 LogUtils 类来实现,我们可以手动设置log日志的开关是否打开(设置方式,见Configuration的配置选项),如果不设置,会根据外层app 项目编译的版本(debug 或者 release)来判断是否输入log日志内容。

  1. 相关类说明:
  1. MonicatManager 核心调用类

   根据上报策略等配置参数,进行相应的业务逻辑处理;注意要在本类的 monitor()方法中调用 registerActivityLifecycleCallbacks()
 
* 并且传入SwitchEventManager.getInstance()实例,来实现监听应用的前后台切换事件;同时本类作为被观察者,在需要上报数据时能通知到各个观察者,
 
* 也即各种类型数据的处理类(比如会话类型数据的处理类 {@link SessionStatisticsManager}),在收到通知时可以各自上报自己的数据;
 
* 如果上报策略是 {@link UploadStrategy.INSTANT即时上报,则各个类自行上传数据(或者也可以由本类实现统一上传所有数据表的数据,
 
* 可在本类的 onDataChangeListener() 方法中加判断条件进行处理)
 
* 其它的上报策略可以由本类集中统一上传所有数据表的数据,或者通知观察者各自上传自己的数据(目前采用这个方法)
 *
对于 {@link BATCH} 批量上传的情况,每个类在新增数据成功后,需要调用
 
* 本类的 onDataChangeListener() 方法,在这里会计算所有类型的数据新增数据的数量的总和,再判断是否达到配置里设置的批量上限值,
 
* 之后再进行上传数据的操作。

  1. Configuration 参数配置类

 * 配置一些必要的参数,非必要的参数有默认值

  1. SessionStatisticsManager会话统计:

   用于统计启动次数,启动时间和退出时间等
 
* 以下3种情况下,会视为用户打开一次新的会话:
 
* 1) 应用第一次启动,或者应用进程在后台被杀掉之后启动
 
* 2) 应用退到后台或锁屏超过X之后再次回到前台
 
* X秒通过Configuration.setTimeInterval(int)函数设置,默认为30000ms,即30
 
* 主要功能方法:
 
* 监听应用程序的前后台切换状态;
 
* 缓存启动数据到本地数据库,包括启动时间,退出时间,启动(包括前后台切换)记录;
 
* 作为观察者,监听{@link MonicatManager} 发出的通知并上报数据,或者根据上传数据的即时上报策略上传数据;

  1. StatisticsSQLiteHelper 数据库创建类:

 * 用户行为统计的数据库,保证StatisticsSQLiteHelper类是单例的;完成数据库各表的创建;
 
* 实现数据库的升级、降级及数据迁移等主要功能
 * 对数据库的升级、降级能及数据迁移等功能,项目中已经有代码示例,可根据后续业务需求扩展。

  1. 扩展的其它方法说明:
  1. 调用这个方法可以手动的、一次性上传所有本地缓存数据。
      MonicatManager.getInstance().notifyUploadData();
  1. 如果设置了打开启动次数统计功能(默认设置也是 true),如:
Configuration config = new Configuration
                        .Builder(context)
				// 设置是否打开启动次数统计功能,默认为true
        			.setOnStartNum(true)
				// 其它参数配置省略
				……………………
				……………………
                        .build();

那么:

Boolean isForeground = MonicatManager.getInstance().isForeground;

这个方法可以得到时当前app应用是在前台( isForeground = true )还是后台( isForeground = false )

int appStatus = MonicatManager.getInstance().app_status;

这个方法可以得到时当前app运行状态:1为刚启动打开应用,0为应用正在运行中(包括在前后台的情况)

特别说明:

有关项目中SQLite数据库缓存数据及其它CRUD相关的功能及网络请求的功能,在本项目中只是粗略实现,未优化,有需要时可以进行修改,替换为其它ORM框架,完善网络请求的代码封装,另外相应的后台需要再开发实现,本文只讲述Android端部分的实现。

核心调用类MonicatManager代码示例:

package com.windfallsheng.monicat.action;

import android.app.AlarmManager;
import android.app.Application;
import android.content.Context;
import android.os.Looper;
import android.support.annotation.NonNull;
import android.text.TextUtils;
import android.util.Log;

import com.windfallsheng.monicat.base.UploadStrategy;
import com.windfallsheng.monicat.common.MonicatConstants;
import com.windfallsheng.monicat.listener.BatchDataChangeListener;
import com.windfallsheng.monicat.listener.UploadDataObserver;
import com.windfallsheng.monicat.model.BatchInfo;
import com.windfallsheng.monicat.model.Properties;
import com.windfallsheng.monicat.service.TimedService;
import com.windfallsheng.monicat.util.LogUtils;
import com.windfallsheng.monicat.util.TimeUtils;
import com.windfallsheng.monicat.util.TimedTaskUtils;

import java.util.Iterator;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;


/**
 * CreateDate: 2018/4/9.
 * <p>
 *
 * @author lzsheng
 * <p>
 * Description: 根据上报策略等配置参数,进行相应的业务逻辑处理;
 * <p>
 * 注意要在本类的 {@link #monitor()}方法中必须要调用
 * {@link Application#registerActivityLifecycleCallbacks(Application.ActivityLifecycleCallbacks)}注册Activity生命周期监听,
 * 并且传入{@link SwitchEventManager}实例,来实现监听应用的前后台切换事件;
 * <p>
 * 同时本类作为被观察者,在需要上报数据时能通知到各个观察者,
 * 也即各种类型数据的处理类(比如会话类型数据的处理类 {@link SessionStatisticsManager}),在收到通知时可以各自上报自己的数据;
 * <p>
 * {@link UploadStrategy#INSTANT}
 * <p>
 * 如果上报策略是即时上报,则各个类自行上传数据(或者也可以由本类实现统一上传所有数据表的数据,
 * 可在本类的 onDataChangeListener() 方法中加判断条件进行处理),
 * 其它的上报策略可以由本类集中统一上传所有数据表的数据,或者通知观察者各自上传自己的数据(目前采用这个方法)
 * <p>
 * {@link UploadStrategy#BATCH}
 * <p>
 * 对于批量上传的情况,每个类在初始化本类相关数据或者新增数据成功后,需要调用
 * 本类的{@link #onBatchDataChanged(BatchInfo)}方法,在这里会累计所有类型的数据的数量,并且判断是否达到批量上限值,
 * 之后再进行上传数据的操作。
 * <p>
 * Version:
 */
public class MonicatManager implements BatchDataChangeListener {

    private final String TAG = "MonicatManager";
    private static volatile MonicatManager instance = null;

    private MonicatConfig mMonicatConfig;
    private Context mContext;
    private UploadStrategy mUploadStrategy;
    private boolean mEnableSessionStatistics;
    private boolean mEnablePageStatistics;
    private boolean mEnableEventStatistics;
    private int mBatchValue;
    /**
     * 所有类型的数据已缓存的总数总和
     */
    private int mTotalBatchCount;
    private ScheduledExecutorService mUploadExecutorService;
    private CopyOnWriteArrayList<UploadDataObserver> mUploadDataObservers;
    private SessionStatisticsManager mSessionStatisticsManager;
    private PageStatisticsManager mPageStatisticsManager;
    private EventStatisticsManager mEventStatisticsManager;

    private MonicatManager() {

    }

    public static MonicatManager getInstance() {
        if (instance == null) {
            synchronized (MonicatManager.class) {
                if (instance == null) {
                    instance = new MonicatManager();
                }
            }
        }
        return instance;
    }

    public MonicatConfig getMonicatConfig() {
        return mMonicatConfig;
    }

    public Context getContext() {
        return mContext;
    }

    public UploadStrategy getUploadStrategy() {
        return mUploadStrategy;
    }

    /**
     * 初始化配置参数
     *
     * @param monicatConfig
     */
    public MonicatManager initConfig(@NonNull MonicatConfig monicatConfig) {
        if (monicatConfig == null) {
            throw new NullPointerException("Monicat:monicatConfig == null.");
        }
        this.mMonicatConfig = monicatConfig;
        this.mContext = monicatConfig.context;
        this.mUploadStrategy = monicatConfig.uploadStrategy;
        return instance;
    }

    /**
     * 根据上报策略等配置参数处理相关业务逻辑
     */
    public void monitor() {
        enforce("Application", "monitor");
        if (mMonicatConfig == null) {
            throw new NullPointerException("Monicat:mMonicatConfig == null.");
        }
        //是你当前方法执行堆栈
//        Thread.currentThread().getStackTrace()[1];
        //就是上一级的方法堆栈 以此类推
//        Thread.currentThread().getStackTrace()[2];
        StackTraceElement[] temp = Thread.currentThread().getStackTrace();
        StackTraceElement a = (StackTraceElement) temp[2];
        //这就是调用当前方法的方法名称
        Log.d(MonicatConstants.SDK_NAME, TAG + ":::method:monitor#currentMethodName=" + a.getMethodName());

        // 根据配置信息,完善内部必要的功能设置和逻辑关系处理
        LogUtils.init(mMonicatConfig.debugEnable);
        Boolean enableMonicat = mMonicatConfig.enableMonicat;
        if (!enableMonicat) {
            LogUtils.d(MonicatConstants.SDK_NAME, TAG + ":::method:monitor#return#enableMonicat=" + enableMonicat);
            return;
        }

        initConfigParams();

//        if (isDebugConfig == null) {// 没有设置isDebug值时,会根据外层项目app的模式(debug or release)模式来选择
//            Boolean isDebug = LogUtils.getBuildConfig(mContext);
//            Log.d(Constants.SDK_NAME, "Monicat: debugEnable==" + isDebug.booleanValue());
//        } else {
//            Log.d(Constants.SDK_NAME, "Monicat: isDebugConfig==" + isDebugConfig.booleanValue());
//        }
        TimecalibrationManager.getInstance().getCurrentServerTime();

        handleEnableStatistics();

        handleUploadStrategy();
    }

    private void initConfigParams() {
        mContext = mMonicatConfig.context;
        mUploadStrategy = mMonicatConfig.uploadStrategy;
        mEnableSessionStatistics = mMonicatConfig.enableSessionStatistics;
        mEnablePageStatistics = mMonicatConfig.enablePageStatistics;
        mEnableEventStatistics = mMonicatConfig.enableEventStatistics;
    }

    private void handleEnableStatistics() {
        // 是否开始会话统计的功能,若开启了此功能,要加入上报数据通知的观察者中
        if (mEnableSessionStatistics) {
            if (mContext == null) {
                throw new IllegalArgumentException("Monicat:mContext == null");
            }
            if (!(mContext instanceof Application)) {
                throw new IllegalArgumentException("Monicat:The context instance must be of type application");
            }
            /**
             * 有需要监听前后台变化,或有其它依据于此的业务逻辑处理的,需要添加到这个观察者里
             * 使用{@link SwitchEventManager}的监听
             */
            if (mSessionStatisticsManager == null) {
                mSessionStatisticsManager = new SessionStatisticsManager((Application) mContext);
            }
            //添加到这个观察者里,以便在需要上报数据时能被通知到
            if (mUploadStrategy != UploadStrategy.INSTANT) {
                addUploadDataObserver(mSessionStatisticsManager);
            }
            mSessionStatisticsManager.handleAppLaunch();
        }
        if (mEnablePageStatistics) {
            if (mPageStatisticsManager == null) {
                mPageStatisticsManager = new PageStatisticsManager();
            }
            if (mUploadStrategy != UploadStrategy.INSTANT) {
                addUploadDataObserver(mPageStatisticsManager);
            }
        }
        if (mEnableEventStatistics) {
            if (mEventStatisticsManager == null) {
                mEventStatisticsManager = new EventStatisticsManager();
            }
            if (mUploadStrategy != UploadStrategy.INSTANT) {
                addUploadDataObserver(mEventStatisticsManager);
            }
        }

        // 如果还有其它的数据类型要统计,也要添加到这个观察者里,以便在需要上报数据时能被通知到
//        addUploadDataObserver(***Observer);
    }

    /**
     * 判断上报策略,进行相关逻辑处理
     */
    private void handleUploadStrategy() {
        switch (mUploadStrategy) {
            case APP_LAUNCH:
                notifyUploadData();
                break;
            case TIMED_TASK:
                // 定时上报的策略
                if (mContext == null) {
                    throw new IllegalArgumentException("Monicat: Enabling this feature requires the " +
                            MonicatConfig.class.getSimpleName() + " to set the context parameter by method setContext()");
                }
                long timeMillis = mMonicatConfig.triggerTime;
                String triggerTime = TimeUtils.timeLongToDefaultDateStr(timeMillis);
                LogUtils.d(MonicatConstants.SDK_NAME, TAG + ":::method:monitor#TIMED_TASK#timeMillis="
                        + timeMillis + ", triggerTime=" + triggerTime);
                /**
                 * 通过定时时钟实现定时任务,在定时时钟中开启服务 {@link TimedService},到指定时间时,再通知各观察者上传数据
                 */
                TimedTaskUtils.startTimedTask(mContext, timeMillis, AlarmManager.INTERVAL_DAY,
                        TimedService.class, TimedService.ACTION_TIMEDSERVICE_TIMED_UPLOAD);
                break;
            case PERIOD:
                // 间隔时间上报的策略,用ScheduledExecutorService去实现间隔时间上报数据
                long periodTime = mMonicatConfig.periodTime;
                LogUtils.d(MonicatConstants.SDK_NAME, TAG + ":::method:monitor#periodTime=" + periodTime);
                if (mUploadExecutorService == null) {
                    mUploadExecutorService = Executors.newSingleThreadScheduledExecutor();
                }
                mUploadExecutorService.scheduleAtFixedRate(new Runnable() {
                    @Override
                    public void run() {
                        LogUtils.d(MonicatConstants.SDK_NAME, TAG + ":::method:monitor#mUploadExecutorService#run#currentTime=" + System.currentTimeMillis());
                        notifyUploadData();
                    }
                }, 0, periodTime, TimeUnit.MILLISECONDS);
                break;
            case BATCH:
                // 批量上报的策略
                handleBatchStrategyService();
                break;
            default:
                break;
        }
    }

    private void handleBatchStrategyService() {
        mBatchValue = mMonicatConfig.batchValue;
        if (mSessionStatisticsManager != null) {
            // 首先注入回调接口的实例
            mSessionStatisticsManager.setBatchDataChangeListener(this);
        }
        if (mPageStatisticsManager != null) {
            mPageStatisticsManager.setBatchDataChangeListener(this);
        }
        if (mEventStatisticsManager != null) {
            mEventStatisticsManager.setBatchDataChangeListener(this);
        }
    }

    /**
     * 各类型的数据在初始化(通过查询本地数据库)各自未上传的数据总和时,回调此方法,本类会累计入批量值的总和,
     * 并且之后再有数据变化,比如新增数据后,可以通过接口回调这个方法告知本类,本类会累计这个批量值,
     * 在本类累计的{@link MonicatManager#mTotalBatchCount}值,已达到批量上报策略的设定值时就通知各类型数据上传数据,
     * 并且在通知之前再初始化本类的{@link MonicatManager#mTotalBatchCount}值
     *
     * @param batchInfo 新增一条数据成功时,传递的参数值为1;初始化数据总和时,传递的参数是这个总和的值
     */
    @Override
    public void onBatchDataChanged(BatchInfo batchInfo) {
        LogUtils.d(MonicatConstants.SDK_NAME, TAG + ":::method:#onBatchDataChanged#batchInfo=" + batchInfo);
        if (batchInfo == null) {
            LogUtils.d(MonicatConstants.SDK_NAME, TAG + ":::method:onBatchDataChanged#return#batchInfo == null");
            return;
        }
        synchronized (MonicatManager.class) {
            mTotalBatchCount += batchInfo.getCount();
            LogUtils.d(MonicatConstants.SDK_NAME, TAG + ":::method:monitor#onBatchDataChanged#mTotalBatchCount=" + mTotalBatchCount);
            // 数据大于设定值时,就上传数据;
            if (mTotalBatchCount >= mBatchValue) {
                // TODO: 2018/5/2 如果是在这个类里集中上传所有表的数据时,可以先查询各表的数据,再请求
                // TODO: 2018/5/4  另一种情况中各个相关的类去处理各自的数据,这时可以通知观察者各自上传数据
                /**
                 * 先初始化{@link MonicatManager#mTotalBatchCount}值,
                 * 再调用{@link MonicatManager#notifyUploadData()}方法,因为各个数据类型在被通知上报数据并且各自成功后,
                 * 会再次回调{@link MonicatManager#onBatchDataChanged(int)}方法
                 */
                notifyUploadData();// 再通知各个数据类各自上传数据
                mTotalBatchCount = 0;
            }
        }
    }

    /**
     * 手动上传所有当前开启统计功能的数据;
     */
    public void uploadmEnableStatisticsData() {
        notifyUploadData();
    }

    /**
     * 添加进行数据上传操作的观察者
     */
    protected void addUploadDataObserver(UploadDataObserver uploadDataObserver) {
        if (uploadDataObserver == null) {
            throw new NullPointerException("Monicat: The uploadDataObserver parameter passed in is null.");
        }
        if (mUploadDataObservers == null) {
            mUploadDataObservers = new CopyOnWriteArrayList<>();
        }
        if (!mUploadDataObservers.contains(uploadDataObserver)) {
            mUploadDataObservers.add(uploadDataObserver);
        }
    }

    /**
     * 删除进行数据上传操作的观察者
     */
    protected void removeUploadDataObserver(UploadDataObserver uploadDataObserver) {
        if (mUploadDataObservers != null && mUploadDataObservers.contains(uploadDataObserver)) {
            mUploadDataObservers.remove(uploadDataObserver);
        }
    }

    /**
     * 删除所有进行数据上传操作的观察者
     */
    protected void removeAllUploadDataObservers() {
        if (mUploadDataObservers != null && mUploadDataObservers.size() > 0) {
            Iterator<UploadDataObserver> it = mUploadDataObservers.iterator();
            while (it.hasNext()) {
                it.remove();
            }
        }
    }

    /**
     * 通知所有的观察者,可以开始上传数据
     */
    private void notifyUploadData() {
        if (mUploadDataObservers != null && mUploadDataObservers.size() > 0) {
            for (UploadDataObserver observer : mUploadDataObservers) {
                LogUtils.d(MonicatConstants.SDK_NAME, TAG + ":::method:notifyUploadData#observer="
                        + observer.getClass().getSimpleName());
                observer.uploadData();
            }
        }
    }

    private void enforce(String className, String methodName) {
        if (Looper.myLooper() != Looper.getMainLooper()) {
            throw new IllegalStateException(className + " object calls method " + methodName + " in the non-main thread " + Thread.currentThread());
        }
    }

    /**
     * 标记一次页面访问;
     *
     * @param context 页面的设备上下文
     */
    public void trackPage(@NonNull Context context) {
        if (!mEnablePageStatistics) {
            throw new NullPointerException("Monicat: mEnablePageStatistics == false.");
        }
        if (context == null) {
            throw new NullPointerException("Monicat: The 'context' parameter passed " +
                    "in the 'trackbeginpage(Context context)' method is null or empty.");
        }
        String className = context.getClass().getName();
        enforce(className, "trackPage");
        mPageStatisticsManager.savePageInfo(className, "", MonicatConstants.PAGE_OPEN);
    }

    /**
     * 标记一次页面访问;
     *
     * @param context 页面的设备上下文
     */
    public void trackPage(@NonNull Context context, @NonNull String pageName) {
        if (!mEnablePageStatistics) {
            throw new NullPointerException("Monicat: mEnablePageStatistics == false.");
        }
        if (context == null) {
            throw new NullPointerException("Monicat: The 'context' parameter passed " +
                    "in the 'trackbeginpage(Context context)' method is null or empty.");
        }
        String className = context.getClass().getName();
        enforce(className, "trackPage");
        mPageStatisticsManager.savePageInfo(className, pageName, MonicatConstants.PAGE_OPEN);
    }

    /**
     * 标记一次页面访问的开始
     *
     * @param context 页面的设备上下文
     */
    public void trackBeginPage(@NonNull Context context) {
        if (!mEnablePageStatistics) {
            throw new NullPointerException("Monicat: mEnablePageStatistics == false.");
        }
        if (context == null) {
            throw new NullPointerException("Monicat: The 'context' parameter passed " +
                    "in the 'trackbeginpage(Context context)' method is null or empty.");
        }
        String className = context.getClass().getName();
        enforce(className, "trackBeginPage");
        mPageStatisticsManager.savePageInfo(className, "", MonicatConstants.PAGE_OPEN);
    }

    /**
     * 标记一次页面访问的开始
     *
     * @param context  页面的设备上下文
     * @param pageName 自定义页面名称
     */
    public void trackBeginPage(@NonNull Context context, @NonNull String pageName) {
        if (!mEnablePageStatistics) {
            throw new NullPointerException("Monicat: mEnablePageStatistics == false.");
        }
        if (pageName == null) {
            throw new NullPointerException("Monicat: The 'context' parameter passed " +
                    "in the 'trackbeginpage(Context context, String pageName)' method is null or empty.");
        }
        String className = context.getClass().getName();
        enforce(className, "trackBeginPage");
        // TODO: 2020/2/5 判断是否已注册 
        mPageStatisticsManager.savePageInfo(className, pageName, MonicatConstants.PAGE_OPEN);
    }

    /**
     * 标记一次页面访问的结束
     *
     * @param context 页面的设备上下文
     */
    public void trackEndPage(@NonNull Context context) {
        if (!mEnablePageStatistics) {
            throw new NullPointerException("Monicat: mEnablePageStatistics == false.");
        }
        if (context == null) {
            throw new NullPointerException("Monicat: The 'context' parameter passed " +
                    "in the 'trackEndPage(Context context)' method is null or empty.");
        }
        String className = context.getClass().getName();
        enforce(className, "trackEndPage");
        mPageStatisticsManager.savePageInfo(className, "", MonicatConstants.PAGE_CLOSE);
    }

    /**
     * 标记一次页面访问的结束
     *
     * @param context  页面的设备上下文
     * @param pageName 自定义页面名称
     */
    public void trackEndPage(@NonNull Context context, @NonNull String pageName) {
        if (!mEnablePageStatistics) {
            throw new NullPointerException("Monicat: mEnablePageStatistics == false.");
        }
        if (context == null) {
            throw new NullPointerException("Monicat: The 'context' parameter passed " +
                    "in the 'trackEndPage(Context context, String pageName)' method is null or empty.");
        }
        String className = context.getClass().getName();
        enforce(className, "trackEndPage");
        mPageStatisticsManager.savePageInfo(className, pageName, MonicatConstants.PAGE_CLOSE);
    }

    /**
     * 标记一次普通事件的开始
     *
     * @param context   页面的设备上下文
     * @param eventName 事件名称
     */
    public void trackBeginEvent(@NonNull Context context, @NonNull String eventName) {
        if (!mEnableEventStatistics) {
            throw new NullPointerException("Monicat: mEnableEventStatistics == false.");
        }
        if (context == null) {
            throw new NullPointerException("Monicat: The 'context' parameter passed " +
                    "in the 'trackbeginpage(Context context)' method is null or empty.");
        }
        String className = context.getClass().getName();
        enforce(className, "trackBeginEvent");
        mEventStatisticsManager.saveEventInfo(className, eventName,
                TimecalibrationManager.getInstance().getCurrentServerTime(), 0, null);
    }

    /**
     * 标记一次普通事件的结束
     *
     * @param context   页面的设备上下文
     * @param eventName 事件名称
     */
    public void trackEndEvent(@NonNull Context context, @NonNull String eventName) {
        if (!mEnableEventStatistics) {
            throw new NullPointerException("Monicat: mEnableEventStatistics == false.");
        }
        if (context == null) {
            throw new NullPointerException("Monicat: The parameter context for" +
                    " the method trackEndPage method is null.");
        }
        if (TextUtils.isEmpty(eventName)) {
            throw new NullPointerException("Monicat: The parameter eventName for" +
                    " the method trackEndPage is empty.");
        }
        String className = context.getClass().getName();
        enforce(className, "trackEndEvent");
        mEventStatisticsManager.saveEventInfo(className, eventName, 0,
                TimecalibrationManager.getInstance().getCurrentServerTime(), null);
    }

    /**
     * 标记一次自定义事件的次数;
     *
     * @param context    页面的设备上下文
     * @param eventName  事件名称
     * @param properties 自定义事件Key-Value参数
     */
    public void trackCustomEvent(@NonNull Context context, @NonNull String eventName, @NonNull Properties properties) {
        if (!mEnableEventStatistics) {
            throw new NullPointerException("Monicat: mEnableEventStatistics == false.");
        }
        if (context == null) {
            throw new NullPointerException("Monicat: The 'context' parameter passed " +
                    "in the 'trackEndPage(Context context, String pageName)' method is null or empty.");
        }
        String className = context.getClass().getName();
        enforce(className, "trackCustomEndEvent");
        mEventStatisticsManager.saveEventInfo(className, eventName, System.currentTimeMillis(), 0, properties);
    }

    /**
     * 标记一次自定义事件的开始
     *
     * @param context    页面的设备上下文
     * @param eventName  事件名称
     * @param properties 自定义事件Key-Value参数
     */
    public void trackCustomBeginEvent(@NonNull Context context, @NonNull String eventName, @NonNull Properties properties) {
        if (!mEnableEventStatistics) {
            throw new NullPointerException("Monicat: mEnableEventStatistics == false.");
        }
        if (context == null) {
            throw new NullPointerException("Monicat: The 'context' parameter passed " +
                    "in the 'trackbeginpage(Context context, String pageName)' method is null or empty.");
        }
        String className = context.getClass().getName();
        enforce(className, "trackCustomBeginEvent");
        mEventStatisticsManager.saveEventInfo(className, eventName, TimecalibrationManager.getInstance().getCurrentServerTime(),
                0, properties);
    }

    /**
     * 标记一次自定义事件的结束
     *
     * @param context    页面的设备上下文
     * @param eventName  事件名称
     * @param properties 自定义事件Key-Value参数
     */
    public void trackCustomEndEvent(@NonNull Context context, @NonNull String eventName, @NonNull Properties properties) {
        if (!mEnableEventStatistics) {
            throw new NullPointerException("Monicat: mEnableEventStatistics == false.");
        }
        if (context == null) {
            throw new NullPointerException("Monicat: The 'context' parameter passed " +
                    "in the 'trackEndPage(Context context, String pageName)' method is null or empty.");
        }
        String className = context.getClass().getName();
        enforce(className, "trackCustomEndEvent");
        mEventStatisticsManager.saveEventInfo(className, eventName, 0,
                TimecalibrationManager.getInstance().getCurrentServerTime(), properties);
    }

}

GitHub地址

由于作者水平有限,语言描述及代码实现中难免有纰漏,望各位看官多提宝贵意见!

Hello , World !

感谢所有!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

windfallsheng

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值