Author of this article: dl
1. Background
In the current mobile Internet era, if a product wants to quickly and accurately seize the market, it is undoubtedly necessary to iteratively update the product quickly. How to assist the product manager to make the best judgment on the current data of the product is the key, which requires the client side to provide high-precision , stable , full-link buried point data; students who do client development know deeply that if you want to meet the above three points in the development process, the development process is a big one;
In response to this problem, we have developed a set of full-link buried point solutions, from buried point design, to client three-terminal ( iOS , Android , H5 ) development, as well as buried point verification & inspection, and then to buried point data usage , which has been widely used in all major apps of cloud music.
Second, let’s talk about the disadvantages of the traditional burying scheme
The traditional burial point is that the BI data personnel design a single-point burial point according to the data they want to plan, and then the client personnel bury them one by one. These burial points often have the following characteristics:
- The event burying point of the pit is very simple: click/double-click/swipe and other clear event-type burying points are very simple, and you can bury them one by one according to your needs.
- Resource position exposure and burying is a nightmare: In the exposure and burying of listed/non-listed resources, it is very difficult to achieve high precision (the burying accuracy is 99.99% ). You may need to consider the following for each exposure and burying point. Most scenarios:
- Each pit is independent: the buried points between pits have no relationship, and each pit needs to be named (for example, identified by a random string or a combination of parameters), and among pages, lists, and elements, there are A large number of replicate parameters to meet data analysis requirements
- Difficulty in funnel/attribution analysis: Since each pit location is independent, the buried points generated successively during the use of the APP are unrelated. If you want to do funnel/attribution analysis, you need to pass the devil parameter on the client side. , and then do parameter correlation analysis scene by scene during data analysis
- Pit black box: Want to know how many pits are buried in an app, how many pits have been displayed under the current page, what is the relationship between the pits, and the management cost is high
3. Some of the attempts we have made
3.1 Traceless burial
There are many people on the market who have introduced traceless burying points , and we have made similar attempts; this kind of traceless, mainly for some pit events (such as click, double-click, sliding, etc.) burying points to automatically generate burying points, At the same time, the generated xpath (generated according to the view level) is attached, and after the buried point is reported to the data platform, the xpath is given real business meaning, so that data analysis can be performed;
However, the problem with this solution is that it can only handle some simple event scenarios, and it is a nightmare to do xpath association on the data platform, the workload is large, and the most important thing is instability . For high-precision buried point data scenarios, this solution is not feasible (no Which client developer spends a lot of time looking up xpath every day, what is the meaning, and with the development of iterative business, the workload of xpath due to data problems caused by uncontrolled changes is huge).
Especially for the exposure of resource bits, it is not feasible to achieve true tracelessness and automatic burying of points; for example, in the list scene, the bottom layer does not know what resource a cell is, and even does not know whether it is a cell or not. resource.
4. Our plan
4.1 Objects
The object is the basic unit of management and development of our solution. Set _oid (Object Id: Object Id) for a UIView , and the view is an object; objects are divided into two categories, page & element ;
- page object: such as UIViewController.view, WebView, or a half-screen floating layer view, or a business pop-up window
- element object: such as UIButton, UICollectionViewCell, or a custom view
- Object parameters: The object is the carrier of the specific information of the buried point, carrying the specific buried point parameters of the object dimension
- Reuse of objects: One of the big reasons for the existence of objects is that they need to be reused, which is especially suitable for some general UI components
4.2 Virtual Tree (VTree)
Objects do not exist in isolation, but are grouped together in a virtual tree (VTree) . Here is an example:
The virtual tree VTree has the following characteristics:
- View tree subset: The original view tree level is very complex. Those identified as objects are called nodes. All nodes are combined into a VTree, which is a subset of the original view tree.
- Context: The objects in the virtual tree have a contextual relationship. All ancestor nodes of a node are the context of the object (node).
- Object parameters: With the upper and lower levels of nodes, objects of different dimensions only care about the parameters of their own dimensions. For example, the song cell in the playlist details page does not care about the playlist id at the page request level
- SPM: The oid of the node and all its ancestor nodes constitutes the SPM value (in fact, there is also the participation of the position parameter, which will be explained in detail later), and the SPM can uniquely locate the node
- Continuous generation: VTree is continuously constructed, every view changes, View addition/deletion/level change/displacement/size change/hidden/alpha, etc., will cause a new VTree to be rebuilt
Fifth, the generation of buried points
After the above scheme is introduced, you must have a lot of doubts. With objects, virtual trees, objects with parameters, where are the buried points?
5.1 Let's first look at the buried point format
In addition to some basic information such as event type (action) and buried time, a buried point must also have business buried point parameters, as well as the structure that can reflect the upper and lower levels of the object.
Let’s first look at the format of the next common buried point:
{
"_elist": [
{
"_oid": "【必选】元素的oid",
"_pos": "【可选】,业务方配置的位置信息",
"biz_param": "【按需】业务参数"
}
],
"_plist": [
{
"_oid": "【必选】page的oid",
"_pos": "【可选】,业务方配置的位置信息",
"_pgstep": "【必选】, 该page/子page曝光时的页面深度"
}
],
"_spm": "【必选】这里描述的是节点的“位置”信息,用来定位节点",
"_scm": "【必选】这里描述的是节点的“内容”信息,用来描述节点的内容",
"_sessid": "【必选】冷启动生成,会话id",
"_eventcode": "【必选】事件: _ec/_ev/_ed/_pv/_pd",
"_duration": "数字,毫秒单位"
}
- _eventcode: Type of embedded point, such as element click (_ec), element exposure start (_ev), element exposure end (_ed), page exposure start (_pv), page exposure end (_pd), etc.
- _elist: The collection of all element nodes starting from the current element node and going up is an array, flashback
- _plist: Starting from the current node and going up to all page nodes, it is an array, flashback
- _spm: As described above (SPM), the pit can be uniquely located
It can be seen from the above data structure that the data structure is structured, the pits are not independent, and there is a hierarchical relationship.
5.2 Click event
Most click events occur in the following four scenarios:
- TapGesture click gesture added on UIView
- TouchUpInside click event added by subclass of UIControl
- UITableViewCell's didSelectedRowAtIndexPath click event
- UICollectionViewCell's didSelectedItemAtIndexPath click event
For the above four scenarios, we use the AOP method to undertake it internally. Here is a brief description of how to do it;
UIView: Hook the key methods through Method Swizzling. When we need to add TapGesture to the view, add our own TapGesture by the way, so that we can increase the click buried point when the click event is triggered. The key method is as follows:
- initWithTarget:action:
- addTarget:action:
- removeTarget:action:
- Hook attention to UIView click events need to be added/deleted along with the addition/deletion of business-side events
- At the same time, we have achieved two dimensions of hook before all business side click events are triggered (pre) & after all business side click events are triggered (after).
The key code is as follows:
@interface UIViewEventTracingAOPTapGesHandler : NSObject
@property(nonatomic, assign) BOOL isPre;
- (void)view_action_gestureRecognizerEvent:(UITapGestureRecognizer *)gestureRecognizer;
@end
@implementation UIViewEventTracingAOPTapGesHandler
- (void)view_action_gestureRecognizerEvent:(UITapGestureRecognizer *)gestureRecognizer {
if (![gestureRecognizer isKindOfClass:[UITapGestureRecognizer class]]
|| gestureRecognizer.ne_et_validTargetActions.count == 0) {
return;
}
UIView *view = gestureRecognizer.view;
// for: pre
if (self.isPre) {
/// MARK: 这里是 Pre 代码位置
return;
}
// for: after
/// MARK: 这里是 After 代码位置
}
@interface UITapGestureRecognizer (AOP)
@property(nonatomic, strong, setter=ne_et_setPreGesHandler:) UIViewEventTracingAOPTapGesHandler *ne_et_preGesHandler; /// MARK: Add Category Property
@property(nonatomic, strong, setter=ne_et_setAfterGesHandler:) UIViewEventTracingAOPTapGesHandler *ne_et_afterGesHandler; /// MARK: Add Category Property
@property(nonatomic, strong, readonly) NSMapTable<id, NSMutableSet<NSString *> *> *ne_et_validTargetActions; /// MARK: Add Category Property
@end
@implementation UITapGestureRecognizer (AOP)
- (instancetype)ne_et_tap_initWithTarget:(id)target action:(SEL)action {
if ([self _ne_et_needsAOP]) {
[self _ne_et_initPreAndAfterGesHanderIfNeeded];
}
if (target && action) {
UITapGestureRecognizer *ges = [self init];
[self addTarget:target action:action];
return ges;
}
return [self ne_et_tap_initWithTarget:target action:action];
}
- (void)ne_et_tap_addTarget:(id)target action:(SEL)action {
if (!target || !action
|| ![self _ne_et_needsAOP]
|| [[self.ne_et_validTargetActions objectForKey:target] containsObject:NSStringFromSelector(action)]) {
[self ne_et_tap_addTarget:target action:action];
return;
}
SEL handlerAction = @selector(view_action_gestureRecognizerEvent:);
// 1. pre
[self _ne_et_initPreAndAfterGesHanderIfNeeded];
if (self.ne_et_validTargetActions.count == 0) { // 第一个 target+action 被添加的时候,才添加 pre
[self ne_et_tap_addTarget:self.ne_et_preGesHandler action:handlerAction];
}
[self ne_et_tap_removeTarget:self.ne_et_afterGesHandler action:handlerAction]; // 保障 after 是最后一个,所以先行尝试删除一次
// 2. original
[self ne_et_tap_addTarget:target action:action];
NSMutableSet *actions = [self.ne_et_validTargetActions objectForKey:target] ?: [NSMutableSet set];
[actions addObject:NSStringFromSelector(action)];
[self.ne_et_validTargetActions setObject:actions forKey:target];
// 3. after
[self ne_et_tap_addTarget:self.ne_et_afterGesHandler action:handlerAction];
}
- (void)ne_et_tap_removeTarget:(id)target action:(SEL)action {
[self ne_et_tap_removeTarget:target action:action];
NSMutableSet *actions = [self.ne_et_validTargetActions objectForKey:target];
[actions removeObject:NSStringFromSelector(action)];
if (actions.count == 0) {
[self.ne_et_validTargetActions removeObjectForKey:target];
}
if (self.ne_et_validTargetActions.count > 0) { // 删除当前 target+action 之后,还有其他的,则不需做任何处理,否则清理掉 pre+after
return;
}
SEL handlerAction = @selector(view_action_gestureRecognizerEvent:);
[self ne_et_tap_removeTarget:self.ne_et_preGesHandler action:handlerAction];
[self ne_et_tap_removeTarget:self.ne_et_afterGesHandler action:handlerAction];
}
- (BOOL)_ne_et_needsAOP {
return self.numberOfTapsRequired == 1 && self.numberOfTouchesRequired == 1;
}
- (void)_ne_et_initPreAndAfterGesHanderIfNeeded {
if (!self.ne_et_preGesHandler) {
UIViewEventTracingAOPTapGesHandler *preGesHandler = [[UIViewEventTracingAOPTapGesHandler alloc] init];
preGesHandler.isPre = YES;
self.ne_et_preGesHandler = preGesHandler;
}
if (!self.ne_et_afterGesHandler) {
self.ne_et_afterGesHandler = [[UIViewEventTracingAOPTapGesHandler alloc] init];
}
}
@end
- UIControl: Hook the key method by Method Swizzling, the key method: sendAction:to:forEvent:
For the hook of the UIcontrol click event, it is necessary to pay attention to adding multiple Target-Action events on the business side, and it cannot be buried multiple times. Similarly, the two dimensions of pre & after are also supported.
The key code is as follows:
@interface UIControl (AOP)
@property(nonatomic, copy, readonly) NSMutableArray *ne_et_lastClickActions; /// MARK: Add Category Property
@end
@implementation UIControl (AOP)
- (void)ne_et_Control_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event {
NSString *selStr = NSStringFromSelector(action);
NSMutableArray<NSString *> *actions = @[].mutableCopy;
[self.allTargets enumerateObjectsUsingBlock:^(id _Nonnull obj, BOOL * _Nonnull stop) {
NSArray<NSString *> *actionsForTarget = [self actionsForTarget:obj forControlEvent:UIControlEventTouchUpInside];
if (actionsForTarget.count) {
[actions addObjectsFromArray:actionsForTarget];
}
}];
BOOL valid = [actions containsObject:selStr];
if (!valid) {
[self ne_et_Control_sendAction:action to:target forEvent:event];
return;
}
// pre
if ([self.ne_et_lastClickActions count] == 0) {
/// MAKR: 这里是 Pre 代码位置
}
[self.ne_et_lastClickActions addObject:[NSString stringWithFormat:@"%@-%@", [target class], NSStringFromSelector(action)]];
// original
[self ne_et_Control_sendAction:action to:target forEvent:event];
// after
if (self.ne_et_lastClickActions.count == actions.count) {
/// MARK: 这里是 After 代码位置
[self.ne_et_lastClickActions removeAllObjects];
}
}
@end
- UITableViewCell: Hook the setDelegate: first, then encapsulate the Original Delegate in the form of NSProxy to form a Delegate Chain, and then distribute messages inside the DelegateProxy, so that you can fully control the click event
- The Delegate Chain method can be used to hook the click event that does not support it, and it can also hook all Delegate methods.
- Similarly, pre & after two-dimensional hooks are also supported
- Special attention: It is necessary to achieve a real DelegateChain, otherwise it will conflict with many third-party libraries, such as RXSwift, RAC, BlocksKit, IGListKit, etc.
Several important related methods of key sample code (more code will not be displayed, and there are multiple libraries for the three parties to learn from):
- (id)forwardingTargetForSelector:(SEL)selector;
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector;
- (void)forwardInvocation:(NSInvocation *)invocation;
- (BOOL)respondsToSelector:(SEL)selector;
- (BOOL)conformsToProtocol:(Protocol *)aProtocol;
5.3 Exposure Buried Points
Exposure and buried points are the most difficult in traditional buried point scenarios. It is difficult to achieve high-precision buried points. The timing of buried points is always exhausted. Even with perfect specifications, developers will always miss scenes.
Our solution here allows developers to completely ignore the timing of exposure and buried points. Developers only focus on building objects (or VTrees) and adding parameters to objects. Let’s see how to expose based on VTrees:
- Continue to build VTree: As mentioned above, VTree is continuously built, every view changes, View addition/deletion/level change/displacement/size change/hidden/alpha, etc. ), will cause a new VTree to be rebuilt
- VTree Diff: The diff of two VTrees one after another is the result of our exposure and buried points
Over time, new VTrees will be continuously generated:
For example, the VTree generated at T1 time:
VTree generated at time T2:
The diff of two VTrees successively:
- T1 has nodes that T2 does not: 3, 4, 6, 7, 8, 11
- T1 does not exist nodes where T2 exists: 20, 21, 22, 23
The above diff result is the conclusion of exposure buried point
- Exposure end: 3, 4, 6, 7, 8, 11
- Exposure starts: 20, 21, 22, 23
From the above and the exposure strategy of VTree Diff, it is concluded as follows:
- This strategy completely wipes out lists and non-lists
- The issue of exposure timing turns into the issue of when to build a VTree
- The question of whether resources are exposed has turned into the visibility of nodes in VTree
5.4 Embedding development steps
Based on the embedding of VTree, whether it is the embedding of events such as clicks and slides, or the exposure of elements and pages, it is transformed into the following two development steps:
- Set oid to View => become an object (build VTree)
- Set Buried Point Parameters for Objects
6. Construction of VTree
6.1 VTree construction process
To construct a VTree, it is necessary to traverse the original view tree. The construction process has the following characteristics:
- Whether a node is visible is related to the hidden, alpha of the view, and must be added to the window
- The visible area of the child node is less than or equal to the visible area of the parent node
- The visible area of the node can be customized to expand or shrink , just like UIButton's contentEdgeInsets
- Nodes can be occluded: a page node can occlude other nodes whose parent node name is added earlier than itself
From the virtual tree, the occluded result:
Can break the original view hierarchy: You can manually intervene in the upper and lower hierarchy relationships to achieve the ability to logically mount
In fact, three logical mounting capabilities are currently provided, which are briefly mentioned here and will not be expanded in detail.
- Manual logical mount: Specify to mount A under the name of B
- Automatic logical mount: mount A to the name of the current rootPage (the page node at the bottom and rightmost of the current VTree)
- spm form logical mount: specify to mount A under the name
spm
(especially useful for decoupling)
- Virtual parent node: You can virtualize a parent node for multiple nodes, which is very useful when the UI difference between the two ends is required, but the same set of buried point structure is required.
A common example, take the cloud music homepage list as an example, the title and resource container of each module (the interior can be slid horizontally) are respectively a cell; the light red (module) in the figure does not actually have a UIView corresponding to it, the business The side buried point requires us to provide exposure data of the module dimension (but during the Android development process, there is usually a UI corresponding to it)
Fine burial:
- The combination of custom visible area & occlusion & recursive visibility of nodes can achieve refined buried point effect
- For the issue of whether the list cell is exposed or not caused by scenarios such as tabbar, navbar, or the mini playback bar at the bottom of the cloud music app, it can achieve refined control
- And with the occlusion ability, it truly achieves the effect that the node sees and exposes, and the exposure ends when invisible.
6.2 Performance Considerations for the Build Process
Any change in the view will cause the construction of the VTree, which seems to be a terrible thing, because every time the VTree is constructed, the entire original view tree needs to be traversed. We have made the following optimizations to ensure performance:
- Build a VTree when the main thread runloop is idle (and it needs the time the runloop has been running, less than or equal to 16.7ms/3, this is an example of a fixed frame rate of 60 frames)
- runloop build restrictor
The key code is as follows:
/// MARK: 添加最小时长限流器
_throtte = [[NEEventTracingTraversalRunnerDurationThrottle alloc] init];
/// 至少间隔 0.1s 才做一次
_throtte.tolerentDuration = 0.1f;
_throtte.callback = self;
/// MAKR: runloop observer
CFRunLoopObserverContext context = {0, (__bridge void *) self, NULL, NULL, NULL};
const CFIndex CFIndexMax = LONG_MAX;
_runloopObserver = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, CFIndexMax, &ETRunloopObserverCallback, &context);
/// MAKR: Observer Func
void ETRunloopObserverCallback(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {
NEEventTracingTraversalRunner *runner = (__bridge NEEventTracingTraversalRunner *)info;
switch (activity) {
case kCFRunLoopEntry:
[runner _runloopDidEntry];
break;
case kCFRunLoopBeforeWaiting:
[runner.throtte pushValue:nil];
break;
case kCFRunLoopAfterWaiting:
[runner _runloopDidEntry];
break;
default:
break;
}
}
- (void)_runloopDidEntry {
_currentLoopEntryTime = CACurrentMediaTime() * 1000.f;
}
- (void)_needRunTask {
CFTimeInterval now = CACurrentMediaTime() * 1000.f;
// 如果本次主线程的runloop已经使用了了超过 16.7/2.f 毫秒,则本次runloop不再遍历,放在下个runloop的beforWaiting中
// 按照目前手机一秒60帧的场景,一帧需要1/60也就是16.7ms的时间来执行代码,主线程不能被卡住超过16.7ms
// 特别是针对 iOS 15 之后,iPhone 13 Pro Max 帧率可以设置到 120hz
static CFTimeInterval frameMaxAvaibleTime = 0.f;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSInteger maximumFramesPerSecond = 60;
if (@available(iOS 10.3, *)) {
maximumFramesPerSecond = [UIScreen mainScreen].maximumFramesPerSecond;
}
frameMaxAvaibleTime = 1.f / maximumFramesPerSecond * 1000.f / 3.f;
});
if (now - _currentLoopEntryTime > frameMaxAvaibleTime) {
return;
}
BOOL runModeMatched = [[NSRunLoop mainRunLoop].currentMode isEqualToString:(NSString *) self.currentRunMode];
/// MARK: 这里回调,开始构建 VTree
}
- Local virtual tree VTree in list sliding
- Partial construction of VTree can greatly reduce the workload of constructing a VTree once
- On the premise of partial construction, the views that have changed since the last time the virtual tree was built are ScrollView or sub-views of ScrollView
- List swipe in restrictor
6.3 Performance related data
- Appropriate exposure delay to meet data requirements, such as a delay of 1 or 2 frames (depending on the performance of the phone and the current CPU workload)
- The function of the runloop's minimum duration current limiter also ensures that the delay will not be too large. The currently used 0.1s
- Using the iPhone 12 mobile phone, take the complex scene of the cloud music homepage as an example, keep sliding up and down, the full/partial construction of the VTree takes about 3-8ms/1-2ms respectively, and the CPU occupies about 2-3% (the original list of cloud music). The exposure component takes up about 10% of the CPU)
- The existence of the SDK will not cause the main thread to freeze or the phone to become hot.
7. Link Tracking
This is the most important function of the SDK. The goal is to link all the buried points generated by the app to assist the data side to unify a set of models to analyze the funnel/attribution data
7.1 The meaning of link tracking refer
refer is a formatted string through which one can uniquely locate a buried point in the entire data warehouse, which is link tracking
7.2 How to define a buried point
- _sessid: Generated every time the app is cold started, format:
[timestap]#[rand]#[appver]#[buildver]
- _pgstep: Within the startup range of the app, each page is exposed,
_pgstep
+1 - _actseq: The
rootPage
exposure period, every交互
event (_pv is also counted as an event),_actseq
+1
Through the above three parameters, you can locate the 交互
event within a certain app startup & page exposure cycle
7.3 Let's first see how to recognize a buried pit
- _spm: The pit information of the buried point, this string describes what the pit is
_scm: The content information of the buried pit, this string describes what the content of the resource is
- Format:
[cid:ctype:ctraceid:ctrp]
- cid: content id, the unique id of the resource
- ctype: content type, the type of the resource
- ctraceid: content traceid, which is generated when the interface reaches the gateway. The server/algorithm/recommendation uses this string for data logic, which is associated with subsequent tracking points to jointly analyze the effect of the recommendation/algorithm
- ctrp: The extension field for transparent transmission, which is used to transparently transmit the server/algorithm/recommended custom parameters in the resource dimension
- Format:
7.3 Refer format parsing
Format: [_dkey:${keys}][F:${option}][sessid][e/p/xxx][_actseq][_pgstep][spm][scm]
- option: is a
位
operation value to describe what the refer string contains - _dkey: It is the string form of option, which is highly readable (currently only available during development, which is convenient for manual identification)
- undefined-xpath: It is used to identify that the content pointed to by the refer is
降级
. As the buried point coverage becomes more and more complete, there will be fewer and fewer references with this logo.
7.4 Use of refer
Let's start with a typical usage scenario
Process interpretation:
- Clicking on the song cell triggers the update of the song playlist. The playback attribution of these songs (
_addrefer
) is attributed to the click buried point of the cell - At the same time, it jumps to the song playing page, and the attribution of the song playing (
_pgrefer
) is also attributed to the click of the cell
Lookup for refer:
- Automatic forward lookup: This is the strategy most used, automatically forward to find a suitable refer in the refer queue
- undefine-xpath downgrade: If the found reference is generated earlier than the last AOP capture
点击事件
time, it indicates that there is no buried point in this location, indicating that the referer is not credible, and it is downgraded to the lastrootPage曝光
on the corresponding refer - Accurate referrer search: There are also precise referrer search mechanisms for multiple strategies, but it is inconvenient to use and has not been widely used
7.5 Unified parsing of refer
According to the format of the above referrer, the data warehouse side sorts out the format of the referrer for unified analysis, and cooperates with the tracking management platform to make standardized funnel/attribution analysis possible.
7.6 Other refer usage scenarios
- multirefers: In real-time analysis scenarios, for some key buried points, a five-level (or even more) refer array is attached to directly describe what the first five steps of the operation have done (real-time analysis requirements are high, and offline data association cannot be done) )
- _hsrefer: One-click attribution, which can be attributed to which scene at the app level the consumption operation originated from, such as the home page, search page, my page, etc.
- _rqrefer: Bridge the client-side buried point with the server-side buried point
7.7 Referrals are transparent to developers
- The complexity of refer: the complexity of refer is very high, and the real refer processing is much more complicated than the above description. For ordinary client developers, if they want to fully understand, the cost is too high
Transparency during development: For developers, it is enough to add corresponding parameters to the corresponding nodes
Three standard private parameters of the object dimension (composed of _scm): cid, ctype, ctraceid, ctrp
- Platform verification: Whether the event of the object is involved in link tracking, parameter integrity, etc., can be verified on the platform to further ensure the correctness of the referrer
Eight, H5, RN
- RN: A layer of bridging is done, you can set nodes for the view in the RN dimension, and set parameters at the same time
- On-site H5: A semi-white box solution is adopted, with a local virtual tree inside the H5. All buried points are generated through the client SDK. After the H5 buried points reach the SDK, virtual tree fusion is performed on the native side, thereby seamlessly connecting the on-site H5 with the native. up
9. Visualization tools
The traditional buried points on the client are invisible and intangible. The VTree-based solution is structured and can visualize the data of buried points and how to bury them. The following are screenshots of several tools
10. Buried point verification & inspection
- The buried point is structured, and the virtual tree is managed on the buried point platform. The verification of the buried point can be accurately verified to check whether the virtual tree of the embedded point of the client is correct.
- And whether the parameters of the buried points on each object are correct
Audit:
- In the test package and grayscale package, audit all the generated points on the platform side, and output the audit report. Before the version is released, timely repair the problematic embedding points to avoid data problems caused by the launch.
Eleven, landing
This full-link burying solution has been fully deployed in each cloud music app, and the P0 scene has been cut on the data side, which has been fully verified.
12. Future Planning
There are many things that can be done based on VTree, such as:
- Automated testing: The key point is to identify the view, and at the same time, you can use the identifier to query the view (the VTree-based UI automation test has been implemented, and I will discuss it with you later)
- Page identification: unified page identification capability across terminals, used for scene identification of various dimensions
- Data visualization capability based on VTree: You can view the data trend of the entire app level on the mobile phone
- H5 visualization in the station: further reduce the workload of H5 scene embedding
- Automatic verification of refer ability and data audit: refer ability is very strong, but after troubleshooting problems, with the cooperation of related tools, the refer ability that was originally transparent to developers can also be easily checked
This article is published from the NetEase Cloud Music technical team, and any form of reprinting of the article is prohibited without authorization. We recruit various technical positions all year round. If you are ready to change jobs and happen to like cloud music, then join us at grp.music-fe(at)corp.netease.com!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。