Practice Sample to Run a Peers' Practice Notebook 1st Issue

Practice Sample to Run A Peers’ Practice Notebook 1st Issue #

Hello, I’m Zhang Shaowen. Today, I want to share with you the “notes” of WeiLu student’s completion of the exercise after the column. The column promises to give away GMTC conference tickets to students who persist in completing the exercise. WeiLu student has won a ticket to the GMTC conference through his own efforts and perseverance.

If you haven’t started exercising yet, I strongly recommend that you spend some time on it, because each exercise sample has been carefully prepared by me and the study committee, in order to give you the opportunity to practice hands-on after learning, helping you digest the knowledge in the column as quickly as possible for your own use.

Hello everyone, I am Wei Lu, from Xi’an. I have been engaged in Android development for nearly 5 years, and currently I am working on smart community-related businesses. I have been persistently writing blogs for more than three years, hoping to share my achievements in work and learning.

Let me talk about my learning method for this column. I will start learning on the day the column is updated, but the difficulty is really not easy. I don’t require myself to understand everything after just reading it once, but whenever I encounter something I don’t understand, I will immediately look up the information and try to have a general understanding of the whole content. Then, during the weekend, I will concentrate on doing the sample exercises, reviewing the content released that week, and recording the results of the exercises in the form of blog posts.

I plan to read and practice the column multiple times after it ends to continuously fill in the gaps and deficiencies. To be honest, I really like the difficulty level of “Android Development Guru Course”. It gives me a sense of accomplishment when I complete the exercise assignments as if I were climbing a mountain. Finally, I hope that students can stick to it together and enjoy the sense of achievement brought by climbing the mountain.


Recently, I have been studying Zhang Shaowen’s “Android Development Guru Course”. The homework after class is not easy. I practiced it in my spare time for the past few days and, combined with the steps provided by the teacher and the experiences shared by other students, I completed the content of the first five lessons.

I have sorted out and summarized the key points, and I am sharing them here hoping to help fellow students who are studying together (of course, I hope everyone can try to solve problems on their own).

Chapter01

In the example, Breakpad is integrated to obtain system information and thread stack information when a Native Crash occurs. Through a simple Native crash capture process, we generate and analyze minidump files to deepen our understanding of the working mechanism of Breakpad in practice.

To run the project directly, follow the steps in README.md.

There is an issue in the middle. The minidump_stackwalker tool provided by the teacher cannot be executed successfully on macOS 10.14 or above because the libstdc++.6.dylib library is missing. So, I downloaded the Breakpad source code and recompiled it.

The crashLog.txt file generated by using the minidump_stackwalker tool to generate stack trace logs based on the minidump file is as follows:

Operating system: Android
                  0.0.0 Linux 4.9.112-perf-gb92eddd #1 SMP PREEMPT Tue Jan 1 21:35:06 CST 2019 aarch64
CPU: arm64  // Note 1
     8 CPUs

GPU: UNKNOWN

Crash reason:  SIGSEGV /SEGV_MAPERR
Crash address: 0x0
Process uptime: not available

Thread 0 (crashed)
 0  libcrash-lib.so + 0x600 // Note 2
     x0 = 0x00000078e0ce8460    x1 = 0x0000007fd4000314
     x2 = 0x0000007fd40003b0    x3 = 0x00000078e0237134
     x4 = 0x0000007fd40005d0    x5 = 0x00000078dca14200
     x6 = 0x0000007fd4000160    x7 = 0x00000078c8987e18
     x8 = 0x0000000000000000    x9 = 0x0000000000000001
    x10 = 0x0000000000430000   x11 = 0x00000078e05ef688
    x12 = 0x00000079664ab050   x13 = 0x0ad046ab5a65bfdf
x14 = 0x000000796650c000   x15 = 0xffffffffffffffff
x16 = 0x00000078c83defe8   x17 = 0x00000078c83ce5ec
x18 = 0x0000000000000001   x19 = 0x00000078e0c14c00
x20 = 0x0000000000000000   x21 = 0x00000078e0c14c00
x22 = 0x0000007fd40005e0   x23 = 0x00000078c89fa661
x24 = 0x0000000000000004   x25 = 0x00000079666cc5e0
x26 = 0x00000078e0c14ca0   x27 = 0x0000000000000001
x28 = 0x0000007fd4000310    fp = 0x0000007fd40002e0
 lr = 0x00000078c83ce624    sp = 0x0000007fd40002c0
 pc = 0x00000078c83ce600
