1. 前言
在 App 的经营流动中,对用户进行弹窗提醒,是一种常见的经营形式。例如:用户曾经下单但未付款的时候,能够给用户一个优惠券的弹窗提醒。
神策 Android 弹窗 SDK[1] 次要针对的就是上述经营场景,经营人员能够在神策智能经营中配置弹窗的 UI 以及触发弹窗的一些条件,当用户满足配置的条件时,集成了弹窗 SDK 的 App 会展现弹窗。UI 成果如图 1-1 所示:
图 1-1 弹窗 UI 效果图
2. 弹窗的实时性
在很多场景下,弹窗须要很高的实时性。如果弹窗的计算规定通过后端解决,符合条件时再下发给客户端,实时性将得不到保障。
为了解决这一问题,把计算逻辑等放在了客户端。简略来说,就是触发埋点事件之后会判断是否触发弹窗。
此外,弹窗 SDK 为了保障实时性,也做了很多工作,上面就逐个为大家进行介绍。
3. 计划演进
3.1. 计划一:共用埋点数据采集线程
触发弹窗的机会,取决于经营同学在神策智能经营中的配置,那弹窗 SDK 如何来决定是否弹窗呢?
举个例子,经营同学的配置条件是:有商品页面的浏览数据就弹窗。因而,在 App 端监控到有商品页面浏览的埋点数据产生就会弹窗。
埋点数据采集工作是在埋点数据采集线程中执行的。因而,最后的想法是在埋点数据采集线程中监控埋点数据,如果有合乎弹窗条件的数据,那么就展现弹窗。示例代码如下:
// 判断是否弹窗
PlanManager.ensureShowDialog(data);
// 数据缓存到数据库
enqueueEventMessage(data);
这种做法很简略,也能满足需要。它的长处如下:
从代码上来看,逻辑比拟清晰;
判断是否弹窗和埋点数据采集都是在一个线程,不便保护。
然而,它的毛病也很显著:
如果在判断是否弹窗这一步,有阻塞或者异样,那么会影响埋点数据缓存到数据库;
耦合度十分高。当初是弹窗的业务须要监控数据,如果未来其余业务也须要监控数据,那么在埋点数据缓存之前,还须要减少更多的业务逻辑。
为了解决上述问题,咱们拆分了埋点数据采集线程和弹窗判断线程。
3.2. 计划二:拆分埋点数据采集线程和弹窗判断线程
思考到共用埋点数据采集线程的毛病,咱们做了线程的拆分,如图 3-1 所示:
图 3-1 线程拆分示意图
此时,埋点数据采集线程和弹窗判断线程,是两个独立的线程。当埋点数据采集线程有新的数据时,会被动告诉弹窗判断线程,让其解决弹窗业务。
这样做不仅升高了耦合度,并且弹窗业务不会影响到埋点数据采集。即便最极其的状况,比方弹窗判断线程因为某些起因呈现了异样,埋点数据采集线程依然能失常工作。
埋点数据采集线程只须要依据接口,回调给数据接收端就行,示例代码如下:
// 监控数据,并传给注册接口的中央
DataMonitorInterface.trackEvent(data);
// 数据缓存到数据库
mMessages.enqueueEventMessage(eventType.getEventType(), dataObj);
在弹窗判断线程中,收到数据后会缓存在队列,示例代码如下:
public class SFDataMonitorImpl{public void trackEvent(String data){mSFPlanTaskManager.addTriggerTask(new Runnable() {
// 判断是否弹窗
PlanManager.ensureShowDialog(data)
}
}
}
在新的线程中去执行此队列的工作,示例代码如下:
public class SFPlanTriggerRunnable implements Runnable {
@Override
public void run() {Runnable downloadTask = mSFPlanTaskManager.getTriggerTask();
mPool.execute(downloadTask);
}
}
这种计划看上去曾经很完满了,升高了埋点数据缓存和弹窗业务之间的耦合,并且代码上也做了拆分,相互之间的影响很小,同时也能满足各种业务场景。
然而,在测试过程中,咱们发现其实弹窗 SDK 的网络申请是最耗时的一步,为了进步实时性,须要进行优化。
这一步次要是为了申请后端,拿到业务同学配置的弹窗信息。要介绍这一部分的优化,首先须要对弹窗 SDK 运行的流程有所理解,如图 3-2 所示:
图 3-2 弹窗 SDK 运行流程图
大抵流程如下:
弹窗 SDK 初始化后,首先会读取本地缓存的弹窗数据;
在 App 进入前台时,会申请后端的弹窗数据,申请实现后会把后端返回的弹窗数据和本地的弹窗数据做比照,只取更新的局部;
接着解决埋点数据的工作。
将埋点数据采集和弹窗判断放到各自的串行队列中,具备如下长处:
外面的工作会依照咱们增加的程序进行执行;
个别状况下,不须要思考并发导致数据不平安的问题。
然而,也有毛病:
当串行队列中的某一个工作产生阻塞时,其后的工作都会提早执行,特地是此队列中还存在网络申请的工作。因为须要应用网络申请的后果,所以当网络申请实现后能力持续解决其余工作;
所有的工作都由一个线程来执行,特地在初始化的时候,串行队列的累赘会比拟重,除了图 3-2 中的两次 IO 操作,还有其余的 IO 操作,以及弹窗判断等工作。
那么,在此基础上还能够优化吗?答案是必定的。
3.3. 计划三:抽离数据加载线程
其实,认真思考之后可知:弹窗数据申请的工作不用和弹窗判断在同一个串行队列。当实现本地弹窗数据读取之后,就能够启动弹窗判断线程。
至于网络申请,自身是不牢靠的。在弹窗的业务中,如果有本地数据,那么就用本地数据,不用等到网络的数据返回后再解决弹窗业务。
基于以上的思路,将数据加载线程抽离,读取到本地数据后,就进行业务的解决,详情参考示意图 3-3:
图 3-3 抽离数据加载线程示意图
流程看上去比之前的计划要简单,同时牵扯到三个线程,但只有捋分明它们之前的关系,以及在什么时候进行通信,就比拟好了解:
初始化 SDK 后,咱们启动了两个线程,别离是数据加载线程和弹窗判断线程,并且让弹窗判断线程处于期待状态,而让数据加载线程去加载本地的弹窗数据;
加载数据实现后,如果数据不为空,那么启动弹窗判断线程;
当 App 进入前台时,告诉数据加载线程,加载网络申请返回的数据。
这里有一个场景须要特地留神:弹窗数据正在加载中,同时产生了埋点数据。此时须要依据弹窗数据判断埋点数据是否应该弹窗,然而弹窗数据还没有加载胜利,应该怎么办呢?
此时,应该让埋点的数据先缓存在队列中。只有当弹窗数据加载实现后,才会执行缓存队列中的工作,这也是弹窗判断线程启动后就让它期待的起因。另外,在数据更新的过程中,因为更新的是同一份数据,所以也须要对这一步加锁。
在初始化 SDK 外部,等到弹窗数据加载胜利时,才会启动弹窗判断线程。示例代码如下:
readLocalPlanData(new Callback() {
@Override
public void onFailure(int code, String errorMessage) { }
@Override
public void onResponse(String response) {
// 弹窗判断线程启动
new TriggerThread().start();
}
});
其余的代码和计划二中的代码很相似,此处不再展现。
须要留神的是:如果应用了线程池,线程池也会缓存工作。如果有业务场景须要进行线程,那么就不能让线程池缓存工作,能够让线程阻塞执行:
public class SFPlanTriggerRunnable implements Runnable {
@Override
public void run() {Runnable downloadTask = mSFPlanTaskManager.getTriggerTask();
mPool.submit(downloadTask).get();}
}
这种计划将数据加载线程抽离,解决了网络申请阻塞弹窗判断的场景,它有两个长处:
使线程的职责更加清晰;
网络申请弹窗数据,不再阻塞弹窗线程。
当然,也有毛病:
代码变得比较复杂;
须要保障并发场景下数据的安全性。
不过,基于目前的计划,既能升高提早,也能保障业务上的需要,目前看来是一次胜利的革新。
4. 总结
本文讲述了为了保障弹窗的实时性,弹窗 SDK 线程相干计划的演进过程。
弹窗 SDK 的实时性,通过一直致力终于获得了肯定的成绩。但在理论应用过程中,咱们依然碰到了不少难点:前后台判断的准确性、弹窗被遮蔽等问题。不过,咱们有信念在不久的将来肯定会冲破这些难点。
很多场景,只有咱们朝着指标一直前行,一直优化本人的计划与代码,总能达成指标。
认真斟酌,敢于尝试,犹如心有猛虎,细嗅蔷薇。
5. 参考文献
[1]https://github.com/sensorsdat…
文章来自公众号——神策技术社区