background

A buried module is an unreachable part of a complete system, whether mobile, Web, or back end (the back end may prefer to be called a logging system). Of course, there are many third-party embedded SDKS, such as Umeng, that are easy to access and only require a few lines of code to use. But most are invasive, that is to say, where each need buried point manually add code, this coupling is too big, although can through the way of secondary packaging, reduce the dependence on the SDK, but buried point is still significant statistical module coupling, in order to solve this problem, we can pass a buried point scheme to realize the data collection process.

Buried point system type

At present, buried point system is mainly divided into two types: intrusive and no buried point. There is a visualization of the buried point scheme, can be considered as a buried point, but the process of setting the buried point configuration information made of visualization.

Intrusive buried point scheme

Add the code manually in each place that needs to be buried, the advantage is that the buried point is accurate, the disadvantage is also obvious, the code coupling degree is high, it is difficult to maintain later, and the buried point that is not needed needs to be manually deleted.

No buried point scheme

The no-buried method is an implementation scheme to add buried points through global listening or AOP technology. Developers do not need to add code in every place where buried points are needed, but only need to obtain the corresponding buried point data according to the configuration of server distribution. On the one hand, the code coupling degree is low, but also high flexibility, buried point data directly controlled by the server. The disadvantage is that it is less precise than the intrusive embedding.

Data to be collected

Buried point is mainly used for statistics, for buried point system, at least need to collect the following data:

  • The new device information of the APP used for the first time (precise control still needs the cooperation of the back end);
  • Page duration;
  • View interaction events (click, slide, etc.);
  • Various data to assist operation (channel number, geographic location, equipment information, etc.)

Introduction to buried point system

A complete buried point system should contain at least three modules:

Network module

Responsible for obtaining configuration information from the server and uploading buried data;

Storage module

Cache buried point configuration information and save the generated buried point data;

Core processing module

Responsible for collecting buried data and storing it in the storage module. Upload data at the specified time according to the configuration.

Working principle of unburied point system

When the APP starts, initialize the SDK without buried point. During initialization, the system will first request the buried point configuration information from the URL set in the configuration, and then monitor the Activity, Fragment, and View globally. When corresponding events occur, the system will compare with the configuration information. You can save the events to be collected in the database, obtain the data from the database, and upload the data to the server. After the upload is successful, delete the uploaded content from the database.

The realization of no buried point system

The main goal of the unburied system is to reduce the developer’s participation in the buried process. The core of the unburied system is how to globally listen for events and how to generate the buried configuration list.

Listen for page duration

Android application page, also Activity,Fragment two kinds. For activities, the system has a global lifecycle listening method, just record the page display time in onResume, calculate the display time in onPause, and add the duration event to the database in onDestroy:

