Android SDK startup and exit scheme evolution | data collection

1, Foreword

In operation analysis, DAU (Daily Active User), UV (Unique Visitor) and user usage duration are the three most common indicators. For an App, the meanings of the three indicators are as follows:

  • DAU: number of daily active users;

  • UV: independent visitors;

  • User usage duration: App usage duration.

According to the above description, the statistical analysis of DAU and UV is closely related to the App start event, and the user's use time needs to be analyzed through the App exit event.

In the divine strategy analysis, the statistics of the above three indicators are as follows:

  • DAU: count by querying the number of independent users launched by the App every day;

  • UV: statistics by querying the total number of users started by App;

  • User usage duration: it is calculated by querying the average duration of App exit events.

Shence Android SDK[1] has been open source for more than four years. During this period, nearly 160 versions have been released one after another. From the initial support of only code embedding points to the current support of full embedding points, visual full embedding points and other functions. Among them, the collection of start-up events and exit events of the whole buried point has also undergone continuous evolution. As a participant in the scheme design, the author sorted out the whole process. Let's introduce it to you step by step, hoping to help you and get some guidance.

2, Basic principles

Because there is no open API interface in Android system to monitor the startup and exit of App, it is impossible to accurately judge the startup and exit of App directly from the system level. In Android, the page hosting of App is based on Activity, so you can try to judge the start and exit of App from the dimension of the number of starts of Activity.

Generally speaking, when it is detected that the first Activity is open, it is marked as App startup; When it is detected that the last Activity page is closed, it is marked as App exit.

In Android system, you can register Application in Application The activitylifecyclecallbacks callback is used to count the switching of activities, so as to achieve the buried point collection of App startup and exit events. The code is as follows:

ActivityLifecycleCallbacks

public interface ActivityLifecycleCallbacks {

     void onActivityCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState);     void onActivityStarted(@NonNull Activity activity);     void onActivityResumed(@NonNull Activity activity);     void onActivityPaused(@NonNull Activity activity);     void onActivityStopped(@NonNull Activity activity);     void onActivityDestroyed(@NonNull Activity activity);}

The code is interpreted as follows:

  • onActivityCreated: when the Activity calls super Triggered when oncreate();

  • onActivityStarted: when an Activity calls super Triggered when onstart();

  • onActivityResumed: when the Activity calls super Triggered when onresume();

  • onActivityPaused: when the Activity calls super Triggered when onpause();

  • onActivityStopped: when an Activity calls super Triggered when onstop();

  • onActivityDestroyed: when an Activity calls super Triggered when ondestroy().

3, Scheme evolution

3.1 initial version 1.0

3.1. 1. Principle introduction

In the early version, App startup and exit are judged by monitoring the onStart and onStop life cycle functions of Activity:

1. Register the application during SDK initialization The activitylifecyclecallbacks monitors and internally maintains an Activity counter;

2. When the onActivityStarted callback is triggered, if the number of Activity counters is 0, it indicates the first page opened by the App (i.e. App startup). At this time, the App startup event is triggered and the counter is incremented by 1;

3. When the onActivityStopped callback is triggered, it indicates that an Activity page is not visible, and the Activity counter is decremented by 1. If the number of Activity counters is 0, it means that the last page of the App is not visible (i.e. App exits). At this time, the App exit event is triggered.

The core process is shown in Figure 3-1:

Figure 3-1 flow chart of the initial version

3.1. 2. Concrete realization

For the core logic of start and exit event collection in the early version, the code example is as follows:

Core logic implementation

public class MyApplication extends Application {     @Override    public void onCreate() {        super.onCreate();        registerActivityLifecycleCallbacks(new SensorsDataActivityLifecycleCallbacks());    }     static class SensorsDataActivityLifecycleCallbacks implements Application.ActivityLifecycleCallbacks {        private static final String TAG = "SA.LifecycleCallbacks";        private int mActivityCount;         @Override        public void onActivityCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState) {}         @Override        public void onActivityStarted(@NonNull Activity activity) {            if (mActivityCount == 0) {                Log.d(TAG, "App start-up");            }            mActivityCount++;        }         @Override        public void onActivityResumed(@NonNull Activity activity) {}         @Override        public void onActivityPaused(@NonNull Activity activity) {}         @Override        public void onActivityStopped(@NonNull Activity activity) {            mActivityCount--;            if (mActivityCount == 0) {                Log.d(TAG, "App sign out");            }        }         @Override        public void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle outState) {}         @Override        public void onActivityDestroyed(@NonNull Activity activity) {}    }}

