1. 采集 Fragment 页面名称和标题的意义

Fragment 与 Activity 相似,有自己的生命周期、布局。常作为 Activity 的组成部分。Fragment 在 App 开发中十分常用,例如,很多应用页面中会有多个 Tab 页对应不同的 Fragment ,还有很多 App 采用单页面实现,即只有一个 Activity ,所有的页面使用 Fragment 来实现 。不同的 Fragment 可能会复用相同的布局或包含相同的元素内容,这些元素的点击事件如果采集不到 Fragment 的页面名称和标题,就很难区分点击事件是在哪个页面中触发的,导致全埋点点击事件很难发挥它最大价值,所以在 Fragment 内的 App 点击事件需要采集 Fragment 的页面名称和标题。

2. 如何给 Fragment 设置标题

Fragment 中没有统一设置标题的方法,所以想要采集 Fragment 的页面标题,首先需要给 Fragment 提供统一的设置标题的方法。神策 Android SDK 提供了 2 种为 Fragment 设置标题的方法:

  1. 使用 @SensorsDataFragmentTitle 注解设置 Fragment 的 $title 属性的值。
  2. 通过实现 ScreenAutoTracker 接口,在 getTrackProperties 方法中扩展 $title 属性。

通过这2中方式均可以实现为 Fragment 设置标题,在点击事件全埋点采集时,只需要获取点击元素对应的 Fragment,就可以通过注解或者 getTrackProperties 方法来获取开发者设置的 $title 属性。

3. 如何实现 Fragment 页面名称和标题采集

Fragment 的页面名称取值可以为 Fragment 包名+ 类名的形式, Fragment 中元素点击事件想要采集 Fragment 的页面名称和标题,需要解决的关键问题就是通过被点击的元素,找到对应的 Fragment。

神策 Android SDK 的实现方式是在 App 编译时,对于 Fragment 中的根布局进行循环遍历所有的 View ,并给 View 通过 setTag 方法设置 View 所对应的 Fragment 的类名,实现 Fragment 内所有 View 和 Fragment 的绑定,具体实现代码如下:

private static void traverseView(String fragmentName, ViewGroup root) {
        try {
            if (TextUtils.isEmpty(fragmentName)) {
                return;
            }
            if (root == null) {
                return;
            }
            final int childCount = root.getChildCount();
            for (int i = 0; i < childCount; ++i) {
                final View child = root.getChildAt(i);
                child.setTag(R.id.sensors_analytics_tag_view_fragment_name, fragmentName);
                if (child instanceof ViewGroup && !(child instanceof ListView ||
                                                            child instanceof GridView ||
                                                            child instanceof Spinner ||
                                                            child instanceof RadioGroup)) {
                    traverseView(fragmentName, (ViewGroup) child);
                }
            }
        catch (Exception e) {
            //ignored
        }
    }

View 和 Fragment 绑定后,在触发 Fragment 内 View 的点击事件时,通过 View getTag 方法就可以获取到对应的 Fragment 类名,通过反射获取到对应的 Fragment 类,即可实现 Fragment 页面名称和页面标题属性的采集。具体实现代码如下:

public static void getScreenNameAndTitleFromFragment(JSONObject properties, Object fragment, Activity activity) {
       try {
           String screenName = null;
           String title = null;
           if (fragment instanceof ScreenAutoTracker) {
               ScreenAutoTracker screenAutoTracker = (ScreenAutoTracker) fragment;
               JSONObject trackProperties = screenAutoTracker.getTrackProperties();
               if (trackProperties != null) {
                   if (trackProperties.has(AopConstants.SCREEN_NAME)) {
                       screenName = trackProperties.optString(AopConstants.SCREEN_NAME);
                   }
                   if (trackProperties.has(AopConstants.TITLE)) {
                       title = trackProperties.optString(AopConstants.TITLE);
                   }
               }
           }
           if (TextUtils.isEmpty(title) && fragment.getClass().isAnnotationPresent(SensorsDataFragmentTitle.class)) {
               SensorsDataFragmentTitle sensorsDataFragmentTitle = fragment.getClass().getAnnotation(SensorsDataFragmentTitle.class);
               if (sensorsDataFragmentTitle != null) {
                   title = sensorsDataFragmentTitle.title();
               }
           }
           boolean isTitleNull = TextUtils.isEmpty(title);
           boolean isScreenNameNull = TextUtils.isEmpty(screenName);
           if (isTitleNull || isScreenNameNull) {
               if (activity == null) {
                   activity = getActivityFromFragment(fragment);
               }
               if (activity != null) {
                   if (isTitleNull) {
                       title = SensorsDataUtils.getActivityTitle(activity);
                   }
                   if (isScreenNameNull) {
                       screenName = fragment.getClass().getCanonicalName();
                       screenName = String.format(Locale.CHINA, "%s|%s", activity.getClass().getCanonicalName(), screenName);
                   }
               }
           }
           if (!TextUtils.isEmpty(title)) {
               properties.put(AopConstants.TITLE, title);
           }
           if (TextUtils.isEmpty(screenName)) {
               screenName = fragment.getClass().getCanonicalName();
           }
           properties.put("$screen_name", screenName);
       catch (Exception ex) {
           SALog.printStackTrace(ex);
       }
   }

 

 

每日一问的答案中可能无法全完解读这个问题,如果您是相关技术专家或者是对本问题有自己的见解,欢迎带着「批判性」的态度阅读,指正其中的不足。