application.registerActivityLifecycleCallbacks(new Application.ActivityLifecycleCallbacks() { private Map<Context, Long> durationMap = new WeakHashMap<>(); private Map<Context, Long> resumeTimeMap = new WeakHashMap<>(); @Override public void onActivityCreated(Activity activity, Bundle bundle) { durationMap.put(activity, 0L); } @Override public void onActivityResumed(Activity activity) { resumeTimeMap.put(activity, System.currentTimeMillis()); } @Override public void onActivityPaused(Activity activity) { durationMap.put(activity, durationMap.get(activity) + (System.currentTimeMillis() - resumeTimeMap.get(activity))); } @Override public void onActivityDestroyed(Activity activity) { long duration = durationMap.get(activity); If (duration > 0) {// Add events to the database} resumeTimemap.remove (activity); durationMap.remove(activity); } // Other lifecycle methods});Copy the code

For fragments, although the com.app package does not provide global listening for the life cycle, the V4 package after 25.1.0 provides global listening. Considering that the V4 package is usually used, So the method provided in the V4 package is used directly to implement page-duration listening.

FragmentManager fm = getSupportFragmentManager(); fm.registerFragmentLifecycleCallbacks(new FragmentManager.FragmentLifecycleCallbacks() { private Map<Fragment, Long> resumeTimeMap = new WeakHashMap<>(); private Map<Fragment, Long> durationMap = new WeakHashMap<>(); @Override public void onFragmentAttached(@NonNull FragmentManager fm, @NonNull Fragment f, @NonNull Context context) { super.onFragmentAttached(fm, f, context); resumeTimeMap.put(f, 0L); } @Override public void onFragmentResumed(@NonNull FragmentManager fm, @NonNull Fragment f) { super.onFragmentResumed(fm, f); resumeTimeMap.put(f, System.currentTimeMillis()); } @Override public void onFragmentPaused(@NonNull FragmentManager fm, @NonNull Fragment f) { super.onFragmentPaused(fm, f); durationMap.put(f, durationMap.get(f) + System.currentTimeMillis() - resumeTimeMap.get(f)); } @Override public void onFragmentDetached(@NonNull FragmentManager fm, @NonNull Fragment f) { super.onFragmentDetached(fm, f); long duration = durationMap.get(f); If (duration > 0) {// Add events to database} resumeTimemap.remove (f); durationMap.remove(f); } }, true);Copy the code

The code above only listens for the Fragment lifecycle, but the visibility of the Fragment does not always correspond to the lifecycle, for example: Fragment show/hide or ViewPager methods in the Fragment lifecycle are not always executed when switching, so you also need to listen for onHiddenChanged and setUserVisibleHint that correspond to both cases. However, the global listening provided in the two sides V4 packages is not present, so it needs special handling. There are two solutions:

  • Provide a LifycycleFragment that listens for the onHiddenChanged and setUserVisibleHint methods. The business layer Fragment inherits this Fragment.
  • Using AOP, listen for onHiddenChanged and setUserVisibleHint;

The processing logic is the same as in onResume and onPause, refer to the source code below.

If you want to implement global listening for fragments in com.app packages for the life cycle, you can use the following two methods:

  • Write a LifycycleFragment that implements life-cycle listening and is inherited by the business layer’s Fragment implementation.
  • Use transparent fragments. Transparent fragments have no UI and have the same life cycle as the current Fragment.

Since a Fragment is always dependent on an Activity, its scope of listening is also activity-level. Set the Fragment to listen for in the onCreate section of the Activity.

Listen for the View click event

View click events can be monitored in two ways:

AOP based listening onClick method;

Here we use Aspect as an example to implement global listening on onClick:

@Aspect public class ViewClickedEventAspect { @After("execution(* android.view.View.OnClickListener.onClick(android.view.View))") public void viewClicked(final ProceedingJoinPoint JoinPoint) {/** * Save click event */}}Copy the code
With setAccessibilityDelegate:

SetAccessibilityDelegate: View click event: setAccessibilityDelegate

public boolean performClick() { // We still need to call this method to handle the cases where performClick() was called  // externally, instead of through performClickInternal() notifyAutofillManagerOnClick(); final boolean result; final ListenerInfo li = mListenerInfo; if (li ! = null && li.mOnClickListener ! = null) { playSoundEffect(SoundEffectConstants.CLICK); li.mOnClickListener.onClick(this); result = true; } else { result = false; } sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); notifyEnterOrExitForAutoFillIfNeeded(true); return result; }Copy the code

SendAccessibilityEvent = sendAccessibilityEvent = sendAccessibilityEvent = sendAccessibilityEvent

public void sendAccessibilityEvent(int eventType) {
    if (mAccessibilityDelegate != null) {
        mAccessibilityDelegate.sendAccessibilityEvent(this, eventType);
    } else {
        sendAccessibilityEventInternal(eventType);
    }
}
Copy the code

As you can see from the code, all we need to do is set the mAccessibilityDelegate for the View to listen for the View’s onClick event. The View mAccessibilityDelegate method happens to be public, so we can use this method to listen for the View click event. The core code is as follows:

Public class ViewClickedEventListener extends the AccessibilityDelegate {/ * * * set the Activity in the page View of events to monitor * @ param activity */ public void setActivityTracker(Activity activity) { View contentView = activity.findViewById(android.R.id.content); if (contentView ! = null) { setViewClickedTracker(contentView, null); }} @param Fragment */ public void setFragmentTracker(Fragment Fragment) {View contentView = fragment.getView(); if (contentView ! = null) { setViewClickedTracker(contentView, fragment); } } private void setViewClickedTracker(View view, Fragment fragment) { if (needTracker(view)) { if (fragment ! = null) { view.setTag(FRAGMENT_TAG_KEY, fragment); } view.setAccessibilityDelegate(this); } if (view instanceof ViewGroup) { int childCount = ((ViewGroup) view).getChildCount(); for (int i = 0; i < childCount; i++) { setViewClickedTracker(((ViewGroup) view).getChildAt(i), fragment); } } } @Override public void sendAccessibilityEvent(View host, int eventType) { super.sendAccessibilityEvent(host, eventType); if (AccessibilityEvent.TYPE_VIEW_CLICKED == eventType && host ! = null) {// Add events to database}}}Copy the code

Then add a View listener to the onResume of the Activity and Fragment.

Generate buried point configuration information

The global monitoring of events has been implemented. Theoretically, APP developers do not need to participate in the process of burying points, but the background statistics do not need all the data, so the collection of burying point configuration information needs to be added here. Provides a buried point data real-time uploaded here, in front of the APP online, amend the data upload strategy into real-time uploaded, you can send all the event information through a Socket to the background, and then will need to import to the buried point configuration information in the list, the APP after launch, will get buried point configuration information from the server, and after the generated data, Save the required data based on the obtained configuration information and submit the data to the server at the specified upload time.

use

Initialize the Application in onCreate:

TrackerConfiguration configuration = new TrackerConfiguration() .openLog(true) .setuploadcategory (Constants.upload_category.real_time.getValue ()).setConfigURL ("http://m.baidu.com") // URL of buried configuration information .sethostName ("127.0.0.1") // IP address and port for receiving real-time buried data.sethostPort (10001).setNewDeviceurl ("http://m.baidu.com") // URL for saving new device information .setUploadUrl("http://m.baidu.com"); // Save the buried data URL tracker.getInstance ().init(this, configuration);Copy the code

Before the release, set the upload policy to Constants.UPLOAD_CATEGORY.REAL_TIME To collect the buried configuration information and change the data upload policy to another one when the APP goes live to avoid power consumption.

For the upload of buried data, the following policies are provided:

REAL_TIME(0), // Real-time transmission, NEXT_LAUNCH(-1), // Upload NEXT_15_MINUTER(15) at next startup, // Upload NEXT_30_MINUTER(30) every 15 minutes, // Upload every 30 minutes NEXT_KNOWN_MINUTER(-1); // Use the upload policy delivered by the server (interval determined by the server)Copy the code

instructions

At present, this SDK only integrates the information of the new device, the stay events of the page (Activity/Fragment) and the statistics of the click events of the View. Other interactive events have not been integrated, and some details still need to be improved, which will be further improved later.

The source address

Tracker

Refer to the article

Android buried point technology analysis

Android unburied data collection SDK key technology

Netease HubbleData’s Android no-buried practice