3.1. 3. Advantages and disadvantages

advantage:

1. The implementation of the initial version is relatively simple;

2. It better meets the early customer needs and can accurately collect App start events and App exit events.

Disadvantages:

When multi process pages appear in the App, if cross process page switching is carried out, the App will be triggered to exit, resulting in the interruption of user behavior sequence, which will cause great trouble to the analysis of user behavior sequence.

3.2 interim version 2.0

3.2. 1. Principle introduction

With the increase of customers, the scenarios used by App become more and more complex. The initial version can no longer meet the needs of some scenarios, affecting the experience of analysis functions. It mainly faces the following scenarios:

Scenario 1: App front and back console switching

In the early version, when the Activity enters the background, the number of counters is 0. At this time, the App exit event will be triggered, and the App start event will be triggered when entering the App again. If the front and back switches frequently, many App start events and exit events will be collected. The App start events in this scenario are meaningless to the actual analysis of DAU. For example, a user needs to enter a verification code on a page and temporarily switch the App to the background to view the verification code in the SMS. When the App is opened again, a pair of App start and exit events will be triggered. In this scenario, triggering App start and exit events will interrupt the user's logical behavior sequence, which is not conducive to the actual user behavior sequence analysis.

Scenario 2: the App jumps to a third-party page and returns

In some scenarios, App startup and exit will interrupt the user's actual behavior sequence, which is not conducive to the analysis of the actual user behavior sequence. For example, in some merchant service apps, after the user completes the order, click the payment button to jump to the third-party payment platform for payment. After the payment is completed, click the return button to re-enter the scene in the App. The processing of the initial version will trigger a pair of App start and exit events. The start and exit in this scenario will interrupt the user's actual behavior sequence.

Scenario 3: there are multiple process pages inside the App

When an Activity page in an App is in a separate process, the page counter in the initial version does not support multi process sharing, resulting in multi process page Jump in the same App, which will trigger App start and exit events, resulting in incorrect App start and exit event collection.

Scenario 4: App crashes abnormally or kills by force

Abnormal crash or forced killing in the App is a very common scenario, but it cannot be handled in the initial version, so the exit event of the App cannot be collected in this scenario.

For the above four scenarios, we give the following solutions:

1. For scenario 1 and scenario 2, we introduce the concept of Session duration. With the default Session duration interval of 30s, for an App, when one of its pages exits, if no new page is opened within 30s, we think the App enters the background, and the App exit event will be triggered; When one of its pages is displayed, if the exit time interval from the previous page exceeds 30s, we think that the application is in the foreground again, and the App startup event will be triggered;

2. For scenario 3, we introduce a flag bit to identify whether the App exit event is triggered. When the App exits the background for 30s, if the App exit event flag bit is false, the App exit event will be triggered and the App exit event flag will be reset to true. For the reading of cross process flag bits, we use the "ContentProvider + SharedPreferences" method of Android system to share cross process data, so as to ensure the accuracy of App exit event flag bits read between different processes;

3. For scenario 4, for the abnormal crash of the App, by introducing thread Uncaughtexceptionhandler listens to the user-defined exception handler and completes the collection of App exit in the user-defined uncaughtException method. Because some crash or forced killing scenarios on the Native end cannot be captured, the timed management function is used to save the information of App exit events. When the App is opened next time, if the App flag bit is detected to be false, the App exit event will be reissued, and the App exit event flag will be reset to true.

The core process is shown in Figure 3-2:

Figure 3-2 flow chart of intermediate version

It can be seen from the flow chart of the medium-term version that the collection principle for App start events and exit events has been greatly changed, which is obviously much more complex than the initial version. At the same time, more details are involved. For example: tag bit storage failure, event triggered timestamp saving, etc.