Found by: given as instruction pointer in context
1  libcrash-lib.so + 0x620
    fp = 0x0000007fd4000310    lr = 0x00000078e051c7e4
    sp = 0x0000007fd40002f0    pc = 0x00000078c83ce624
Found by: previous frame's frame pointer
2  libart.so + 0x55f7e0
    fp = 0x130c0cf800000001    lr = 0x00000079666cc5e0
    sp = 0x0000007fd4000320    pc = 0x00000078e051c7e4
Found by: previous frame's frame pointer
......

Next is symbol resolution, and `addr2line` provided by NDK can be used to reverse symbolize addresses. This tool can be found in `$NDK_HOME/toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64/bin/arm-linux-androideabi-addr2line`.

Note: Pay attention to the platform. If it is an ARM 64-bit .so file, the analysis needs to use the toolchain under `aarch64-linux-android-4.9`.

Since my .so file is ARM 64-bit, I use `aarch64-linux-android-4.9`. The `libcrash-lib.so` file is located in `app/build/intermediates/cmake/debug/obj/arm64-v8a`, and `0x600` is the error position symbol.

aarch64-linux-android-addr2line -f -C -e libcrash-lib.so 0x600


The output is as follows:

Crash() /Users/weilu/Downloads/Chapter01-master/sample/.externalNativeBuild/cmake/debug/arm64-v8a/../../../../src/main/cpp/crash.cpp:10


As you can see, the output is consistent with the error position in the image (line 10).

![](../images/355d9e91e5d243e89dba7d5e3394463e.jpg)

