⏰ : 全文字数:3150+

: 内容关键字:kotlin, 特点托付,埋点设计

概述

在实际的项目开发中,一定会有埋点上报的需求。可能每个项目上报的办法不一样,有些是经过自动化埋点,不需要开发手动上报,有些是需要开发自己上报。在咱们事务的一个项目中,由于采用的是事务推动迭代的办法,有大量的跟事务相关的埋点,且变动比较大,所以咱们基本上都是开发自己上报。

在之前的 Java 作为主力言语开发的时分,埋点参数上报的封装基本上是运用 Builder 模式,或许运用 Map 的办法来进行上报,而到了 Kotlin 作为主力开发言语后,能够运用 Kotlin 提供的一些语法,更好的封装实现埋点的上报,今日就来谈谈我在实际项目中封装的一种思路。

旧办法

原来的埋点上报,关于上报参数的安排,有些是经过 Builder,有些是运用 Map 来安排。我这儿取神策埋点 SDK 的上报办法为例:

运用 JSONObject 构建 Json 数据的办法上报

try {
    JSONObject newProperties = new JSONObject();
    newProperties.put("AdSource", "Email");
    properties.put("AdSource", "XXX Store");
    // 再次设定用户渠道,设定无效,"developer@sensorsdata.cn" 的 "AdSource" 特点值仍然是 "XXX Store"
    SensorsDataAPI.sharedInstance().profileSetOnce(newProperties);
} catch (JSONException e) {
    e.printStackTrace();
}

运用 Map 调集的办法上报:

// map调集
Map<String, Number> properties = new HashMap<String, Number>();
properties.put("UserPaid", 1);
properties.put("PointEarned", 12.5);
SensorsDataAPI.sharedInstance().profileIncrement(properties);

个人在运用的时分,觉得这种上报参数的安排办法,比较零散,不易于办理,复用性不强。咱们之前也是运用了差不多的上报办法,但是运用多了之后,发现不是很便利,因而基于 Kotlin 的托付的语法特性,从头进行了封装。

新思路

根底笼统类 BaseEventTrack

首要关于每个埋点,咱们采用了类来进行办理,即每个埋点笼统成一个埋点类。下每个埋点类根底一个笼统类,这个笼统类,来界说一些根底的信息,大概如下:

abstract class BaseEventTrack {
    private val tag = "BaseEventTrack"
    // 一切上报参数得收集,这一块能够自己界说和修正
    private val paramsMap = hashMapOf<String, Any?>()
    // 埋点姓名
    abstract val eventId: String
    // 埋点的归类
    abstract val eventTrackCategory: String
    /**
     * 触发上报当前的埋点
     */
    fun upload() {
        // 
        EventTrack.stat(eventTrackCategory, eventId, paramsMap)
    }
    /**
     * 界说每种类型的托付办法,操控它的get和set办法,如果有新增,能够在这儿新增
     */
    protected fun int(): ReadWriteProperty<BaseEventTrack, Int?> = InnerReadWriteProperty<BaseEventTrack, Int?>(0)
    protected fun string(): ReadWriteProperty<BaseEventTrack, String?> =
        InnerReadWriteProperty<BaseEventTrack, String?>("")
    protected fun long(): ReadWriteProperty<BaseEventTrack, Long?> = InnerReadWriteProperty<BaseEventTrack, Long?>(0)
    protected fun boolean(): ReadWriteProperty<BaseEventTrack, Boolean?> =
        InnerReadWriteProperty<BaseEventTrack, Boolean?>(false)
    protected fun float(): ReadWriteProperty<BaseEventTrack, Float?> =
        InnerReadWriteProperty<BaseEventTrack, Float?>(0F)
    protected fun double(): ReadWriteProperty<BaseEventTrack, Double?> =
        InnerReadWriteProperty<BaseEventTrack, Double?>(0.0)
    // 署理类特点的处理
    inner class InnerReadWriteProperty<T, V>(defaultValue: V) : ReadWriteProperty<T, V> {
        private var currentValue: V = defaultValue
        override fun getValue(thisRef: T, property: KProperty<*>): V {
            Log.i(tag, "thisRef=${thisRef},property=${property}")
            return currentValue
        }
        override fun setValue(thisRef: T, property: KProperty<*>, value: V) {
            Log.i(tag, "thisRef=${thisRef},property=${property},value=${value}")
            currentValue = value
            paramsMap[property.name] = value
        }
    }
}

这个埋点笼统类主要的效果在:

  • 界说了埋点一些根底特点,如 eventId
  • 运用 Kotlin 的特点托付,界说每种常用的根底数据类型的托付署理办法
  • 界说上报埋点的办法

这儿新的思路便是:运用 Kotlin 中的特点托付类 ReadWriteProperty 来处理每个特点的 get 和 set,经过这个托付署理,收集和处理每个字段的值。其实便是起到一个拦截特点赋值和获取值的效果。

当有新的埋点时,咱们能够这样界说埋点数据。比方咱们常见的 Show 埋点:

class ShowEvent : BaseEventTrack() {
    override val eventId: String
        get() = "show"
    override val eventTrackCategory: String
        get() = "10001"
    /**
     * 页面停留时间
     */
    var pageTime by long()
    /**
     * 场景
     */
    var sceneName by string()
}

触发上报

埋点数据类封装好了之后,那么怎么给数据赋值和触发上报呢?这儿运用inline函数,界说一个全局的静态办法,具体内容如下:

/**
 * @param block 初始化函数
 */
inline fun <reified T : BaseEventTrack> uploadEvent(crossinline block: T.() -> Unit) {
    kotlin.runCatching {
        // 实例化泛型T
        val clz = T::class.java
        val instance = clz.getDeclaredConstructor()
        instance.isAccessible = true
        val tInstance = instance.newInstance()
        // 触发目标初始化
        block.invoke(tInstance)
        // 触发上报
        tInstance.upload()
    }.onFailure {
        // 抛出反常,或许运用兜底方案
        Log.w("EventTrack", "upload event error,message=${it.message}")
    }
}

这个办法的参数是一个类型 T 的扩展函数类型,这个函数内部主要做的是初始化埋点数据字段的值。而办法内部的逻辑主要有:

  • 对泛型 T 进行实例化,结构目标
  • 触发目标的初始化操做,即对埋点字段赋值
  • 触发埋点的上报

留意:这儿的办法中的reified有必要加上,不然咱们无法实例话一个泛型类型,这也是和 Java 不同的当地

比方上面的 Show 埋点,咱们在运用的时分,能够这么运用:

fun uploadShowStat(){
    uploadEvent<ShowEvent> {
        sceneName = "七郎"
        pageTime = 100
    }
}

这样用起来是不是很便利呢~~~~

总结

这儿的新思路,运用了 kotlin 的特点托付和reified的特性,实现了一种埋点参数封装的办法,这种办法优点是:运用办理起来非常的便利,每个埋点都对应一个类,代码可读性较好。缺陷是:过程中可能会参数一些额外的类

当然上面只是提供了一种思路,一些细节没有提到,比方一些反常处理,兜底逻辑,线程安全等,有爱好的能够在上面的根底上实现。

如果想看更多文章,能够到*:

: 公_众_号:七郎的小院
: 七郎的_博_客:我的的博客