3.2. 2. Concrete realization

For the core logic collected in the medium-term version, the code is implemented as follows:

Core logic implementation

package com.sensorsdata.analytics.android.demo; import android.app.Activity;import android.app.Application;import android.content.ContentResolver;import android.content.ContentValues;import android.content.Context;import android.database.Cursor;import android.net.Uri;import android.os.Bundle;import android.os.CountDownTimer; import java.lang.ref.WeakReference; public class MyApplication extends Application {     @Override    public void onCreate() {        super.onCreate();        registerActivityLifecycleCallbacks(new SensorsDataActivityLifecycleCallbacks());    }     static class SensorsDataActivityLifecycleCallbacks implements Application.ActivityLifecycleCallbacks {        private static SensorsDatabaseHelper mDatabaseHelper;        private static CountDownTimer countDownTimer;        private final static int SESSION_INTERVAL_TIME = 30 * 1000;         @Override        public void onActivityCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState) {         }         @Override        public void onActivityStarted(@NonNull Activity activity) {            mDatabaseHelper.commitAppStart(true);            double timeDiff = System.currentTimeMillis() - mDatabaseHelper.getAppPausedTime();            if (timeDiff > SESSION_INTERVAL_TIME) {                if (!mDatabaseHelper.getAppEndEventState()) {                    trackAppEnd();                }            }             if (mDatabaseHelper.getAppEndEventState()) {                mDatabaseHelper.commitAppEndEventState(false);                trackAppStart();            }        }         @Override        public void onActivityResumed(@NonNull Activity activity) {            //Here you want to start timing, which is used to record App exit event information timer();}@ Override        public void onActivityPaused(@NonNull Activity activity) {            countDownTimer.start();            cancelTimer();            mDatabaseHelper.commitAppPausedTime(System.currentTimeMillis());        }         @ Override        public void onActivityStopped(@NonNull Activity activity) {        }         @Override        public void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle outState) {         }         @Override        public void onActivityDestroyed(@NonNull Activity activity) {         } //Trigger the App start event private void trackAppStart() {} / / trigger the App exit event private void trackAppEnd() {} / / regularly record the App exit information private void timer() {} / / cancel the regularly scheduled private void cancelTimer() {}}

3.2. 3. Advantages and disadvantages

advantage:

1. It realizes the collection of App startup and exit events in complex scenes;

2. The collection of APP startup and exit events is more accurate.

Disadvantages:

1. In order to solve more scenarios, the implementation principle is more complex;

2. For frequent storage of marker bits and timing management, these business processes have a lot of performance consumption.

3.3 stable version 3.0

3.3. 1. Principle introduction

The core purpose of the mid-term version is to solve more complex scenarios and make up for the shortcomings of the early version. With the increasing number of users, there are increasingly strict requirements for the performance of the SDK. Therefore, some optimization needs to be done for the medium-term version.

Performance consumption points exposed in the interim version:

  • When onActivityStarted is triggered, execute frequent reading of start timestamp AppStartTime, frequent reading of App exit event flag bit, and frequent judgment of Session duration;

  • When onActivityResumed is triggered, restart the dotting timer;

  • When onActivityPaused is triggered, the dotting timer is closed.

In an App, when frequent Activity switching is involved, the above life cycle function will be executed multiple times. Frequent internal access timestamp will cause large resource consumption. How to avoid frequent reading? Following the troubleshooting idea of this problem, the early version of the counter combined with the mid-term version of cross process communication is finally used to solve the frequent performance consumption. In general, it means "the logical judgment of App startup is executed only on the first page, and the logical judgment of App exit is executed only on the last page".

The core process is shown in Figure 3-3:

Figure 3-3 flow chart of stable version

In the stable version, there will be a process of trying to reissue the App exit event:

  • If the difference between the current start timestamp and the last exit timestamp exceeds the session duration, try reissue;

  • If the App exit information read from the local cache is empty, it will not be reissued. If it is not empty, it will be reissued.

Under the normal process, the App exit information of local management will be cleared after the App exit event is triggered. If the App exit event has been triggered, an empty App exit information will be read when trying to reissue, and the App exit event will not be triggered. Therefore, this process is called trying to reissue. It can be seen from the flow chart of the stable version that compared with the medium-term version, the process has been greatly optimized and easier to understand, and the performance consumption is much lower than that of the medium-term version.

3.3. 2. Concrete realization

For the stable version of the core logic, the code implementation is as follows:

Core logic implementation

public class MyApplication extends Application {

     @Override    public void onCreate() {        super.onCreate();        registerActivityLifecycleCallbacks(new SensorsDataActivityLifecycleCallbacks());    }     static class SensorsDataActivityLifecycleCallbacks implements Application.ActivityLifecycleCallbacks {        private static SensorsDatabaseHelper mDatabaseHelper;        private int startActivityCount;        private int startTimerCount;        private final static int SESSION_INTERVAL_TIME = 30 * 1000;         @Override        public void onActivityCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState) {         }         @Override        public void onActivityStarted(@NonNull Activity activity) {            // Number of read activities startactivitycount = mdatabasehelper getActivityCount();             mDatabaseHelper. commitActivityCount(++startActivityCount);            //  If it is the first page, if (startactivitycount = = 1) {Boolean sessiontimeout = issessiontimeout(); if (sessiontimeout) {/ / if the session duration exceeds, try reissuing trackappend();} If (sessiontimeout) {/ / trigger the app startup event trackAppStart();}}// If it is the first page if (startTimerCount++ == 0) {/ * * start dotting at startup and stop dotting at exit. Two points can be prevented here: * 1. App crashes before onResume, resulting in only startup and no exit; * 2. In the case of multiple processes, only one Dotter will be started; * / timer();            }        }          @Override        public void onActivityResumed(@NonNull Activity activity) {        }         @Override        public void onActivityPaused(@NonNull Activity activity) {        }         @Override        public void onActivityStopped(@NonNull Activity activity)  {/ / stop the timer. For cross process situations, stop the current process's timer starttimercount --; if (starttimercount = = 0) {canceltimer();} startActivityCount = mDatabaseHelper. getActivityCount();             startActivityCount = startActivityCount > 0 ? -- startActivityCount : 0;             mDatabaseHelper. commitActivityCount(startActivityCount);             If (startactivitycount < = 0) {/ / if it is the last page, initiate a countdown to trigger app exit / / the logic of TODO countdown. After the countdown, trigger app exit trackappend();}}@ Override public void onactivitysaveinstancestate (@ nonnull activity, @ nonnull bundle outstate) {} @ override public void onactivitydestroyed (@ nonnull activity) {} / / APP startup event private void trackAppStart() {} is triggered //Trigger the app exit event private void trackAppEnd() {} / / timed dotting records the app exit information private void timer() {} / / cancel timed dotting private void canceltimer() {} private Boolean issessiontimeout() {long currenttime = math.max (System.currentTimeMillis(), 946656000000L);             return Math. abs(currentTime - mDatabaseHelper.getAppEndTime()) > SESSION_ INTERVAL_ TIME;        }    }}

The above example code is only a general implementation of the core logic. For more detailed implementation, please refer to Shence Android SDK[1].

3.3. 3. Advantages and disadvantages

advantage:

1. Reduce frequent tag bit access and improve efficiency;

2. The collection of APP startup and exit events is more accurate.

Disadvantages:

1. Some services are still implemented in the main thread;

2. Although the scheme is optimized, the implementation principle is still complex.

4, Summary

The App startup and exit collection scheme implemented by Shence Android SDK is constantly changing with the deepening understanding of the actual scene. At present, the stable version is used, which has become stable after verification by a large number of customers, and there are few problems related to App startup and exit. Our subsequent optimization for the stable version is to separate the relevant logic of App startup and exit from the main thread to further reduce the impact on the main thread.

The accurate detection of App startup and exit in Android system is a topic worthy of research. You are welcome to participate and communicate together.

reference:

[1]https://github.com/sensorsdata/sa-sdk-android

Source: official account - the technology community of the divine strategy

Keywords: Front-end Big Data Back-end

Added by Derokorian on Fri, 24 Dec 2021 20:33:37 +0200