From 9b5c2af7d145520991efefc9fa33a0d68af7be7b Mon Sep 17 00:00:00 2001 From: weiqiangliu Date: Sat, 31 Jul 2021 15:11:13 +0800 Subject: [PATCH] Release 2.1.3 --- RNSensorsAnalyticsModule.podspec | 2 +- .../analytics/RNSensorsAnalyticsPackage.java | 2 +- .../analytics/RNSensorsDataModule.java | 28 ++-- .../analytics/utils/RNViewUtils.java | 94 +++++++------ ios/RNSensorsAnalyticsModule.m | 2 +- .../project.pbxproj | 6 - ios/RNSensorsDataModule.m | 2 +- ios/SAReactNativeCategory.h | 24 +++- ios/SAReactNativeCategory.m | 116 +++------------- ios/SAReactNativeManager.h | 2 - ios/SAReactNativeManager.m | 125 +++++++----------- package.json | 2 +- 12 files changed, 165 insertions(+), 240 deletions(-) diff --git a/RNSensorsAnalyticsModule.podspec b/RNSensorsAnalyticsModule.podspec index 1f8bfdc..506e20d 100644 --- a/RNSensorsAnalyticsModule.podspec +++ b/RNSensorsAnalyticsModule.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = "RNSensorsAnalyticsModule" - s.version = "2.1.2" + s.version = "2.1.3" s.summary = "The official React Native SDK of Sensors Analytics." s.description = <<-DESC 神策分析 RN 组件 diff --git a/android/src/main/java/com/sensorsdata/analytics/RNSensorsAnalyticsPackage.java b/android/src/main/java/com/sensorsdata/analytics/RNSensorsAnalyticsPackage.java index dcea421..ca652d8 100644 --- a/android/src/main/java/com/sensorsdata/analytics/RNSensorsAnalyticsPackage.java +++ b/android/src/main/java/com/sensorsdata/analytics/RNSensorsAnalyticsPackage.java @@ -28,7 +28,7 @@ import java.util.List; public class RNSensorsAnalyticsPackage implements ReactPackage { - public static final String VERSION = "2.1.2"; + public static final String VERSION = "2.1.3"; @Override public List createNativeModules(ReactApplicationContext reactContext) { List modules = new ArrayList<>(); diff --git a/android/src/main/java/com/sensorsdata/analytics/RNSensorsDataModule.java b/android/src/main/java/com/sensorsdata/analytics/RNSensorsDataModule.java index cf3796c..46fcea4 100755 --- a/android/src/main/java/com/sensorsdata/analytics/RNSensorsDataModule.java +++ b/android/src/main/java/com/sensorsdata/analytics/RNSensorsDataModule.java @@ -20,17 +20,15 @@ import com.facebook.react.bridge.LifecycleEventListener; import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.bridge.ReactContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactMethod; import com.facebook.react.bridge.ReadableMap; -import com.sensorsdata.analytics.android.sdk.SensorsDataAPI; import com.sensorsdata.analytics.android.sdk.SALog; import com.sensorsdata.analytics.property.LibMethodInterceptor; +import com.sensorsdata.analytics.property.PluginVersionInterceptor; +import com.sensorsdata.analytics.property.RNPropertyManager; import com.sensorsdata.analytics.utils.RNUtils; import com.sensorsdata.analytics.utils.RNViewUtils; -import com.sensorsdata.analytics.property.RNPropertyManager; -import com.sensorsdata.analytics.property.PluginVersionInterceptor; import org.json.JSONObject; @@ -47,13 +45,13 @@ * ReadableArray -> Array */ -public class RNSensorsDataModule extends ReactContextBaseJavaModule{ +public class RNSensorsDataModule extends ReactContextBaseJavaModule { public RNSensorsDataModule(ReactApplicationContext reactContext) { super(reactContext); - try{ + try { reactContext.addLifecycleEventListener(new SensorsDataLifecycleListener()); - }catch(Exception e){ + } catch (Exception e) { } RNAgent.ignoreView(); @@ -79,23 +77,23 @@ public void trackViewClick(int viewId) { @ReactMethod public void trackViewScreen(ReadableMap params) { - try{ + try { if (params != null) { JSONObject jsonParams = RNUtils.convertToJSONObject(params); JSONObject properties = null; - if(jsonParams.has("sensorsdataparams")){ + if (jsonParams.has("sensorsdataparams")) { properties = jsonParams.optJSONObject("sensorsdataparams"); } String url = null; - if(jsonParams.has("sensorsdataurl")){ + if (jsonParams.has("sensorsdataurl")) { url = jsonParams.getString("sensorsdataurl"); } - if(url == null){ + if (url == null) { return; } RNAgent.trackViewScreen(url, properties, true); } - }catch(Exception e){ + } catch (Exception e) { SALog.printStackTrace(e); } } @@ -107,13 +105,11 @@ public void saveViewProperties(int viewId, boolean clickable, ReadableMap viewPr class SensorsDataLifecycleListener implements LifecycleEventListener { public void onHostResume() { - RNViewUtils.setScreenVisiable(true); - RNViewUtils.setCurrentActivity(getCurrentActivity()); + RNViewUtils.onActivityResumed(getCurrentActivity()); } public void onHostPause() { - RNViewUtils.setScreenVisiable(false); - RNViewUtils.clearCurrentActivityReference(); + RNViewUtils.onActivityPaused(); } public void onHostDestroy() { diff --git a/android/src/main/java/com/sensorsdata/analytics/utils/RNViewUtils.java b/android/src/main/java/com/sensorsdata/analytics/utils/RNViewUtils.java index df79ce0..24477a4 100644 --- a/android/src/main/java/com/sensorsdata/analytics/utils/RNViewUtils.java +++ b/android/src/main/java/com/sensorsdata/analytics/utils/RNViewUtils.java @@ -18,34 +18,24 @@ package com.sensorsdata.analytics.utils; import android.app.Activity; -import android.app.ActivityManager; -import android.content.Context; -import android.util.Log; import android.view.View; - -import com.facebook.react.uimanager.NativeViewHierarchyManager; -import com.facebook.react.uimanager.UIImplementation; -import com.facebook.react.uimanager.UIManagerModule; -import com.facebook.react.uimanager.UIViewOperationQueue; -import java.lang.ref.SoftReference; -import com.sensorsdata.analytics.android.sdk.SALog; - -import java.lang.ref.WeakReference; -import java.lang.reflect.Field; import android.view.ViewGroup; -import android.view.View; import android.view.ViewParent; + import org.json.JSONObject; +import java.lang.ref.WeakReference; +import java.util.WeakHashMap; + public class RNViewUtils { - private static SoftReference mSoftCurrentActivityReference; + private static WeakReference mWeakCurrentActivityReference; private static String currentTitle; private static String currentScreenName; public static boolean isScreenVisiable = false; - private static JSONObject properties = new JSONObject(); + private static JSONObject screenProperties; private static WeakReference onTouchViewReference; - + private static WeakHashMap mScreenMap = new WeakHashMap<>(); public static void setOnTouchView(View nativeTargetView) { onTouchViewReference = new WeakReference(nativeTargetView); @@ -53,15 +43,15 @@ public static void setOnTouchView(View nativeTargetView) { public static View getViewByTag(int viewTag) { View clickView = null; - try{ + try { Activity currentActivity = getCurrentActivity(); - if(currentActivity != null){ + if (currentActivity != null) { clickView = currentActivity.findViewById(viewTag); } - if(clickView == null){ + if (clickView == null) { clickView = getTouchViewByTag(viewTag); } - }catch (Exception ignored){ + } catch (Exception ignored) { } return clickView; @@ -113,56 +103,80 @@ private static View getClickViewInChild(int viewId, ViewGroup currentView) { return null; } - public static void saveScreenAndTitle(String screenName,String title){ + public static void saveScreenAndTitle(String screenName, String title) { currentScreenName = screenName; currentTitle = title; - try{ - properties.put("$title", title); - properties.put("$screen_name", screenName); - }catch (Exception ignored){ + try { + screenProperties = new JSONObject(); + screenProperties.put("$title", title); + screenProperties.put("$screen_name", screenName); + } catch (Exception ignored) { } + Activity currentActivity; + if ((currentActivity = getCurrentActivity()) != null) { + mScreenMap.put(currentActivity, screenProperties); + } } - public static String getTitle(){ + public static String getTitle() { return currentTitle; } - public static String getScreenName(){ + public static String getScreenName() { return currentScreenName; } /** * 供可视化调用,返回 $title,$screen_name,勿删 + * * @return json 格式 */ - public static String getVisualizeProperties(){ - if(!isScreenVisiable){ + public static String getVisualizeProperties() { + //当前页面不可见或无 $screen_name $title 时,可视化获取原生页面信息 + if (!isScreenVisiable || screenProperties == null) { return ""; } - return properties.toString(); + return screenProperties.toString(); } - public static void setScreenVisiable(boolean isVisiable){ + public static void setScreenVisiable(boolean isVisiable) { isScreenVisiable = isVisiable; } - public static void setCurrentActivity(Activity currentActivity) { + private static void setCurrentActivity(Activity currentActivity) { clearCurrentActivityReference(); - mSoftCurrentActivityReference = new SoftReference(currentActivity); + mWeakCurrentActivityReference = new WeakReference(currentActivity); + JSONObject properties = mScreenMap.get(currentActivity); + if (properties != null && properties.has("$screen_name")) { + saveScreenAndTitle(properties.optString("$screen_name"), properties.optString("$title")); + } else { + currentScreenName = null; + currentTitle = null; + screenProperties = null; + } } - private static Activity getCurrentActivity(){ - if(mSoftCurrentActivityReference == null){ + private static Activity getCurrentActivity() { + if (mWeakCurrentActivityReference == null) { return null; } - return (Activity)mSoftCurrentActivityReference.get(); + return (Activity) mWeakCurrentActivityReference.get(); } public static void clearCurrentActivityReference() { - if(mSoftCurrentActivityReference != null){ - mSoftCurrentActivityReference.clear(); - mSoftCurrentActivityReference = null; + if (mWeakCurrentActivityReference != null) { + mWeakCurrentActivityReference.clear(); + mWeakCurrentActivityReference = null; } } + + public static void onActivityResumed(Activity currentActivity) { + setScreenVisiable(true); + setCurrentActivity(currentActivity); + } + + public static void onActivityPaused() { + setScreenVisiable(false); + } } diff --git a/ios/RNSensorsAnalyticsModule.m b/ios/RNSensorsAnalyticsModule.m index d361743..1f902b3 100644 --- a/ios/RNSensorsAnalyticsModule.m +++ b/ios/RNSensorsAnalyticsModule.m @@ -32,7 +32,7 @@ #import "SAReactNativeManager.h" #import "SAReactNativeEventProperty.h" -NSString *const kSAReactNativePluginVersion = @"react_native:2.1.2"; +NSString *const kSAReactNativePluginVersion = @"react_native:2.1.3"; @implementation RNSensorsAnalyticsModule diff --git a/ios/RNSensorsAnalyticsModule.xcodeproj/project.pbxproj b/ios/RNSensorsAnalyticsModule.xcodeproj/project.pbxproj index ede410e..b3ab54f 100644 --- a/ios/RNSensorsAnalyticsModule.xcodeproj/project.pbxproj +++ b/ios/RNSensorsAnalyticsModule.xcodeproj/project.pbxproj @@ -8,7 +8,6 @@ /* Begin PBXBuildFile section */ A18E9FB421A6AEDD00A66E41 /* RNSensorsAnalyticsModule.m in Sources */ = {isa = PBXBuildFile; fileRef = A18E9FB221A6AEDD00A66E41 /* RNSensorsAnalyticsModule.m */; }; - FC045CC2246D191C007117C1 /* SAReactNativeswizzler.m in Sources */ = {isa = PBXBuildFile; fileRef = FC045CC1246D191C007117C1 /* SAReactNativeswizzler.m */; }; FC39004C244715AE00F486A7 /* SAReactNativeManager.m in Sources */ = {isa = PBXBuildFile; fileRef = FC39004B244715AE00F486A7 /* SAReactNativeManager.m */; }; FC6EF7A9259DA6A40099EEB5 /* SAReactNativeEventProperty.m in Sources */ = {isa = PBXBuildFile; fileRef = FC6EF7A8259DA6A40099EEB5 /* SAReactNativeEventProperty.m */; }; FC9EE671243731C000C45D16 /* RNSensorsDataModule.m in Sources */ = {isa = PBXBuildFile; fileRef = FC9EE670243731C000C45D16 /* RNSensorsDataModule.m */; }; @@ -31,8 +30,6 @@ A18E9FA621A6AB4300A66E41 /* libRNSensorsAnalyticsModule.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRNSensorsAnalyticsModule.a; sourceTree = BUILT_PRODUCTS_DIR; }; A18E9FB221A6AEDD00A66E41 /* RNSensorsAnalyticsModule.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNSensorsAnalyticsModule.m; sourceTree = ""; }; A18E9FB321A6AEDD00A66E41 /* RNSensorsAnalyticsModule.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNSensorsAnalyticsModule.h; sourceTree = ""; }; - FC045CC0246D191C007117C1 /* SAReactNativeswizzler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SAReactNativeswizzler.h; sourceTree = ""; }; - FC045CC1246D191C007117C1 /* SAReactNativeswizzler.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SAReactNativeswizzler.m; sourceTree = ""; }; FC39004A244715AE00F486A7 /* SAReactNativeManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SAReactNativeManager.h; sourceTree = ""; }; FC39004B244715AE00F486A7 /* SAReactNativeManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SAReactNativeManager.m; sourceTree = ""; }; FC6EF7A7259DA6A40099EEB5 /* SAReactNativeEventProperty.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SAReactNativeEventProperty.h; sourceTree = ""; }; @@ -67,8 +64,6 @@ FC6EF7A8259DA6A40099EEB5 /* SAReactNativeEventProperty.m */, FCD3CF2925466B7800758260 /* SAReactNativeCategory.h */, FCD3CF2A25466B7800758260 /* SAReactNativeCategory.m */, - FC045CC0246D191C007117C1 /* SAReactNativeswizzler.h */, - FC045CC1246D191C007117C1 /* SAReactNativeswizzler.m */, A18E9FA721A6AB4300A66E41 /* Products */, ); sourceTree = ""; @@ -138,7 +133,6 @@ buildActionMask = 2147483647; files = ( A18E9FB421A6AEDD00A66E41 /* RNSensorsAnalyticsModule.m in Sources */, - FC045CC2246D191C007117C1 /* SAReactNativeswizzler.m in Sources */, FC39004C244715AE00F486A7 /* SAReactNativeManager.m in Sources */, FC6EF7A9259DA6A40099EEB5 /* SAReactNativeEventProperty.m in Sources */, FCD3CF2B25466B7800758260 /* SAReactNativeCategory.m in Sources */, diff --git a/ios/RNSensorsDataModule.m b/ios/RNSensorsDataModule.m index a77d28a..fc6aee8 100644 --- a/ios/RNSensorsDataModule.m +++ b/ios/RNSensorsDataModule.m @@ -57,7 +57,7 @@ @implementation RNSensorsDataModule * * @param reactTag 当前控件唯一标识符 * @param clickable 当前控件可点击状态 - * @param paramters 当前控件自定义参数 (预留字段,暂不支持) + * @param paramters 当前控件自定义参数 * */ RCT_EXPORT_METHOD(saveViewProperties:(NSInteger)reactTag clickable:(BOOL)clickable paramters:(NSDictionary *)paramters) { diff --git a/ios/SAReactNativeCategory.h b/ios/SAReactNativeCategory.h index 8f60c2b..b05f3d6 100644 --- a/ios/SAReactNativeCategory.h +++ b/ios/SAReactNativeCategory.h @@ -20,14 +20,36 @@ #import #import -#import "SAReactNativeManager.h" NS_ASSUME_NONNULL_BEGIN +#pragma mark - View Property +@interface SAReactNativeViewProperty : NSObject + +/// View 唯一标识符 +@property (nonatomic, strong) NSNumber *reactTag; +/// View 可点击状态 +@property (nonatomic, assign) BOOL clickable; +/// View 自定义属性 +@property (nonatomic, strong) NSDictionary *properties; + +@end + @interface UIView (SAReactNative) +/// 用于记录 view 关联的页面信息 +@property (nonatomic, copy) NSDictionary *sa_reactnative_screenProperties; + +@end + +@interface UIViewController (SAReactNative) + +/// 触发页面浏览时, 记录页面信息 (和 RCTRootView 关联: rctRootView.reactViewController) @property (nonatomic, copy) NSDictionary *sa_reactnative_screenProperties; +/// 用于记录 view 自定义属性 (和 RCTRootView 关联: rctRootView.reactViewController) +@property (nonatomic, copy) NSSet *sa_reactnative_viewProperties; + @end NS_ASSUME_NONNULL_END diff --git a/ios/SAReactNativeCategory.m b/ios/SAReactNativeCategory.m index 3faf93d..1e12f2a 100644 --- a/ios/SAReactNativeCategory.m +++ b/ios/SAReactNativeCategory.m @@ -23,123 +23,49 @@ #endif #import "SAReactNativeCategory.h" -#import "SAReactNativeswizzler.h" -#import "SAReactNativeManager.h" #import -#import + +static void *const kSensorsAnalyticsRNScreenPropertiesKey = (void *)&kSensorsAnalyticsRNScreenPropertiesKey; +static void *const kSensorsAnalyticsRNViewPropertiesKey = (void *)&kSensorsAnalyticsRNViewPropertiesKey; + +@implementation SAReactNativeViewProperty + +- (NSString *)description { + return [NSString stringWithFormat:@"%@; reactTag: %@; clickable: %@; properties: %@", [super description], self.reactTag, (self.clickable ? @"YES" : @"NO"), self.properties]; +} + +@end #pragma mark - UIView Category @implementation UIView (SAReactNative) - (NSDictionary *)sa_reactnative_screenProperties { - return objc_getAssociatedObject(self, @"SensorsAnalyticsRNScreenProperties"); + return objc_getAssociatedObject(self, kSensorsAnalyticsRNScreenPropertiesKey); } - (void)setSa_reactnative_screenProperties:(NSDictionary *)sa_reactnative_screenProperties { - objc_setAssociatedObject(self, @"SensorsAnalyticsRNScreenProperties", sa_reactnative_screenProperties, OBJC_ASSOCIATION_COPY_NONATOMIC); + objc_setAssociatedObject(self, kSensorsAnalyticsRNScreenPropertiesKey, sa_reactnative_screenProperties, OBJC_ASSOCIATION_COPY_NONATOMIC); } @end #pragma mark - UIViewController Category -@interface UIViewController (SAReactNative) - -@property (nonatomic, assign) BOOL sa_reactnative_isReferrerRootView; - -@end - @implementation UIViewController (SAReactNative) -+ (void)load { - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - [UIViewController sa_reactnative_swizzleMethod:@selector(viewDidAppear:) - withMethod:@selector(sa_reactnative_viewDidAppear:) - error:NULL]; - - [UIViewController sa_reactnative_swizzleMethod:@selector(viewDidDisappear:) - withMethod:@selector(sa_reactnative_viewDidDisappear:) - error:NULL]; - }); -} - -- (BOOL)sa_reactnative_isReferrerRootView { - NSNumber *result = objc_getAssociatedObject(self, @"sa_reactnative_isReferrerRootView"); - return result.boolValue; -} - -- (void)setSa_reactnative_isReferrerRootView:(BOOL)isRootView { - objc_setAssociatedObject(self, @"sa_reactnative_isReferrerRootView", @(isRootView), OBJC_ASSOCIATION_RETAIN_NONATOMIC); -} - -- (void)sa_reactnative_viewDidAppear:(BOOL)animated { - [self sa_reactnative_viewDidAppear:animated]; - - if ([self isKindOfClass:UIAlertController.class]) { - return; - } - // 当前 Controller 为 React Native 根视图时,设置标志位为 YES - if ([self isReactRootView:self.view]) { - [[SAReactNativeManager sharedInstance] setIsRootViewVisible:YES]; - return; - } - - //检查 referrer 是否为 React Native 根视图 - UIViewController *referrer = self.presentingViewController; - if (!referrer) { - return; - } - - // 当前 Controller 不为 React Native 根视图时, isRootViewVisible 肯定为 NO - [[SAReactNativeManager sharedInstance] setIsRootViewVisible:NO]; - - if ([referrer isKindOfClass:UITabBarController.class]) { - UIViewController *controller = [(UITabBarController *)referrer selectedViewController]; - [self checkReferrerController:controller]; - } else { - [self checkReferrerController:referrer]; - } +- (NSDictionary *)sa_reactnative_screenProperties { + return objc_getAssociatedObject(self, kSensorsAnalyticsRNScreenPropertiesKey); } -- (void)checkReferrerController:(UIViewController *)controler { - if ([controler isKindOfClass:UINavigationController.class]) { - UIViewController *vc = [(UINavigationController *)controler viewControllers].lastObject; - if ([vc.view isReactRootView]) { - self.sa_reactnative_isReferrerRootView = YES; - } - } else if ([controler isKindOfClass:UIViewController.class]) { - if ([self isReactRootView:controler.view]) { - self.sa_reactnative_isReferrerRootView = YES; - } - } +- (void)setSa_reactnative_screenProperties:(NSDictionary *)sa_reactnative_screenProperties { + objc_setAssociatedObject(self, kSensorsAnalyticsRNScreenPropertiesKey, sa_reactnative_screenProperties, OBJC_ASSOCIATION_COPY_NONATOMIC); } -- (void)sa_reactnative_viewDidDisappear:(BOOL)animated { - [self sa_reactnative_viewDidDisappear:animated]; - - // 当前 Controller 为 React Native 根视图时,消失时将标志位设置为 NO - if ([self isReactRootView:self.view]) { - [[SAReactNativeManager sharedInstance] setIsRootViewVisible:NO]; - return; - } - - // 当前 Controller 的 referrer 为 React Native 根视图时,消失时将标志位设置为 YES - if (self.sa_reactnative_isReferrerRootView) { - [[SAReactNativeManager sharedInstance] setIsRootViewVisible:YES]; - return; - } +- (NSSet *)sa_reactnative_viewProperties { + return objc_getAssociatedObject(self, kSensorsAnalyticsRNViewPropertiesKey); } -- (BOOL)isReactRootView:(UIView *)view { - if ([view isReactRootView]) { - return YES; - } - for (UIView *subview in view.subviews) { - if ([subview isReactRootView]) { - return YES; - } - } - return NO; +- (void)setSa_reactnative_viewProperties:(NSSet *)sa_reactnative_viewProperties { + objc_setAssociatedObject(self, kSensorsAnalyticsRNViewPropertiesKey, sa_reactnative_viewProperties, OBJC_ASSOCIATION_COPY_NONATOMIC); } @end diff --git a/ios/SAReactNativeManager.h b/ios/SAReactNativeManager.h index 7a02e72..3a0fb63 100644 --- a/ios/SAReactNativeManager.h +++ b/ios/SAReactNativeManager.h @@ -25,8 +25,6 @@ NS_ASSUME_NONNULL_BEGIN @interface SAReactNativeManager : NSObject -@property (nonatomic, assign) BOOL isRootViewVisible; - + (instancetype)sharedInstance; /** diff --git a/ios/SAReactNativeManager.m b/ios/SAReactNativeManager.m index 935458b..7e514f4 100644 --- a/ios/SAReactNativeManager.m +++ b/ios/SAReactNativeManager.m @@ -38,32 +38,10 @@ NSString *const kSAEventTitleProperty = @"$title"; NSString *const kSAEventElementContentProperty = @"$element_content"; -#pragma mark - View Property -@interface SAReactNativeViewProperty : NSObject - -/// View 唯一标识符 -@property (nonatomic, strong) NSNumber *reactTag; -/// View 可点击状态 -@property (nonatomic, assign) BOOL clickable; -/// View 自定义属性 -@property (nonatomic, strong) NSDictionary *properties; - -@end - -@implementation SAReactNativeViewProperty - -- (NSString *)description { - return [NSString stringWithFormat:@"%@; reactTag: %@; clickable: %@; properties: %@", [super description], self.reactTag, @(self.clickable), self.properties]; -} - -@end - #pragma mark - React Native Manager @interface SAReactNativeManager () -@property (nonatomic, copy) NSDictionary *screenProperties; @property (nonatomic, strong) NSSet *reactNativeIgnoreClasses; -@property (nonatomic, strong) NSMutableSet *viewProperties; @end @@ -89,27 +67,17 @@ - (instancetype)init { } } _reactNativeIgnoreClasses = [NSSet setWithObjects:@"RCTScrollView", @"RCTBaseTextInputView", nil]; - _viewProperties = [[NSMutableSet alloc] init]; - _isRootViewVisible = NO; } return self; } -- (UIView *)viewWithReactTag:(NSNumber *)reactTag { - RCTRootView *rootView = [self rootView]; - RCTUIManager *manager = rootView.bridge.uiManager; - return [manager viewForReactTag:reactTag]; -} - -- (SAReactNativeViewProperty *)viewPropertyWithReactTag:(NSNumber *)reactTag { - __block SAReactNativeViewProperty *viewProperty; - [_viewProperties enumerateObjectsUsingBlock:^(SAReactNativeViewProperty *obj, BOOL * _Nonnull stop) { - if (obj.reactTag.integerValue == reactTag.integerValue) { - viewProperty = obj; - *stop = YES; +- (SAReactNativeViewProperty *)viewPropertyWithReactTag:(NSNumber *)reactTag fromViewProperties:(NSSet *)properties { + for (SAReactNativeViewProperty *property in properties) { + if (property.reactTag.integerValue == reactTag.integerValue) { + return property; } - }]; - return viewProperty; + } + return nil; } - (BOOL)clickableForView:(UIView *)view { @@ -121,27 +89,24 @@ - (BOOL)clickableForView:(UIView *)view { return NO; } } - SAReactNativeViewProperty *viewProperties = [self viewPropertyWithReactTag:view.reactTag]; + + // 通过 RCTRootView 获取 viewProperty + UIViewController *reactViewController = [self rootView].reactViewController; + NSSet *viewProperties = reactViewController.sa_reactnative_viewProperties; + NSDictionary *screenProperties = reactViewController.sa_reactnative_screenProperties; + view.sa_reactnative_screenProperties = screenProperties; // 兼容 Native 可视化全埋点 UISegmentedControl 整体不可圈选的场景 if ([view isKindOfClass:NSClassFromString(@"UISegmentedControl")]) { - view.sa_reactnative_screenProperties = _screenProperties; return NO; } // UISegmentedControl 只有子视图 UISegment 是可点击的 if ([view isKindOfClass:NSClassFromString(@"UISegment")]) { - SAReactNativeViewProperty *superviewProperties = [self viewPropertyWithReactTag:view.superview.reactTag]; - view.sa_reactnative_screenProperties = _screenProperties; - return superviewProperties.clickable; + return [self viewPropertyWithReactTag:view.superview.reactTag fromViewProperties:viewProperties].clickable; } - if (viewProperties.clickable) { - // 可点击控件需要将当前页面信息保存在 sa_reactnative_screenProperties 中,在可视化全埋点时使用 - view.sa_reactnative_screenProperties = _screenProperties; - return YES; - } - return NO; + return [self viewPropertyWithReactTag:view.reactTag fromViewProperties:viewProperties].clickable; } - (BOOL)prepareView:(NSNumber *)reactTag clickable:(BOOL)clickable paramters:(NSDictionary *)paramters { @@ -156,13 +121,19 @@ - (BOOL)prepareView:(NSNumber *)reactTag clickable:(BOOL)clickable paramters:(NS viewProperty.reactTag = reactTag; viewProperty.clickable = clickable; viewProperty.properties = paramters; - [_viewProperties addObject:viewProperty]; + dispatch_async(dispatch_get_main_queue(), ^{ + RCTRootView *rootView = [self rootView]; + NSMutableSet *viewProperties = [NSMutableSet setWithSet:rootView.reactViewController.sa_reactnative_viewProperties]; + [viewProperties addObject:viewProperty]; + rootView.reactViewController.sa_reactnative_viewProperties = [viewProperties copy]; + }); return YES; } #pragma mark - visualize - (NSDictionary *)visualizeProperties { - return _isRootViewVisible ? _screenProperties : nil; + UIView *rootView = [self rootView]; + return rootView.window ? rootView.reactViewController.sa_reactnative_screenProperties : nil; } #pragma mark - AppClick @@ -175,13 +146,18 @@ - (void)trackViewClick:(NSNumber *)reactTag { return; } - SAReactNativeViewProperty *viewProperties = [self viewPropertyWithReactTag:reactTag]; - if ([viewProperties.properties[@"ignore"] boolValue]) { - return; - } - dispatch_async(dispatch_get_main_queue(), ^{ - UIView *view = [self viewWithReactTag:reactTag]; + // 通过 RCTRootView 获取 viewProperty + RCTRootView *rootView = [self rootView]; + NSSet *viewProperties = rootView.reactViewController.sa_reactnative_viewProperties; + NSDictionary *screenProperties = rootView.reactViewController.sa_reactnative_screenProperties; + SAReactNativeViewProperty *viewProperty = [self viewPropertyWithReactTag:reactTag fromViewProperties:viewProperties]; + id ignoreParam = viewProperty.properties[@"ignore"]; + if ([ignoreParam respondsToSelector:@selector(boolValue)] && [ignoreParam boolValue]) { + return; + } + + UIView *view = [rootView.bridge.uiManager viewForReactTag:reactTag]; for (NSString *className in self.reactNativeIgnoreClasses) { if ([view isKindOfClass:NSClassFromString(className)]) { return; @@ -190,8 +166,8 @@ - (void)trackViewClick:(NSNumber *)reactTag { NSMutableDictionary *properties = [NSMutableDictionary dictionary]; NSString *content = [view.accessibilityLabel stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; properties[kSAEventElementContentProperty] = content; - [properties addEntriesFromDictionary:self.screenProperties]; - [properties addEntriesFromDictionary:viewProperties.properties]; + [properties addEntriesFromDictionary:screenProperties]; + [properties addEntriesFromDictionary:viewProperty.properties]; NSDictionary *newProps = [SAReactNativeEventProperty eventProperties:properties isAuto:YES]; [[SensorsAnalyticsSDK sharedInstance] trackViewAppClick:view withProperties:newProps]; }); @@ -210,7 +186,7 @@ - (void)trackViewScreen:(nullable NSString *)url properties:(nullable NSDictiona pageProps[kSAEventScreenNameProperty] = screenName; pageProps[kSAEventTitleProperty] = title; dispatch_async(dispatch_get_main_queue(), ^{ - self.screenProperties = pageProps; + [self rootView].reactViewController.sa_reactnative_screenProperties = [pageProps copy]; }); // 忽略 React Native 触发的 $AppViewScreen 事件 @@ -243,27 +219,26 @@ - (void)trackViewScreen:(nullable NSString *)url properties:(nullable NSDictiona #pragma mark - Find RCTRootView - (RCTRootView *)rootView { - UIViewController *root = [[[UIApplication sharedApplication] keyWindow] rootViewController]; - RCTRootView *rootView = [self findRootViewFromController:root]; - // 如果当前 RootViewController 中有 RCTRootView,就直接返回查找到的 RCTRootView - if (rootView) { - return rootView; - } - // 混编 React Native 项目时获取当前显示的 UIViewController 中的 RCTRootView UIViewController *current = [[SensorsAnalyticsSDK sharedInstance] currentViewController]; - return [self findRootViewFromController:current]; + RCTRootView *rootView = [self rootViewWithCurrentView:current.view]; + while (current && !rootView) { + current = current.presentingViewController; + rootView = [self rootViewWithCurrentView:current.view]; + } + return rootView; } -- (RCTRootView *)findRootViewFromController:(UIViewController *)controller { - if (!controller) { +- (RCTRootView *)rootViewWithCurrentView:(UIView *)currentView { + if (!currentView) { return nil; } - if ([controller.view isKindOfClass:RCTRootView.class]) { - return (RCTRootView *)controller.view; + if (currentView.isReactRootView) { + return (RCTRootView *)currentView; } - for (UIView *subview in controller.view.subviews) { - if ([subview isKindOfClass:RCTRootView.class]) { - return (RCTRootView *)subview; + for (UIView *subView in currentView.subviews) { + RCTRootView *rootView = [self rootViewWithCurrentView:subView]; + if (rootView) { + return rootView; } } return nil; diff --git a/package.json b/package.json index f91090d..4629430 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sensorsdata-analytics-react-native", - "version": "2.1.2", + "version": "2.1.3", "private": false, "description": "神策分析 RN 组件", "main": "index.js",