[**Chapter02**](https://github.com/AndroidAdvanceWithGeektime/Chapter02)

> This example mainly demonstrates how to reduce the triggering of TimeoutException by disabling FinalizerWatchdogDaemon.

Please refer to my previous blog post: [Strange Problems Encountered in Android Development (Part 3)](https://blog.csdn.net/qq_17766199/article/details/84789495#t1) for more details, so I won't repeat them here.
[**Chapter03**](https://github.com/AndroidAdvanceWithGeektime/Chapter03)

> The project uses Inline Hook to intercept the RecordAllocation function when allocating memory objects. By intercepting this interface, we can quickly obtain the class name of the allocated object and the allocated memory size.
> 
> During initialization, we set a maximum value for the number of allocated objects. If the number of object allocations exceeds the maximum value starting from `start`, a memory dump will be triggered, and then the `alloc` object list will be cleared and recalculated. This functionality is similar to the Allocation Tracker in Android Studio, but it allows for more granular control at the code level. It can be controlled at the method level.

After running the project directly, click on "Start Recording", and then click on the "Generate 1000 Objects" button 5 times. The code for generating objects is as follows:

```java
for (int i = 0; i < 1000; i++) {
     Message msg = new Message();
     msg.what = i;
}

Since the code starts recording from the “Start Recording” click, when it triggers 5000 data, it will be dumped into a file under sdcard/crashDump. After clicking 5 times, a file named with a timestamp will be generated under the project root directory. Call the following command:

java -jar tools/DumpPrinter-1.0.jar dump_file_path > dump_log.txt

Then you can see the parsed data in dump_log.txt:

Found 5000 records:
....
tid=4509 android.graphics.drawable.RippleForeground (112 bytes)
    android.graphics.drawable.RippleDrawable.tryRippleEnter (RippleDrawable.java:569)
    android.graphics.drawable.RippleDrawable.setRippleActive (RippleDrawable.java:276)
    android.graphics.drawable.RippleDrawable.onStateChange (RippleDrawable.java:266)
    android.graphics.drawable.Drawable.setState (Drawable.java:778)
    android.view.View.drawableStateChanged (View.java:21137)
    android.widget.TextView.drawableStateChanged (TextView.java:5289)
    android.support.v7.widget.AppCompatButton.drawableStateChanged (AppCompatButton.java:155)
    android.view.View.refreshDrawableState (View.java:21214)
    android.view.View.setPressed (View.java:10583)
    android.view.View.setPressed (View.java:10561)
    android.view.View.onTouchEvent (View.java:13865)
    android.widget.TextView.onTouchEvent (TextView.java:10070)
    android.view.View.dispatchTouchEvent (View.java:12533)
    android.view.ViewGroup.dispatchTransformedTouchEvent (ViewGroup.java:3032)
    android.view.ViewGroup.dispatchTouchEvent (ViewGroup.java:2662)
    android.view.ViewGroup.dispatchTransformedTouchEvent (ViewGroup.java:3032)
tid=4515 int[] (104 bytes)
tid=4509 android.os.BaseLooper$MessageMonitorInfo (88 bytes)
    android.os.Message.<init> (Message.java:123)
    com.dodola.alloctrack.MainActivity$4.onClick (MainActivity.java:70)
    android.view.View.performClick (View.java:6614)
    android.view.View.performClickInternal (View.java:6591)
    android.view.View.access$3100 (View.java:786)
    android.view.View$PerformClick.run (View.java:25948)
    android.os.Handler.handleCallback (Handler.java:873)
    android.os.Handler.dispatchMessage (Handler.java:99)
    android.os.Looper.loop (Looper.java:201)
    android.app.ActivityThread.main (ActivityThread.java:6806)
    java.lang.reflect.Method.invoke (Native method)
    com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:547)
    com.android.internal.os.ZygoteInit.main (ZygoteInit.java:873)
......

Let’s use the Android Profiler to compare a Message object, it’s exactly the same.

Let’s take a quick look at the Hook code:

void hookFunc() {
    LOGI("start hookFunc");
    void *handle = ndk_dlopen("libart.so", RTLD_LAZY | RTLD_GLOBAL);

    if (!handle) {
        LOGE("libart.so open fail");
        return;
    }
    void *hookRecordAllocation26 = ndk_dlsym(handle,
                                             "_ZN3art2gc20AllocRecordObjectMap16RecordAllocationEPNS_6ThreadEPNS_6ObjPtrINS_6mirror6ObjectEEEj");

    void *hookRecordAllocation24 = ndk_dlsym(handle,
                                             "_ZN3art2gc20AllocRecordObjectMap16RecordAllocationEPNS_6ThreadEPPNS_6mirror6ObjectEj");

    void *hookRecordAllocation23 = ndk_dlsym(handle,
                                             "_ZN3art3Dbg16RecordAllocationEPNS_6ThreadEPNS_6mirror5ClassEj");

    void *hookRecordAllocation22 = ndk_dlsym(handle,
                                             "_ZN3art3Dbg16RecordAllocationEPNS_6mirror5ClassEj");

    if (hookRecordAllocation26 != nullptr) {
        LOGI("Finish get symbol26");
        MSHookFunction(hookRecordAllocation26, (void *) &newArtRecordAllocation26,
                       (void **) &oldArtRecordAllocation26);

    } else if (hookRecordAllocation24 != nullptr) {
        LOGI("Finish get symbol24");
        MSHookFunction(hookRecordAllocation26, (void *) &newArtRecordAllocation26,
                       (void **) &oldArtRecordAllocation26);

} else if (hookRecordAllocation23 != NULL) { LOGI(“Finish get symbol23”); MSHookFunction(hookRecordAllocation23, (void *) &newArtRecordAllocation23, (void **) &oldArtRecordAllocation23); } else { LOGI(“Finish get symbol22”); if (hookRecordAllocation22 == NULL) { LOGI(“error find hookRecordAllocation22”); return; } else { MSHookFunction(hookRecordAllocation22, (void *) &newArtRecordAllocation22, (void **) &oldArtRecordAllocation22); } } dlclose(handle); #

Hook the RecordAllocation function in libart.so when using the Inline Hook scheme Substrate. First, if we want to hook a function, we need to know the address of this function. And we also see from the code that this address is checked against four different systems. There is a web-based analysis tool (http://demangler.com/) that allows us to quickly get this information. Let’s take Android 8.0 as an example.

I managed to find the corresponding method in the Android 8.0 source code:

The method of Android 7.0 is obviously different:

I also checked the code of Android 9.0 and found no changes, so it should work on my test device running Android 9.0.

The code then hooks the new memory object allocation processing code:

static bool newArtRecordAllocationDoing24(Class *type, size_t byte_count) {

    allocObjectCount++;
    // Get the class name based on the class
    char *typeName = GetDescriptor(type, &a);
    // Reach the max
    if (allocObjectCount > setAllocRecordMax) {
        CMyLock lock(g_Lock); // Lock is needed here because we don't know which thread the object is allocated in, and not locking will cause repeated dumping
        allocObjectCount = 0;

        // Convert the objects in the `alloc` into byte data and dump them
        jbyteArray allocData = getARTAllocationData();
        // Write the alloc data to the file
        SaveAllocationData saveData{allocData};
        saveARTAllocationData(saveData);
        resetARTAllocRecord();
        LOGI("===========CLEAR ALLOC MAPS=============");

        lock.Unlock();
    }
    return true;
}
}
for (ClassObj clazz : bitmapClasses) {
    // Get all instances of Bitmap from the heap
    List<Instance> bitmapInstances = clazz.getHeapInstances(heap.getId());
    // Get the buffer array, width, and height information from the Bitmap instances
    ArrayInstance buffer = HahaHelper.fieldValue(((ClassInstance) bitmapInstance).getValues(), "mBuffer");
    int bitmapHeight = fieldValue(bitmapInstance, "mHeight");
    int bitmapWidth = fieldValue(bitmapInstance, "mWidth");
    // Reference chain information
    while (bitmapInstance.getNextInstanceToGcRoot() != null) {
        print(instance.getNextInstanceToGcRoot());
        instance = instance.getNextInstanceToGcRoot();
    }
    // Duplicate check based on hashcode

}
}

The final output is:

Let’s compare this with the hprof file opened in Studio:

We can see that the information is exactly the same. For a more optimized processing of reference chain information, you can refer to the implementation in the LeakCanary source code.

I have packaged the code above into a JAR file, which can be called directly:

// Call the method:
java -jar tools/DuplicatedBitmapAnalyzer-1.0.jar /path/to/hprof/file

I have submitted the detailed code to Github for your reference.

Chapter05

Attempt to mimic ProcessCpuTracker.java to obtain the time-consuming percentage of each thread over a period of time.

usage: CPU usage 5000ms(from 23:23:33.000 to 23:23:38.000):
 System TOTAL: 2.1% user + 16% kernel + 9.2% iowait + 0.2% irq + 0.1% softirq + 72% idle
 CPU Core: 8
 Load Average: 8.74 / 7.74 / 7.36

 Process:com.sample.app 
   50% 23468/com.sample.app(S): 11% user + 38% kernel faults:4965

 Threads:
   43% 23493/singleThread(R): 6.5% user + 36% kernel faults:3094
   3.2% 23485/RenderThread(S): 2.1% user + 1% kernel faults:329
   0.3% 23468/.sample.app(S): 0.3% user + 0% kernel faults:6
   0.3% 23479/HeapTaskDaemon(S): 0.3% user + 0% kernel faults:982
  ...

I was a bit confused when looking at this because I don’t know much about Linux. Fortunately, my classmate Sun Pengfei clarified the related issues, and I understood the information above and learned some Linux knowledge.

private void testIO() {
    Thread thread = new Thread(new Runnable() {
        @Override
        public void run() {
            File f = new File(getFilesDir(), "aee.txt");
            FileOutputStream fos = new FileOutputStream(f);
            byte[] data = new byte[1024 * 4 * 3000]; // Allocate a byte array of 12MB

            for (int i = 0; i < 30; i++) {
                Arrays.fill(data, (byte) i);
                fos.write(data);
            }
            fos.flush();
            fos.close();
        }
    });
    thread.setName("SingleThread");
    thread.start();
}

The above code is the culprit that causes the problem. The intensive I/O operations are concentrated in the SingleThread thread, causing 3094 faults and 36% kernel usage, without fully utilizing the 8-core CPU.

Finally, by monitoring CPU usage, we can better avoid lag and prevent ANR.

It took me two or three days, and it was far from smooth as I originally thought. I felt completely drained. I encountered some difficulties along the way, and although I didn’t dive deep into the implementation, the experience gained during the process was rewarding. So we can’t give up just because it’s difficult. Let’s start with what we can handle and get moving!

References