美文网首页Android技术
$AppViewScreen全埋点方案

$AppViewScreen全埋点方案

作者: Peakmain | 来源:发表于2019-05-01 15:01 被阅读71次

参考书籍:《Android全埋点解决方案》

背景

$AppViewScreen事件,即页面浏览事件。在Android中页面浏览实际指的就是切换不同的Activity或者Fragment

  • Activity的生命周期
    文章:https://www.jianshu.com/p/48cb882b8717
    生命周期图

    image.png
    我们会发现,一个activity生命周期中onResume执行了代表该页面被显示出来了,也就是该页面被浏览了,所以事件onResume里触发$AppViewScreen
  • 关键技术
    Application.ActivityLifecycleCallbacks
    原理:调用Application中的registerActivityLifecycleCallbacks方法,在onActivityResumed(Activity activity)方法中拿到正在显示的activity对象

权限6.0

使用

       if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_CONTACTS) ==
                PackageManager.PERMISSION_GRANTED) {
            //拥有权限
        } else {
            //没有权限,需要申请全新啊
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_CONTACTS},
                    PERMISSIONS_REQUEST_READ_CONTACTS);
        }
    }
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        AppViewScreenDataAPI.getInstance().ignoreAutoTrackActivity(MainActivity.class);
        switch (requestCode) {
            case PERMISSIONS_REQUEST_READ_CONTACTS:
                if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    // 用户点击允许
                } else {
                    // 用户点击禁止
                }
                break;
                default:
                    break;
        }
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    }
  • 源码分析
    ActivityCompat.requestPermissions源码分析
    public static void requestPermissions(final @NonNull Activity activity,
            final @NonNull String[] permissions, final @IntRange(from = 0) int requestCode) {
   //代码省略
        if (Build.VERSION.SDK_INT >= 23) {
            //实际会调用 onRequestPermissionsResult和Activity的startActivityForResult方法,随即会走activity的OnResume方法
            activity.requestPermissions(permissions, requestCode);
        } else if (activity instanceof OnRequestPermissionsResultCallback) {
           //代码省略
        }
    }
    public final void requestPermissions(@NonNull String[] permissions, int requestCode) {
        if (mHasCurrentPermissionsRequest) {
           onRequestPermissionsResult(requestCode, new String[0], new int[0]);
            return;
        }
        Intent intent = getPackageManager().buildRequestPermissionsIntent(permissions);
        startActivityForResult(REQUEST_PERMISSIONS_WHO_PREFIX, intent, requestCode, null);
        mHasCurrentPermissionsRequest = true;
    }

用户无论点击同意还是拒绝实际都会调用 onRequestPermissionsResult和Activity的startActivityForResult方法,随即会走activity的OnResume方法,所以在埋点的时候需要避免多次调用onResume方法

  • 思路
    onRequestPermissionsResult的时候忽略某个activity,onStop的时候恢复activity

实现方案

  • 效果


    image.png
  • AppViewScreenDataAPI
    初始化数据,获取设备id,设备信息

  private final String TAG = this.getClass().getSimpleName();
    public static final String SDK_VERSION = "1.0.0";
    private static AppViewScreenDataAPI INSTANCE;
    private static final Object mLock = new Object();
    private static Map<String, Object> mDeviceInfo;
    private String mDeviceId;


    private AppViewScreenDataAPI(Application application) {
        mDeviceId = AppViewScreenDataPrivate.getAndroidID(application.getApplicationContext());
        mDeviceInfo = AppViewScreenDataPrivate.getDeviceInfo(application.getApplicationContext());
        AppViewScreenDataPrivate.registerActivityLifecycleCallbacks(application);
    }

    @Keep
    public static AppViewScreenDataAPI init(Application application) {
        if (INSTANCE == null) {
            synchronized (mLock) {
                if (null == INSTANCE) {
                    INSTANCE = new AppViewScreenDataAPI(application);
                }
            }
        }
        return INSTANCE;
    }

    @Keep
    public static AppViewScreenDataAPI getInstance() {
        return INSTANCE;
    }

指定不采集哪个 Activity 的页面浏览事件

    public void ignoreAutoTrackActivity(Class<?> activity) {
        AppViewScreenDataPrivate.ignoreAutoTrackActivity(activity);
    }

恢复采集某个 Activity 的页面浏览事件

   public void removeIgnoredActivity(Class<?> activity) {
        AppViewScreenDataPrivate.removeIgnoredActivity(activity);
    }

