美文网首页
$AppStart、$AppEnd全埋点方案

$AppStart、$AppEnd全埋点方案

作者: Peakmain | 来源:发表于2019-05-10 15:21 被阅读0次

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

    源码:https://github.com/Peakmain/baselibrary

    背景

    • 基于上篇$AppViewScreen全埋点事件
    • $AppStar事件,应用程序处于前台
    • $AppEnd事件,应用程序处于后台

    原理

    一、应用程序如果有多个进程该如何判断是处于前台还是后台

    • 实际涉及到的是应用程序多进程之间的通信
    • 目前采用的方案ContentProvider+SharedPreferences机制来解决

    二、应用程序如果发生崩溃或者被强杀死该如何判断该应用处于前台还是后台

    • 一个应用程序,它的页面退出了,如果在30s内没有新的页面打开,就认为这个应用程序处于后台
    • 如果一个应用程序的一个新页面显示出来了,且与上一个页面的退出时间超过30s,就认为这个应用程序处于前台

    思路

    • Application.ActivityLifecycleCallbacks回掉,监听所有activity的生命周期
    • 页面退出(onPause生命周期),启动一个30s的计时器,如果30s内没有新的页面进来(或显示),则触发$AppEnd
    • 如果有新的页面进来(或显示),则存储一个标记位来标记已有新的页面进来。
    • 若activity之间跨进程,利用ContentProvider+SharedPreferences存储,然后通过ContentObserver监听到新页面进来的标记位改变,并取消上个页面退出时启动的倒计时
    • 页面启动的时候(onStart),判断与上个页面退出的时间间隔是否超过30s,如果没有超过30s,则触发$AppViewScreen事件
    • 如果超过30s,判断是否触发了AppEnd事件,因为App可能崩溃了或者被强杀死了,没有触发AppEnd事件,若没有,先触发AppEnd事件,然后触发$AppViewScreen事件和AppStart事件

    代码实现

    AppViewScreenDataPrivate代码修改
    首先我们需要一些变量。 时间:30s,倒计时:CountDownTimer,当前activity:WeakReference<Activity>,一个帮助类

        private static SensorsDatabaseHelper mDatabaseHelper;
        /**
         * 当前activity的集合
         */
        private static WeakReference<Activity> mCurrentActivity;
        //倒计时
        private static CountDownTimer countDownTimer;
        //30s
        private final static int SESSION_INTERVAL_TIME = 30 * 1000;
    

    修改Application.ActivityLifecycleCallbacks

    public static void registerActivityLifecycleCallbacks(Application application) {
            mDatabaseHelper=new SensorsDatabaseHelper(application.getApplicationContext(),application.getPackageName());
            //初始化30s,间隔1s
            countDownTimer=new CountDownTimer(SESSION_INTERVAL_TIME,10*1000) {
                @Override
                public void onTick(long millisUntilFinished) {
    
                }
    
                @Override
                public void onFinish() {
                    trackAppEnd(mCurrentActivity.get());
                }
            };
            application.registerActivityLifecycleCallbacks(new Application.ActivityLifecycleCallbacks() {
                @Override
                public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
    
                }
    
                @Override
                public void onActivityStarted(Activity activity) {
                    //添加标记,已经开始了
                    mDatabaseHelper.commitAppStart(true);
                    //获得间隔时间
                    double timeDiff = System.currentTimeMillis() - mDatabaseHelper.getAppPausedTime();
                    //如果间隔时间大于30s
                    if (timeDiff > SESSION_INTERVAL_TIME) {
                        //判断是否已经触发过了$AppEnd
                        if (!mDatabaseHelper.getAppEndEventState()) {
                            trackAppEnd(activity);
                        }
                    }
                    if (mDatabaseHelper.getAppEndEventState()) {
                        //设置未触发$AppEnd
                        mDatabaseHelper.commitAppEndEventState(false);
                        trackAppStart(activity);
                    }
                }
    
                @Override
                public void onActivityResumed(Activity activity) {
                    trackAppViewScreen(activity);
                }
    
                @Override
                public void onActivityPaused(Activity activity) {
                    mCurrentActivity = new WeakReference<>(activity);
                    countDownTimer.start();
                    mDatabaseHelper.commitAppPausedTime(System.currentTimeMillis());
                }
    
                @Override
                public void onActivityStopped(Activity activity) {
    
                }
    
                @Override
                public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
    
                }
    
                @Override
                public void onActivityDestroyed(Activity activity) {
    
                }
            });
        }
    

    AppStart和AppEnd事件

        /**
         * $AppStart事件
         */
        private static void trackAppStart(Activity activity) {
            try {
                if (activity == null) {
                    return;
                }
                JSONObject properties = new JSONObject();
                properties.put("$activity", activity.getClass().getCanonicalName());
                properties.put("$title", getActivityTitle(activity));
                AppViewScreenDataAPI.getInstance().track("$AppStart", properties);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        /**
         * $AppEnd事件
         */
        private static void trackAppEnd(Activity activity) {
            try {
                if(activity==null){
                    return;
                }
                JSONObject properties=new JSONObject();
                properties.put("$activity",activity.getClass().getCanonicalName());
                properties.put("$title",getActivityTitle(activity));
                AppViewScreenDataAPI.getInstance().track("$AppEnd", properties);
                mDatabaseHelper.commitAppEndEventState(true);
                mCurrentActivity = null;
            } catch (JSONException e) {
                e.printStackTrace();
            }
        }
    
    

    注册 AppStart 的监听:当appstart的时候,需要去取消上个页面退出时启动的倒计时

        public static void registerActivityStateObserver(Application application) {
            application.getContentResolver().registerContentObserver(mDatabaseHelper.getAppStartUri(),
                    false, new ContentObserver(new Handler()) {
                        @Override
                        public void onChange(boolean selfChange, Uri uri) {
                            if (mDatabaseHelper.getAppStartUri().equals(uri)) {
                                countDownTimer.cancel();
                            }
                        }
                    });
        }
    

    SensorsDatabaseHelper帮助类

    public class SensorsDatabaseHelper {
        private static final String SENSORSDATACONTENTPROVIDER = ".SensorsDataContentProvider/";
        //内容解析者
        private ContentResolver mContentResolver;
        //uri
        private Uri mAppStart;
        private Uri mAppEndState;
        private Uri mAppPausedTime;
    
        public static final String APP_STARTED = "$app_started";
        public static final String APP_END_STATE = "$app_end_state";
        public static final String APP_PAUSED_TIME = "$app_paused_time";
    
        public SensorsDatabaseHelper(Context context, String packageName) {
            mContentResolver = context.getContentResolver();
            mAppStart = Uri.parse("content://" + packageName + SENSORSDATACONTENTPROVIDER + SensorsDataTable.APP_STARTED.getName());
            mAppEndState = Uri.parse("content://" + packageName + SENSORSDATACONTENTPROVIDER + SensorsDataTable.APP_END_STATE.getName());
            mAppPausedTime = Uri.parse("content://" + packageName + SENSORSDATACONTENTPROVIDER + SensorsDataTable.APP_PAUSED_TIME.getName());
    
        }
        /**
         * 添加标记已经开始了,添加到sharedpreferences
         *
         * @param appStart true 表示开始了
         */
        public void commitAppStart(boolean appStart) {
            ContentValues values=new ContentValues();
            values.put(APP_STARTED,appStart);
            mContentResolver.insert(mAppStart,values);
        }
    
        /**
         * 将暂停时间  添加到sharedpreferences
         *
         * @param pausedTime 暂停时间
         */
        public void commitAppPausedTime(long pausedTime) {
            ContentValues contentValues = new ContentValues();
            contentValues.put(APP_PAUSED_TIME, pausedTime);
            mContentResolver.insert(mAppPausedTime, contentValues);
        }
    
        /**
         * 获得 暂停时间
         */
        public long getAppPausedTime() {
            long pasuedTime = 0;
            Cursor cursor = mContentResolver.query(mAppPausedTime, new String[]{APP_PAUSED_TIME}, null, null, null);
            if (cursor != null && cursor.getCount() > 0) {
                while (cursor.moveToNext()) {
                    pasuedTime = cursor.getLong(0);
                }
            }
            if (cursor != null) {
                cursor.close();
            }
            return pasuedTime;
        }
    
        /**
         * 保存状态到sp
         * @param appEndState
         */
        public void commitAppEndEventState(boolean appEndState) {
            ContentValues contentValues=new ContentValues();
            contentValues.put(APP_END_STATE,appEndState);
            mContentResolver.insert(mAppEndState,contentValues);
        }
    
        /**
         * app是否已经触发过$AppEnd事件
         */
        public boolean getAppEndEventState() {
            boolean state = true;
            Cursor cursor = mContentResolver.query(mAppEndState, new String[]{APP_END_STATE}, null, null, null);
            if (cursor != null && cursor.getCount() > 0) {
                while (cursor.moveToNext()) {
                    state = cursor.getInt(0) > 0;
                }
            }
    
            if (cursor != null) {
                cursor.close();
            }
            return state;
        }
        public Uri getAppStartUri() {
            return mAppStart;
        }
    }
    

    SensorsDataContentProvider

    public class SensorsDataContentProvider extends ContentProvider{
        //id
        private final static int APP_START = 1;
        private final static int APP_END_STATE = 2;
        private final static int APP_PAUSED_TIME = 3;
    
        private static SharedPreferences sharedPreferences;
        private static SharedPreferences.Editor mEditor;
        private static UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
        private ContentResolver mContentResolver;
        @Override
        public boolean onCreate() {
            if(getContext()!=null){
                String packageName = getContext().getPackageName();
                //添加
                uriMatcher.addURI(packageName + ".SensorsDataContentProvider", SensorsDataTable.APP_STARTED.getName(), APP_START);
                uriMatcher.addURI(packageName + ".SensorsDataContentProvider", SensorsDataTable.APP_END_STATE.getName(), APP_END_STATE);
                uriMatcher.addURI(packageName + ".SensorsDataContentProvider", SensorsDataTable.APP_PAUSED_TIME.getName(), APP_PAUSED_TIME);
                sharedPreferences = getContext().getSharedPreferences("com.peakmain.sdk.AppViewScreenDataAPI", Context.MODE_PRIVATE);
                mEditor = sharedPreferences.edit();
                mEditor.apply();
                mContentResolver = getContext().getContentResolver();
            }
            return false;
        }
    
        @Nullable
        @Override
        public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
            int code=uriMatcher.match(uri);
            //没有数据库使用MatrixCursor替代
            MatrixCursor matrixCursor=null;
            switch (code) {
                case APP_START:
                    int appStart = sharedPreferences.getBoolean(SensorsDatabaseHelper.APP_STARTED, true) ? 1 : 0;
                    matrixCursor = new MatrixCursor(new String[]{SensorsDatabaseHelper.APP_STARTED});
                    matrixCursor.addRow(new Object[]{appStart});
                    break;
                case APP_END_STATE:
                    int appEnd = sharedPreferences.getBoolean(SensorsDatabaseHelper.APP_END_STATE, true) ? 1 : 0;
                    matrixCursor = new MatrixCursor(new String[]{SensorsDatabaseHelper.APP_END_STATE});
                    matrixCursor.addRow(new Object[]{appEnd});
                    break;
                case APP_PAUSED_TIME:
                    long pausedTime = sharedPreferences.getLong(SensorsDatabaseHelper.APP_PAUSED_TIME, 0);
                    matrixCursor = new MatrixCursor(new String[]{SensorsDatabaseHelper.APP_PAUSED_TIME});
                    matrixCursor.addRow(new Object[]{pausedTime});
                    break;
                default:
                    break;
            }
            return matrixCursor;
        }
        @Nullable
        @Override
        public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
            if (values == null) {
                return uri;
            }
            int code = uriMatcher.match(uri);
            switch (code) {
                case APP_START:
                    boolean appStart = values.getAsBoolean(SensorsDatabaseHelper.APP_STARTED);
                    mEditor.putBoolean(SensorsDatabaseHelper.APP_STARTED, appStart);
                    mContentResolver.notifyChange(uri, null);
                    break;
                case APP_END_STATE:
                    boolean appEnd = values.getAsBoolean(SensorsDatabaseHelper.APP_END_STATE);
                    mEditor.putBoolean(SensorsDatabaseHelper.APP_END_STATE, appEnd);
                    break;
                case APP_PAUSED_TIME:
                    long pausedTime = values.getAsLong(SensorsDatabaseHelper.APP_PAUSED_TIME);
                    mEditor.putLong(SensorsDatabaseHelper.APP_PAUSED_TIME, pausedTime);
                    break;
                default:
                    break;
            }
            mEditor.commit();
            return uri;
        }
        @Nullable
        @Override
        public String getType(@NonNull Uri uri) {
            return null;
        }
    
    
    
        @Override
        public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
            return 0;
        }
    
        @Override
        public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
            return 0;
        }
    }
    

    需要记得注册

        <application>
            <provider
                android:name=".datacollection.appstartandend.SensorsDataContentProvider"
                android:authorities="${applicationId}.SensorsDataContentProvider"
                android:enabled="true"
                android:exported="false" />
        </application>
    

    相关文章

      网友评论

          本文标题:$AppStart、$AppEnd全埋点方案

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