Write the original

In Android App development, when there are bugs and crashes, testers will come to the door with their phones and develop operations. When the bugs disappear, testers will have to perform a lot of operations to reproduce them.

We must have encountered such a situation, the more extreme is the online bug, although you can set the crash log to collect the crash log, but how does the user operate, we can only rely on guess

Why not have a tool that records what screens have been opened recently and what buttons have been clicked, and record it locally so developers can view it? So I came up with the idea of writing this tool.

Let’s take a look at some of the features our tool is intended to accomplish

  1. Log when the activity is turned on and off
  2. Records the opening and closing of the fragment
  3. Records the clicks on the control
  4. Store these logs locally for easy upload and reference

With what technology

Given a goal, we can consider how to achieve it. There are probably several ways to achieve operation collection

There’s a name for this technique, and it’s called a burying point, because we’re like planting a mine and we set the code in a specific place, log it and collect it when it’s triggered

  1. Manual burying point
  2. Use special controls that work like 1, but shift the work to the person writing the control
  3. Use an AOP framework for automation burying points, such as AspectJ or ASM
  4. AccessibilityService works with ContentDescription to bury points

Which technology to choose? Let’s do it case by case

  1. Although the most simple, but the workload is large, fault tolerance fault buried leakage buried, give up
  2. Heavy workload, high user learning cost, give up
  3. Simple to use, universal, consider using
  4. Add ContentDescription when developing and some models cannot enable AccessibilityService, which has limitations

We now have two choices, AspectJ or ASM, but because ASM is too flexible to learn and use, there is a certain threshold, so we chose AspectJ which is easy to use

The basic idea

In aspectJ, you can set pointcuts for both the onClick method and the lifecycle method. What is a pointcut

In terms of log, we use the open source project ZLog to record, the framework to achieve the log to file, as well as the number and size of the file management function, more convenient

For the last item, because you can’t capture a specific class using a framework like ItemBinding, you do it as needed

About Aspectj

The most important thing about aspectJ is how to use aspectJ to make buried points. There are many examples of aspectJ on the web, so I don’t need to go into details here. Here is a simple document to use

Class AspectJTest {@pointcut ("execution(void _internalCallbackOnClick(..)); ) ") / / point of contact, Fun onBindingClick() {} @around ("onBindingClick()")// Process pointcuts when appropriate @Throws(Throwable::class) fun onClickMethodBinding(joinPoint: If (args. Size >= 1 && args[1] is View) {val View = args[1] as } joinPoint.proceed() // Proceed with the original code}}Copy the code

Problems that need to be solved

While AspectJ seems simple to use, there are a few issues that need to be addressed

  1. How do I get the control ID of the click event and the name of its class
  2. How do I get the click event in the list and get its location information

On the first point, the setOnClick setting is normally available, but when using techniques such as databinding, the situation is more complicated. If we use the following code, we will get an onclick file in the generated class, and we do not know which page control was clicked

@Around("onClick()") @Throws(Throwable::class) fun onClickMethodAround(joinPoint: Bool target = joinPoint. Target var className = "" If (target! = null) { className = target.javaClass.name if (className.contains("$")) { className = className.split("\\$").toTypedArray()[0] } if (className.contains("_ViewBinding")) { className = Split ("_ViewBinding").toTypeDarray ()[0]}} classname.split ("_ViewBinding").toTypeDarray ()[0]} Joinpoint.proceed () // Proceed with the original code}Copy the code

To handle this, we need to listen for the click event in the generated class. In my case, this event method is called _internalCallbackOnClick, and we just need to add a pointcut to it

For problem 2, we can obtain the parent view of the view, and determine its type, and then strong to solve the problem, the code is as follows

  val view = args[0] as View
                var index = -1
                if (view.parent is RecyclerView) {
                    index = (view.parent as RecyclerView).getChildPosition(view)
                }
Copy the code

The output

Once we have buried the site, we can export the logs to a file. Here I used a library called ZLog, but since it is a few years old, I copied the files directly into the project. If you’re interested, go to the ZLog repository and click star

Summary with complete code

After solving the above problems, we can detect the information we want and save it, which will give us a little more clues for bug detection when the test comes to us in the future. When the online users report bugs, they don’t have to make random guesses. Isn’t it very useful? (Probably not

If you want to see the full code, you can check it out here on Github and if you have any questions, you can leave a comment