事件

    /**
     * track 事件
     *
     * @param eventName  String 事件名称
     * @param properties JSONObject 事件自定义属性
     */
    public void track(@NonNull String eventName, @Nullable JSONObject properties) {
        try {
            JSONObject jsonObject = new JSONObject();
            jsonObject.put("event", eventName);
            jsonObject.put("device_id", mDeviceId);

            JSONObject sendProperties = new JSONObject(mDeviceInfo);

            if (properties != null) {
                AppViewScreenDataPrivate.mergeJSONObject(properties, sendProperties);
            }

            jsonObject.put("properties", sendProperties);
            jsonObject.put("time", System.currentTimeMillis());
            //打印数据
            Log.i(TAG, AppViewScreenDataPrivate.formatJson(jsonObject.toString()));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
  • AppViewScreenDataPrivate具体实现类
    初始化
    private static List<String> mIgnoredActivities;

    static {
        mIgnoredActivities = new ArrayList<>();
    }

    private static final SimpleDateFormat mDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"
            + ".SSS", Locale.CHINA);

指定不采集哪个 Activity 的页面浏览事件

    public static void ignoreAutoTrackActivity(Class<?> activity) {
        if (activity == null) {
            return;
        }
        mIgnoredActivities.add(activity.getClass().getCanonicalName());
    }

恢复采集某个 Activity 的页面浏览事件

    public static void removeIgnoredActivity(Class<?> activity) {
        if (activity == null) {
            return;
        }

        if (mIgnoredActivities.contains(activity.getClass().getCanonicalName())) {
            mIgnoredActivities.remove(activity.getClass().getCanonicalName());
        }
    }

获取 Android ID

    public static String getAndroidID(Context mContext) {
        String androidID = "";
        try {
            androidID = Settings.Secure.getString(mContext.getContentResolver(), Settings.Secure.ANDROID_ID);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return androidID;
    }

获取设备信息

    public static Map<String, Object> getDeviceInfo(Context context) {
        final Map<String, Object> deviceInfo = new HashMap<>();
        deviceInfo.put("$lib", "Android");
        deviceInfo.put("$lib_version", AppViewScreenDataAPI.SDK_VERSION);
        deviceInfo.put("$os", "Android");
        deviceInfo.put("$os_version",
                Build.VERSION.RELEASE == null ? "UNKNOWN" : Build.VERSION.RELEASE);
        deviceInfo
                .put("$manufacturer", Build.MANUFACTURER == null ? "UNKNOWN" : Build.MANUFACTURER);
        if (TextUtils.isEmpty(Build.MODEL)) {
            deviceInfo.put("$model", "UNKNOWN");
        } else {
            deviceInfo.put("$model", Build.MODEL.trim());
        }
        try {
            final PackageManager manager = context.getPackageManager();
            final PackageInfo packageInfo = manager.getPackageInfo(context.getPackageName(), 0);
            deviceInfo.put("$app_version", packageInfo.versionName);

            int labelRes = packageInfo.applicationInfo.labelRes;
            deviceInfo.put("$app_name", context.getResources().getString(labelRes));
        } catch (final Exception e) {
            e.printStackTrace();
        }

        final DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
        deviceInfo.put("$screen_height", displayMetrics.heightPixels);
        deviceInfo.put("$screen_width", displayMetrics.widthPixels);

        return Collections.unmodifiableMap(deviceInfo);
    }

获取activity的标题

    private static String getToolbarTitle(Activity activity) {
        ActionBar actionBar = activity.getActionBar();
        if (actionBar != null) {
            if (!TextUtils.isEmpty(actionBar.getTitle())) {
                return actionBar.getTitle().toString();
            }
        } else {
            if (activity instanceof AppCompatActivity) {
                AppCompatActivity appCompatActivity = (AppCompatActivity) activity;
                android.support.v7.app.ActionBar supportActionBar = appCompatActivity.getSupportActionBar();
                if (supportActionBar != null) {
                    if (!TextUtils.isEmpty(supportActionBar.getTitle())) {
                        return supportActionBar.getTitle().toString();
                    }
                }
            }
        }
        return null;
    }

    /**
     * 获取 Activity 的 title
     */
    @SuppressWarnings("all")
    private static String getActivityTitle(Activity activity) {
        String activityTitle = "";
        if (activity == null) {
            return null;
        }
        try {
            activityTitle = activity.getTitle().toString();
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
                String toolbarTitle = getToolbarTitle(activity);
                if (!TextUtils.isEmpty(toolbarTitle)) {
                    activityTitle = toolbarTitle;
                }
            }
            if (TextUtils.isEmpty(activityTitle)) {
                PackageManager packageManager = activity.getPackageManager();
                if (packageManager != null) {
                    ActivityInfo activityInfo = packageManager.getActivityInfo(activity.getComponentName(), 0);
                    if (activityInfo != null) {
                        activityTitle = activityInfo.loadLabel(packageManager).toString();
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return activityTitle;
    }

注册 Application.ActivityLifecycleCallbacks

    /**
     * 注册 Application.ActivityLifecycleCallbacks
     */
    public static void registerActivityLifecycleCallbacks(Application application) {
        application.registerActivityLifecycleCallbacks(new Application.ActivityLifecycleCallbacks() {
            @Override
            public void onActivityCreated(Activity activity, Bundle savedInstanceState) {

            }

            @Override
            public void onActivityStarted(Activity activity) {

            }

            @Override
            public void onActivityResumed(Activity activity) {
                trackAppViewScreen(activity);
            }

            @Override
            public void onActivityPaused(Activity activity) {

            }

            @Override
            public void onActivityStopped(Activity activity) {

            }

            @Override
            public void onActivitySaveInstanceState(Activity activity, Bundle outState) {

            }

            @Override
            public void onActivityDestroyed(Activity activity) {

            }
        });
    }

Track 页面浏览事件

    private static void trackAppViewScreen(Activity activity) {
        try {
            if(activity==null){
                return;
            }
            if(mIgnoredActivities.contains(activity.getClass().getCanonicalName())){
                  return;
            }
            JSONObject properties = new JSONObject();
            properties.put("$activity", activity.getClass().getCanonicalName());
            properties.put("$title", getActivityTitle(activity));
            AppViewScreenDataAPI.getInstance().track("$AppViewScreen", properties);
        } catch (JSONException e) {
            e.printStackTrace();
        }
    }

拼接起来

    private static void addIndentBlank(StringBuilder sb, int indent) {
        try {
            for (int i = 0; i < indent; i++) {
                sb.append('\t');
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static String formatJson(String jsonStr) {
        try {
            if (null == jsonStr || "".equals(jsonStr)) {
                return "";
            }
            StringBuilder sb = new StringBuilder();
            char last;
            char current = '\0';
            int indent = 0;
            boolean isInQuotationMarks = false;
            for (int i = 0; i < jsonStr.length(); i++) {
                last = current;
                current = jsonStr.charAt(i);
                switch (current) {
                    case '"':
                        if (last != '\\') {
                            isInQuotationMarks = !isInQuotationMarks;
                        }
                        sb.append(current);
                        break;
                    case '{':
                    case '[':
                        sb.append(current);
                        if (!isInQuotationMarks) {
                            sb.append('\n');
                            indent++;
                            addIndentBlank(sb, indent);
                        }
                        break;
                    case '}':
                    case ']':
                        if (!isInQuotationMarks) {
                            sb.append('\n');
                            indent--;
                            addIndentBlank(sb, indent);
                        }
                        sb.append(current);
                        break;
                    case ',':
                        sb.append(current);
                        if (last != '\\' && !isInQuotationMarks) {
                            sb.append('\n');
                            addIndentBlank(sb, indent);
                        }
                        break;
                    default:
                        sb.append(current);
                }
            }

            return sb.toString();
        } catch (Exception e) {
            e.printStackTrace();
            return "";
        }
    }

相关文章

  • $AppViewScreen全埋点方案

    参考书籍:《Android全埋点解决方案》 背景 $AppViewScreen事件,即页面浏览事件。在Androi...

  • Android 全埋点解决方案(一)

    一、埋点方案总结AppEnd 全埋点方案 AppClick全埋点方案1: 代理View.OnclickListen...

  • 埋点技术方案-实施简要

    目前市场埋点方案分以下三种,稍后一一说明拙见。(1),代码埋点;(2),视觉化埋点,本文略。(3),全埋点;以前有...

  • Android全埋点方案调研

    1 行业内全埋点技术方案调研 调研文章链接: 网易云音乐Android 自动埋点实践 网易HubbleData之A...

  • 埋点方案细节Q&A

    接上篇《埋点&运营分析产品设计》之后,关于埋点方案还有一些不得不探讨的内容:为什么选择全埋点而不选择可视化埋点?埋...

  • $AppStart、$AppEnd全埋点方案

    参考书籍:《Android全埋点解决方案》 源码:https://github.com/Peakmain/base...

  • 首创Flutter全埋点方案

    引言:无痕埋点,众所周知是移动端一个收集用户行为和数据分析很重要的一项技术手段。Flutter作为近几年年大热的移...

  • 神策Android全埋点方案分析

    神策Android全埋点方案 原理简单分析: Activity生命周期通过监听Application.Act...

  • iOS无痕埋点方案分享探究

    iOS无痕埋点方案分享探究 iOS无痕埋点方案分享探究

  • 如何设计数据埋点方案

    1、常见的埋点方式 (1)全埋点 全埋点是指预先在所有可以埋点的地方进行埋点的操作,再根据实际的的场景来分析需求提...

网友评论

    本文标题:$AppViewScreen全埋点方案

    本文链接:https://www.haomeiwen.com/subject/qnkunqtx.html