ANR monitoring solution for Android client

Author: leafjia

ANR monitoring is a very old-fashioned topic, but the ANR monitoring tools on the market are either not ANR monitoring in the true sense (but 5-second freeze monitoring); or they are not perfect, and they cannot monitor all ANR. If you want to get a perfect ANR monitoring tool, you must first understand the entire ANR process of the system. This paper analyzes the main process of ANR, and gives a perfect ANR monitoring scheme. The solution has been fully verified on the Android WeChat client and has been running stably for more than a year.

We know that the ANR process is basically completed in the system_server system process. It is difficult for us to monitor and change the behavior of the system process. If we want to monitor ANR, we must find out whether the system process interacts with our own application process. If so, the two interact Where is the boundary and the behavior of the application side on the boundary is something we can monitor more easily . To find this boundary, we must understand the ANR process.

1. ANR process

No matter where the source of ANR is, it will eventually go to appNotResponding in ProcessRecord . This method includes the main process of ANR, so it is relatively long. We find some key logic to analyze: frameworks/base/services/core/java/ com/android/server/am/ProcessRecord.java:

void appNotResponding(String activityShortComponentName, ApplicationInfo aInfo,
    String parentShortComponentName, WindowProcessController parentProcess,
    boolean aboveSystem, String annotation, boolean onlyDumpSelf) {
    
    //......

    final boolean isSilentAnr;
    synchronized (mService) {
    // PowerManager.reboot() can block for a long time, so ignore ANRs while shutting down.
    if (mService.mAtmInternal.isShuttingDown()) {
        Slog.i(TAG, "During shutdown skipping ANR: " + this + " " + annotation);
        return;
    } else if (isNotResponding()) {
        Slog.i(TAG, "Skipping duplicate ANR: " + this + " " + annotation);
        return;
    } else if (isCrashing()) {
        Slog.i(TAG, "Crashing app skipping ANR: " + this + " " + annotation);
        return;
    } else if (killedByAm) {
        Slog.i(TAG, "App already killed by AM skipping ANR: " + this + " " + annotation);
        return;
    } else if (killed) {
        Slog.i(TAG, "Skipping died app ANR: " + this + " " + annotation);
        return;
    }

    // In case we come through here for the same app before completing
    // this one, mark as anring now so we will bail out.
    setNotResponding(true);

    // Log the ANR to the event log.
    EventLog.writeEvent(EventLogTags.AM_ANR, userId, pid, processName, info.flags,
            annotation);

    // Dump thread traces as quickly as we can, starting with "interesting" processes.
    firstPids.add(pid);

    // Don't dump other PIDs if it's a background ANR or is requested to only dump self.
    isSilentAnr = isSilentAnr();

    //......
}

First, there is a long list of if else, which gives several extreme situations, which will return directly without generating an ANR. These situations include: the process is in the state of being shut down, the state of being crashed, the state of being killed, Or the same process is already in the process of ANR.

Another very important logic is to judge whether the current ANR is a SilentAnr. The so-called "silent ANR" is actually the background ANR . Kill the process directly . The principle of judging the front and back ANR is: **If the process of ANR is perceived by the user, it will be considered as the front ANR, otherwise it is the background ANR. **In addition, if "Show background ANR" is checked in the developer options, all ANRs will be considered as foreground ANRs.

We continue to analyze this method:


if (!isSilentAnr && !onlyDumpSelf) {
    int parentPid = pid;
    if (parentProcess != null && parentProcess.getPid() > 0) {
        parentPid = parentProcess.getPid();
    }
    if (parentPid != pid) firstPids.add(parentPid);

    if (MY_PID != pid && MY_PID != parentPid) firstPids.add(MY_PID);

    for (int i = getLruProcessList().size() - 1; i >= 0; i--) {
        ProcessRecord r = getLruProcessList().get(i);
        if (r != null && r.thread != null) {
            int myPid = r.pid;
            if (myPid > 0 && myPid != pid && myPid != parentPid && myPid != MY_PID) {
                if (r.isPersistent()) {
                    firstPids.add(myPid);
                    if (DEBUG_ANR) Slog.i(TAG, "Adding persistent proc: " + r);
                } else if (r.treatLikeActivity) {
                    firstPids.add(myPid);
                    if (DEBUG_ANR) Slog.i(TAG, "Adding likely IME: " + r);
                } else {
                    lastPids.put(myPid, Boolean.TRUE);
                    if (DEBUG_ANR) Slog.i(TAG, "Adding ANR proc: " + r);
                }
            }
        }
    }
}

//......

// don't dump native PIDs for background ANRs unless it is the process of interest
String[] nativeProcs = null;
if (isSilentAnr || onlyDumpSelf) {
    for (int i = 0; i < NATIVE_STACKS_OF_INTEREST.length; i++) {
        if (NATIVE_STACKS_OF_INTEREST[i].equals(processName)) {
            nativeProcs = new String[] { processName };
            break;
        }
    }
} else {
    nativeProcs = NATIVE_STACKS_OF_INTEREST;
}

int[] pids = nativeProcs == null ? null : Process.getPidsForCommands(nativeProcs);
ArrayList<Integer> nativePids = null;

if (pids != null) {
    nativePids = new ArrayList<>(pids.length);
    for (int i : pids) {
        nativePids.add(i);
    }
}

After an ANR occurs, in order to let the developer know the reason of the ANR and locate the problem conveniently, a lot of information will be dumped into the ANR Trace file. The above logic is to select the process that needs to be dumped. The ANR Trace file contains the Trace information of many processes, because the reason for ANR may be that other processes seize too many resources, or the IPC gets stuck when going to other processes (especially system processes) .

Selecting the process that needs to be dumped is a very interesting logic. Let’s analyze it a little bit: the process that needs to be dumped is divided into three categories: firstPids, nativePids, and extraPids:

  • firstPIds : firstPids is an important process that needs to be dumped first. The process that occurs ANR must be dumped anyway, and it is also dumped first, so the first one is added to firstPids. If it is SilentAnr (that is, background ANR), there is no need to join any other processes. If not, you need to further add other processes: If the process where the ANR occurs is not the system_server process, you need to add the system_server process; then poll an LRU process list maintained by AMS, if the most recently accessed process contains a persistent process, or Processes with the BIND_TREAT_LIKE_ACTVITY label are added to firstPids.

  • extraPids : Other processes in the LRU process list will be added to lastPids first, and then lastPids will be further selected from the process with high CPU usage recently to further form extraPids;

  • nativePids : nativePids is the simplest, it is some fixed native system processes, defined in WatchDog.java.

After getting the pids of all processes that need to be dumped, AMS starts to dump the stacks of these processes in the order of firstPids, nativePids, and extraPids:

File tracesFile = ActivityManagerService.dumpStackTraces(firstPids,
        isSilentAnr ? null : processCpuTracker, isSilentAnr ? null : lastPids,
        nativePids, tracesFileException, offsets);

This is also the place we need to focus on analysis. Let's continue to see what has been done here and follow it into AMS.

frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

public static Pair<Long, Long> dumpStackTraces(String tracesFile, ArrayList<Integer> firstPids,
        ArrayList<Integer> nativePids, ArrayList<Integer> extraPids) {

    long remainingTime = 20 * 1000;
    
    //......

    if (firstPids != null) {
        int num = firstPids.size();
        for (int i = 0; i < num; i++) {
            //......
            final long timeTaken = dumpJavaTracesTombstoned(pid, tracesFile,
                                                            remainingTime);

            remainingTime -= timeTaken;
            if (remainingTime <= 0) {
                Slog.e(TAG, "Aborting stack trace dump (current firstPid=" + pid
                        + "); deadline exceeded.");
                return firstPidStart >= 0 ? new Pair<>(firstPidStart, firstPidEnd) : null;
            }
            //......
        }
    }

    //......
}

We first pay attention to remainingTime , which is an important variable that specifies the maximum time for us to dump all processes, because dumping the stacks of all threads in a process is a heavy operation in itself, not to mention dumping many processes, so it is stipulated that ANR occurs Afterwards, the total time of dumping all processes cannot exceed 20 seconds . If it exceeds, return immediately to ensure that the ANR pop-up window can pop up in time (or be killed). We continue to follow dumpJavaTracesTombstoned

private static long dumpJavaTracesTombstoned(int pid, String fileName, long timeoutMs) {
    final long timeStart = SystemClock.elapsedRealtime();
    boolean javaSuccess = Debug.dumpJavaBacktraceToFileTimeout(pid, fileName,
            (int) (timeoutMs / 1000));
    //......
    return SystemClock.elapsedRealtime() - timeStart;
}

Then go all the way to system/core/debuggerd/client/debuggerd_client.cpp where the native layer is responsible for the dump stack :

bool debuggerd_trigger_dump(pid_t tid, DebuggerdDumpType dump_type, unsigned int timeout_ms, unique_fd output_fd) {
    pid_t pid = tid;
    //......

    // Send the signal.
    const int signal = (dump_type == kDebuggerdJavaBacktrace) ? SIGQUIT : BIONIC_SIGNAL_DEBUGGER;
    sigval val = {.sival_int = (dump_type == kDebuggerdNativeBacktrace) ? 1 : 0};
    if (sigqueue(pid, signal, val) != 0) {
      log_error(output_fd, errno, "failed to send signal to pid %d", pid);
      return false;
    }
    //......
    LOG(INFO) << TAG "done dumping process " << pid;
    return true;
}

**I'm coming! The interactive boundary mentioned before has finally been found! **Here, the SIGQUIT signal, which is the signal 3 signal, will be sent to the process that needs to dump the stack through sigqueue , and the process that has ANR will definitely be dumped, and it is also the first to be dumped. This means that as long as we can monitor the SIGQUIT signal sent by the system, we may be able to monitor the occurrence of ANR.

Each application process will have a SignalCatcher thread dedicated to handling SIGQUIT , come to art/runtime/signal_catcher.cc :


void* SignalCatcher::Run(void* arg) {
    //......
    // Set up mask with signals we want to handle.
    SignalSet signals;
    signals.Add(SIGQUIT);
    signals.Add(SIGUSR1);
    while (true) {
        int signal_number = signal_catcher->WaitForSignal(self, signals);
        if (signal_catcher->ShouldHalt()) {
            runtime->DetachCurrentThread();
            return nullptr;
        }
        switch (signal_number) {
            case SIGQUIT:
                  signal_catcher->HandleSigQuit();
                  break;
            case SIGUSR1:
                  signal_catcher->HandleSigUsr1();
                  break;
            default:
                  LOG(ERROR) << "Unexpected signal %d" << signal_number;
                  break;
        }
    }
}

The WaitForSignal method calls the sigwait method, which is a blocking method. The infinite loop here will keep waiting to monitor the arrival of the two signals SIGQUIT and SIGUSR1.

Sort out the ANR process: **When an ANR occurs in an application, the system will collect many processes and dump the stack to generate an ANR Trace file. The first one collected is the process that will definitely be collected, which is the process that occurred ANR , and then the system starts to send SIGQUIT signal to these application processes, and the application process starts to dump the stack after receiving SIGQUIT. **To simply draw a schematic diagram:

Therefore, in fact, the entire process of ANR occurs in the process, and only the behavior of dump stack will be executed in the process of ANR. This process starts from receiving SIGQUIT (circle 1), ends with using socket to write Trace (circle 2), and then continues to return to the server process to complete the remaining ANR process. We will make a fuss about these two boundaries.

First of all, we will definitely think, can we monitor the SIGQUIT signal sent to us by system_server? If we can, we're halfway there.

Second, monitor the SIGQUIT signal

The Linux system provides two methods of monitoring signals, one is the sigwait method used by the SignalCatcher thread for synchronous and blocking monitoring, and the other is using the sigaction method to register a signal handler for asynchronous monitoring. Let's try them all.

2.1. next

We first try the former method, imitating the SignalCatcher thread, doing exactly the same thing, and listening to SIGQUIT through an infinite loop sigwait :

static void *mySigQuitCatcher(void* args) {
    while (true) {
        int sig;
        sigset_t sigSet;
        sigemptyset(&sigSet);
        sigaddset(&sigSet, SIGQUIT);
        sigwait(&sigSet, &sig);
        if (sig == SIGQUIT) {
            //Got SIGQUIT
        }
    }
}
pthread_t pid;
pthread_create(&pid, nullptr, mySigQuitCatcher, nullptr);
pthread_detach(pid);

At this time, there are two different threads sigwait with the same SIGQUIT. Which one will it go to? We found such a description in the sigwait document ( the sigwait method is implemented by the sigwaitinfo method):

It turns out that when two threads listen to the same signal through the sigwait method, it is uncertain which thread receives the signal . It's okay to be unsure, and of course it doesn't meet our needs.

3.2. Signal Handler

Then let's try another method to see if it is feasible. We can create a Signal Handler through the sigaction method:

void signalHandler(int sig, siginfo_t* info, void* uc) {
    if (sig == SIGQUIT) {
        //Got An ANR
    }
}

struct sigaction sa;
sa.sa_sigaction = signalHandler;
sa.sa_flags = SA_ONSTACK | SA_SIGINFO | SA_RESTART;
sigaction(SIGQUIT, &sa, nullptr);

After establishing the Signal Handler, we found that when there are both sigwait and signal handlers, the signal did not go to our signal handler but was still caught by the system's Signal Catcher thread. What is the reason?

It turns out that Android sets SIGQUIT to BLOCKED by default, so it will only respond to sigwait and will not enter the handler method we set. We set SIGQUIT to UNBLOCK through pthread_sigmask or sigprocmask , then when we receive SIGQUIT again, we will definitely enter our handler method. It needs to be set like this:

sigset_t sigSet;
sigemptyset(&sigSet);
sigaddset(&sigSet, SIGQUIT);
pthread_sigmask(SIG_UNBLOCK, &sigSet, nullptr);

Finally, it should be noted that after we grab SIGQUIT through Signal Handler, sigwait in the original Signal Catcher thread can no longer receive SIGQUIT, and the logic of the original dump stack cannot be completed. We follow the entire logic and process of ANR It turns out that it is exactly the same, and it is necessary to resend a SIGQUIT to the Signal Catcher thread in the Signal Handler :


int tid = getSignalCatcherThreadId(); //遍历/proc/[pid]目录,找到SignalCatcher线程的tid
tgkill(getpid(), tid, SIGQUIT);

(If the step of re-sending SIGQUIT to SignalCatcher is missing, AMS will not wait until the ANR process writes to the stack, and will not be forced to interrupt until the 20-second timeout, and continue the subsequent process. The direct performance is that the ANR pop-up window is very slow (20 seconds timeout), and the complete ANR Trace file cannot be generated normally in the /data/anr directory.)

The above has obtained a relatively complete mechanism for monitoring the SIGQUIT signal without changing the system behavior, which is also the basis for our monitoring of ANR.

3. Perfect ANR monitoring solution

Monitoring the SIGQUIT signal does not mean monitoring the ANR.

3.1. False Positives

Sufficient and non-essential condition 1: A process that has ANR must receive a SIGQUIT signal; but a process that has received a SIGQUIT signal does not necessarily have an ANR.

Consider the following two situations:

  • ANR of other processes : As mentioned above, after an ANR occurs, the ANR process is not the only process that needs to dump the stack. The system will collect many other processes for dump, that is to say, when an application ANR occurs, other It is also possible for an application to receive a SIGQUIT signal. Further, when we monitor SIGQUIT, it may be that we have monitored the ANR ** generated by other processes, resulting in false positives. **

  • Non-ANR sending SIGQUIT : Sending a SIGQUIT signal is actually very easy. Both developers and manufacturers can easily send a SIGQUIT (the java layer calls the android.os.Process.sendSignal method; the Native layer calls the kill or tgkill method) , so we may receive a SIGQUIT signal sent by a non-ANR process, resulting in false positives.

How to solve the problem of these false positives, I return to the beginning of the ANR process:


void appNotResponding(String activityShortComponentName, ApplicationInfo aInfo,
        String parentShortComponentName, WindowProcessController parentProcess,
        boolean aboveSystem, String annotation, boolean onlyDumpSelf) {
    //......
    synchronized (mService) {
        if (isSilentAnr() && !isDebugging()) {
            kill("bg anr", ApplicationExitInfo.REASON_ANR, true);
            return;
        }

        // Set the app's notResponding state, and look up the errorReportReceiver
        makeAppNotRespondingLocked(activityShortComponentName,
                annotation != null ? "ANR " + annotation : "ANR", info.toString());

        // show ANR dialog ......
    }
}

private void makeAppNotRespondingLocked(String activity, String shortMsg, String longMsg) {
    setNotResponding(true);
    // mAppErrors can be null if the AMS is constructed with injector only. This will only
    // happen in tests.
    if (mService.mAppErrors != null) {
        notRespondingReport = mService.mAppErrors.generateProcessError(this,
                ActivityManager.ProcessErrorStateInfo.NOT_RESPONDING,
                activity, shortMsg, longMsg, null);
    }
    startAppProblemLocked();
    getWindowProcessController().stopFreezingActivities();
}

Before the ANR pop-up window, it will be executed in the makeAppNotRespondingLocked method, where a flag of NOT_RESPONDING will be marked for the ANR process. And this flag we can get through ActivityManager:


private static boolean checkErrorState() {
    try {
        Application application = sApplication == null ? Matrix.with().getApplication() : sApplication;
        ActivityManager am = (ActivityManager) application.getSystemService(Context.ACTIVITY_SERVICE);
        List<ActivityManager.ProcessErrorStateInfo> procs = am.getProcessesInErrorState();
        if (procs == null) return false;
        for (ActivityManager.ProcessErrorStateInfo proc : procs) {
            if (proc.pid != android.os.Process.myPid()) continue;
            if (proc.condition != ActivityManager.ProcessErrorStateInfo.NOT_RESPONDING) continue;
            return true;
        }
        return false;
    } catch (Throwable t){
        MatrixLog.e(TAG,"[checkErrorState] error : %s", t.getMessage());
    }
    return false;
}

After monitoring SIGQUIT, we keep polling within 20 seconds (20 seconds is the timeout time of ANR dump) whether there is a NOT_RESPONDING pair flag. Once we find this flag, we can immediately determine that an ANR has occurred.

(You may think, there is such a convenient method, isn’t it superfluous to monitor the SIGQUIT signal? It’s just an endless loop, and the flag is continuously trained in rotation? Yes, it can be done in theory, but it’s too low In addition to efficiency, power consumption and environmental protection, more importantly, the following problems still cannot be solved)

In addition, the second parameter siginfo_t of the Signal Handler callback also contains some useful information. The third field si_code of the structure indicates the method by which the signal is sent, SI_USER indicates that the signal is sent through kill, and SI_QUEUE indicates that the signal is Sent via sigqueue. However, in the ANR process of Android, the high version uses the signal sent by sigqueue, and some low versions use the signal sent by kill, which is not uniform.

The fifth field (the fourth field on very few models) si_pid indicates the pid of the process that sent the signal. A condition that applies to almost all Android versions and models here is: if the process that sends the signal is itself process, then it must not be an ANR. This condition can be used to exclude the situation of sending SIGQUIT by itself, resulting in false positives.

3.2. False negatives

Sufficient and non-essential condition 2: The process is in the state of NOT_RESPONDING, which can confirm that the process has ANR. But the process of ANR is not necessarily set to NOT_RESPONDING state.

Consider the following two situations:

  • **Background ANR (SilentAnr): **Analyzing the ANR process before, we can know that if ANR is marked as background ANR (ie SilentAnr), then it will return directly after killing the process, and will not go to the process error state logic. This means that there is no way to capture background ANR, and the amount of background ANR is also very large, and background ANR will directly kill the process, and the user experience is also very negative. Such a large part of ANR cannot be monitored, of course it cannot accepted.

  • **Flashback ANR:** In addition, we also found that a considerable number of models (such as models with high Android versions of OPPO and VIVO) have modified the ANR process, even if it occurs in the foreground. It will not pop up a window, but directly kill the process, that is, flash back. The number of users covered by this part of the model is also very large. Moreover, it is determined that the two future new devices will always maintain this mechanism.

So we need a way to quickly detect whether we are in ANR state after receiving the SIGQUIT signal, and perform a quick dump and report. It is easy to think that we can judge by whether the main thread is in a stuck state. So how do you know if the main thread is stuck the fastest? In the previous article, when analyzing the Sync Barrier leakage problem, we reflected the mMessage object of the main thread Looper. The when variable of this object indicates the time when the message currently being processed is enqueued. We can subtract the current time from the when variable Time, what you get is the waiting time. If the waiting time is too long, it means that the main thread is stuck . At this time, receiving the SIGQUIT signal basically means that an ANR has indeed occurred:

private static boolean isMainThreadStuck(){
    try {
        MessageQueue mainQueue = Looper.getMainLooper().getQueue();
        Field field = mainQueue.getClass().getDeclaredField("mMessages");
        field.setAccessible(true);
        final Message mMessage = (Message) field.get(mainQueue);
        if (mMessage != null) {
            long when = mMessage.getWhen();
            if(when == 0) {
                return false;
            }
            long time = when - SystemClock.uptimeMillis();
            long timeThreshold = BACKGROUND_MSG_THRESHOLD;
            if (foreground) {
                timeThreshold = FOREGROUND_MSG_THRESHOLD;
            }
            return time < timeThreshold;
        }
    } catch (Exception e){
        return false;
    }
    return false;
}

We use the above mechanisms to comprehensively judge whether an ANR has actually occurred after receiving the SIGQUIT signal, and minimize false positives and false negatives. This is a relatively complete monitoring solution.

3.3. Additional harvest: get ANR Trace

Going back to the ANR flow diagram drawn before, the Signal Catcher thread writes Trace (circle 2) is also a boundary, and writes Trace through the write method of the socket. If we can hook the write here, we can even get the system Dump the ANR Trace content . This content is very comprehensive, including various states, locks and stacks of all threads (including native stacks), which is very useful for us to troubleshoot problems, especially some native problems and deadlocks . Native Hook We adopt the PLT Hook solution, which has been verified on WeChat and its stability is controllable.


int (*original_connect)(int __fd, const struct sockaddr* __addr, socklen_t __addr_length);
int my_connect(int __fd, const struct sockaddr* __addr, socklen_t __addr_length) {
    if (strcmp(__addr->sa_data, "/dev/socket/tombstoned_java_trace") == 0) {
        isTraceWrite = true;
        signalCatcherTid = gettid();
    }
    return original_connect(__fd, __addr, __addr_length);
}

int (*original_open)(const char *pathname, int flags, mode_t mode);
int my_open(const char *pathname, int flags, mode_t mode) {
    if (strcmp(pathname, "/data/anr/traces.txt") == 0) {
        isTraceWrite = true;
        signalCatcherTid = gettid();
    }
    return original_open(pathname, flags, mode);
}

ssize_t (*original_write)(int fd, const void* const __pass_object_size0 buf, size_t count);
ssize_t my_write(int fd, const void* const buf, size_t count) {
    if(isTraceWrite && signalCatcherTid == gettid()) {
        isTraceWrite = false;
        signalCatcherTid = 0;
        char *content = (char *) buf;
        printAnrTrace(content);
    }
    return original_write(fd, buf, count);
}

void hookAnrTraceWrite() {
    int apiLevel = getApiLevel();
    if (apiLevel < 19) {
        return;
    }
    if (apiLevel >= 27) {
        plt_hook("libcutils.so", "connect", (void *) my_connect, (void **) (&original_connect));
    } else {
        plt_hook("libart.so", "open", (void *) my_open, (void **) (&original_open));
    }

    if (apiLevel >= 30 || apiLevel == 25 || apiLevel ==24) {
        plt_hook("libc.so", "write", (void *) my_write, (void **) (&original_write));
    } else if (apiLevel == 29) {
        plt_hook("libbase.so", "write", (void *) my_write, (void **) (&original_write));
    } else {
        plt_hook("libart.so", "write", (void *) my_write, (void **) (&original_write));
    }
}

There are a few points to note:

  • Only Hook the ANR process : In some cases, the connect/open/write methods in the basic library may be called frequently, and we need to minimize the impact of hooks. So we will only hook after receiving the SIGQUIT signal (before resending the SIGQUIT signal to the Signal Catcher), and unhook after the ANR process is over.

  • Only handle the first write after the Signal Catcher thread open/connect : Except for the dump trace process in the Signal Catcher thread, we don't care about the write method called elsewhere, and don't need to deal with it. For example, before the write method, the dump trace process will use the connet method to link a socket whose path is " /dev/socket/tombstoned_java_trace ". We can hook the connect method to get the name of the socket, and we only process After connecting this socket, the first write of the same thread (ie Signal Catcher thread), the content of this write is the only thing we care about.

  • Hook points are different depending on the API Level : the write method that needs to be hooked is different in different Android versions, and the so is also different. Different API Levels need to be processed separately, and different so and methods are hooked. At present, this solution has been tested and feasible in API 18 and above.

This Hook Trace solution can not only be used to check ANR problems, but at any time we can manually send a SIGQUIT signal to ourselves to hook to the Trace at that time. The content of Trace is very helpful for us to troubleshoot problems such as thread deadlock, thread exception, and power consumption .

In this way, we have obtained a complete ANR monitoring solution, which has been running smoothly on WeChat for a long time, providing us with a very important basis and direction for evaluating and optimizing the quality of WeChat Android clients.


According to different performance monitoring problems, we need to adopt different performance optimization methods. At present, some people are not very proficient in some optimization methods in the middle of performance optimization, so all the different types of optimization methods in the middle of performance optimization are classified. Class sorting, including startup optimization, memory optimization, network optimization, freeze optimization, storage optimization, etc., integrated into "Android Performance Optimization Core Knowledge Points Manual" , you can refer to the following:

"APP Performance Tuning Advanced Manual":https://qr18.cn/FVlo89

Startup optimization

Memory optimization

UI

optimization Network optimization

Bitmap optimization and image compression optimization

Multi-threaded concurrency optimization and data transmission efficiency optimization

Volume package optimization

"Android Performance Tuning Core Notes Summary":https://qr18.cn/FVlo89

"Android Performance Monitoring Framework":https://qr18.cn/FVlo89

Guess you like

Origin blog.csdn.net/weixin_61845324/article/details/130527159