引言
忙忙碌碌又一天,最近学习了一个新项目,每天都是吭哧吭哧的去看项目中的各种源码,都来自于各路大神的贡献。每看到一块地方的时候都感觉触到了自己的知识边界,然后又得吭哧吭哧的去搜索。不知道大家的搜索路径是什么,我的搜索路径从以前的 Google -> 掘金 -> 简书 -> Other(Baidu) 变成了 ChatGPT -> Google -> 其他。

最近在排查主线程耗时的一个任务,既然在主线程了,那还不好办,直接上 Trace 分析主线程中的耗时任务都有哪些不就完了。完了分析完一波后又无从下手啦。接下来考虑从 MainLooper 下手吧,看看哪些是长消息,执行任务又比较耗时。

Handler
Looper
那么如何做长消息的耗时监控呐?这又遇到了一个问题,在以往很多都是通过 Looper 的 Printer 进行控制长消息的检查,但是 Printer 也是有缺点的,就是无法知道它的 callback 来自于哪里,及时统计出来了时间,也不满足需求。
一切的源头都应该从源码进行着手,通过阅读源码发现,在 Looper 中发现了 Observer,它是什么鬼?
            
            
              java
              
              
            
          
          public final class Looper {
    private static Observer sObserver;
    
    /**  
    * Set the transaction observer for all Loopers in this process.  
    *  
    * @hide  
    */  
    public static void setObserver(@Nullable Observer observer) {  
        sObserver = observer;  
    }
    
    public static void loop() {
        // ...此处省略无关紧要的 code
        for (;;) {
            // ...此处省略无关紧要的 code
        
            // This must be in a local variable, in case a UI event sets the logger  
            final Printer logging = me.mLogging;  
            if (logging != null) {  
            logging.println(">>>>> Dispatching to " + msg.target + " " +  
            msg.callback + ": " + msg.what);  
            }  
            // Make sure the observer won't change while processing a transaction.  
            final Observer observer = sObserver;
            
            // ...此处省略无关紧要的 code
            
            Object token = null;
            // 在执行消息执行消息通知
            if (observer != null) {  
                token = observer.messageDispatchStarting();  
            }  
            long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid);  
            try {  
                msg.target.dispatchMessage(msg);  
                // 在消息执行完毕后又执行了消息通知
                if (observer != null) {  
                    observer.messageDispatched(token, msg);  
                }  
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;  
            } catch (Exception exception) {  
                // 消息执行的过程中发生了异常的回调
                if (observer != null) {  
                    observer.dispatchingThrewException(token, msg, exception);  
                }  
                throw exception;  
            } finally {  
                ThreadLocalWorkSource.restore(origWorkSource);  
                if (traceTag != 0) {  
                    Trace.traceEnd(traceTag);  
                }  
            }
        }
    }
}
        根据阅读源码来看,我觉得可以从这个地方下手搞事情。
            
            
              java
              
              
            
          
          /** {@hide} */  
public interface Observer {  
    /**  
    * Called right before a message is dispatched.  
    *  
    * <p> The token type is not specified to allow the implementation to specify its own type.  
    *  
    * @return a token used for collecting telemetry when dispatching a single message.  
    * The token token must be passed back exactly once to either  
    * {@link Observer#messageDispatched} or {@link Observer#dispatchingThrewException}  
    * and must not be reused again.  
    *  
    */  
    Object messageDispatchStarting();  
    /**  
    * Called when a message was processed by a Handler.  
    *  
    * @param token Token obtained by previously calling  
    * {@link Observer#messageDispatchStarting} on the same Observer instance.  
    * @param msg The message that was dispatched.  
    */  
    void messageDispatched(Object token, Message msg);  
    /**  
    * Called when an exception was thrown while processing a message.  
    *  
    * @param token Token obtained by previously calling  
    * {@link Observer#messageDispatchStarting} on the same Observer instance.  
    * @param msg The message that was dispatched and caused an exception.  
    * @param exception The exception that was thrown.  
    */  
    void dispatchingThrewException(Object token, Message msg, Exception exception);  
}
        Observer 是 Looper 的一个内部接口类,用来做事件的回调处理的。主要包含了三个函数 messageDispatchStarting、messageDispatched、dispatchingThrewException,分别会在某条消息调度前、调度处理后、调度处理过程中发生异常时回调。需要注意的是 messageDispatchStarting 要求返回一个 Object 对象,对类型不做任何限制,每个消息类型都对应一个 token,并且理应为独立且唯一。
于是乎,我兴高采烈的开始码起来了。
开搞开搞
先来介绍一下子思路,我们是不是可以直接通过反射调用 setObserver 的方法,是不是直接就可以设置Observer,既然都到了反射,为啥不直接操作 sObserver,说到这就开干。这里使用的动态代理进行 hook Observer。
            
            
              kotlin
              
              
            
          
          public class HandlerLoopHookHelper {
    /**  
    * Hook Looper 的 sObserver 观察消息耗时  
    *  
    * @param context 上下文  
    */  
    public static void hookLooperObserver(@Nullable Context context) {  
        Log.d(TAG, "hookLoopObserver: " + Build.VERSION.SDK_INT);  
            try {  
                @SuppressLint("PrivateApi") final Class<?> sObserverClass = Class.forName("android.os.Looper$Observer");  
                final ObserverInvocation invocation = new ObserverInvocation();  
                final Object o = sObserverClass.cast(Proxy.newProxyInstance(sObserverClass.getClassLoader(), new Class[]{sObserverClass}, invocation));  
                final Class<?> looperClass = Class.forName("android.os.Looper");  
                @SuppressLint("BlockedPrivateApi") final Field sObserver = looperClass.getDeclaredField("sObserver");  
                sObserver.setAccessible(true);  
                sObserver.set(getMainLooper(), o);  
                getMainLooper().setMessageLogging(invocation.printer);  
            } catch (Throwable e) {  
                e.printStackTrace();  
            }  
        }
}
public class ObserverInvocation implements InvocationHandler {
    private long dispatchStart = 0;  
    private long dispatchEnd = 0;
    private static final long MESSAGE_WORK_TIME_300 = 300;  
    private static final long MESSAGE_WORK_TIME_100 = 100;  
    private static final long MESSAGE_WORK_TIME_50 = 50;  
    private static final long MESSAGE_WORK_TIME_10 = 10;
    @Override  
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
        if (Looper.getMainLooper().isCurrentThread()) {  
            if ("messageDispatchStarting".equals(method.getName())) {  
                return messageDispatchStarting();  
            } else if ("messageDispatched".equals(method.getName())) {  
                messageDispatched((long) args[0], (Message) args[1]);  
            } else if ("dispatchingThrewException".equals(method.getName())) {  
                dispatchingThrewException((long) args[0], (Message) args[1], (Exception) args[2]);  
            }  
        }  
        return null;  
    }
    
    public Object messageDispatchStarting() {  
        dispatchStart = SystemClock.uptimeMillis();  
        final long token = atomicLong.getAndIncrement();  
        return token;  
    }  
    public void messageDispatched(Object token, Message msg) {  
        dispatchEnd = SystemClock.uptimeMillis();  
        getTime(msg, (long) token);  
    }  
    public void dispatchingThrewException(Object token, Message msg, Exception exception) {  
    }
    
    /**
     * 计时,可根据自己的需求进行详细的计
     */
    private void getTime(Message message, long token) {  
        // message 指定开始时间,基于开机时间  
        final long when = message.getWhen();  
        // 等待的时长:开始执行 - 指定开始时间  
        final long wait = dispatchStart - when;  
        // 执行的时长:执行结束时间 - 执行开始时间  
        final long work = dispatchEnd - dispatchStart;  
        printRecord(message, token, wait, work);  
    }  
    private void printRecord(Message message, long token, long wait, long work) {  
        final StringBuilder stringBuilder = new StringBuilder();  
        if (work <= MESSAGE_WORK_TIME_10) {  
            stringBuilder.append("(可忽略)").append(dispatchStart).append("-").append(dispatchEnd)  
            .append(">>> token: ").append(token).append(message.toString())  
            .append(" wait: ").append(wait).append("ms")  
            .append(" work: ").append(work).append("ms");  
            Log.v(TAG, stringBuilder.toString());  
        } else if (work <= MESSAGE_WORK_TIME_50) {  
            stringBuilder.append("(看看就好)").append(dispatchStart).append("-").append(dispatchEnd)  
            .append(">>> token: ").append(token).append(message.toString())  
            .append(" wait: ").append(wait).append("ms")  
            .append(" work: ").append(work).append("ms");  
            Log.i(TAG, stringBuilder.toString());  
        } else if (work <= MESSAGE_WORK_TIME_100) {  
            stringBuilder.append("(需要关注一下)").append(dispatchStart).append("-").append(dispatchEnd)  
            .append(">>> token: ").append(token).append(message.toString())  
            .append(" wait: ").append(wait).append("ms")  
            .append(" work: ").append(work).append("ms");  
            Log.w(TAG, stringBuilder.toString());  
        } else if (work <= MESSAGE_WORK_TIME_300) {  
            stringBuilder.append("(需要处理啦~)").append(dispatchStart).append("-").append(dispatchEnd)  
            .append(">>> token: ").append(token).append(message.toString())  
            .append(" wait: ").append(wait).append("ms")  
            .append(" work: ").append(work).append("ms");  
            Log.d(TAG, stringBuilder.toString());  
        } else {  
            stringBuilder.append("(这个就超级严重啦~)").append(dispatchStart).append("-").append(dispatchEnd)  
            .append(">>> token: ").append(token).append(message.toString())  
            .append(" wait: ").append(wait).append("ms")  
            .append(" work: ").append(work).append("ms");  
            Log.e(TAG, stringBuilder.toString());  
        }  
    }
}
        Coding 完毕啦,这里面的输出监控建议不要搞到线上,可能会比较的耗时,根据实际情况来确认如何使用,定制监控的策略,可以考虑超过多长时间来进行报警,或者上报等操作。废话不多说,run 一下子吧,结果gg。

日志的大致意思是,这个类是被 @hide 的,对开发者是屏蔽调用及使用的。查阅相关代码得知,基于 Android 10 版本增加的 Observer,在设计之初就只是为了观察并统计系统服务的Looper消息调度性能(可以查阅LooperStats与LooperStatsService),所以这个API只给自己内部使用。
只要是代码总会有解决的方案,能不能绕过去隐藏 API 呐?能,下来就看看看一下如何如绕开隐藏API 。

绕开 Observer 隐藏 API
解决 Hidden API 的方式有很多种,可能某些在 Android 高版本系统重被官方封堵。不过由于 Android 系统是开源的,所以无论怎么封堵,还是可以通过其他的方式绕过,毕竟系统是不会限制用户修改自身进程的内存。
在这里我使用的方案是 FreeReflection,大致的思路是将自己伪装成系统类,然后就可以调用这些私有 API 了。具体的内容可以参考作者博客里面的介绍。
Android 14 无法加载.dex文件
在这里提个醒,在Android 14及以上版本,对于动态代码的加载发生了一些更改,否则会发生如下内容错误。

通过阅读 DexClassLoader 与 更安全的动态代码加载 发现 Android 14 对于动态加载 .dex 文件做了安全策略的限制,Android 14 对于动态加载(DCL)功能,必须将所有动态加载的文件标记为只读。否则,系统将会抛出异常。

正确操作姿势,就是将 FreeReflection 三方库 Copy 下来,然后修改源码,完美解决在 Android 14 上面不能动态加载 .dex 问题。

然后再跑一下子。完美解决。

正确的打开姿势
            
            
              java
              
              
            
          
          public class HandlerLoopHookHelper {  
  
    private static volatile boolean isHooked = false;  
    private static final String TAG = "HandlerLoopHookHelper";  
    private static void checkHooked(Context context) {  
        if (!isHooked) {  
            Reflection.unseal(context);  
            isHooked = true;  
        }  
    }  
    /**  
    * Hook Looper 的 sObserver 观察消息耗时  
    *  
    * @param context 上下文  
    */  
    public static void hookLooperObserver(@Nullable Context context) {  
        Log.d(TAG, "hookLoopObserver: " + Build.VERSION.SDK_INT);  
        try {  
            checkHooked(context);  
            @SuppressLint("PrivateApi") final Class<?> sObserverClass = Class.forName("android.os.Looper$Observer");  
            final ObserverInvocation invocation = new ObserverInvocation();  
            final Object o = sObserverClass.cast(Proxy.newProxyInstance(sObserverClass.getClassLoader(), new Class[]{sObserverClass}, invocation));  
            final Class<?> looperClass = Class.forName("android.os.Looper");  
            @SuppressLint("BlockedPrivateApi") final Field sObserver = looperClass.getDeclaredField("sObserver");  
            sObserver.setAccessible(true);  
            sObserver.set(getMainLooper(), o);  
            getMainLooper().setMessageLogging(invocation.printer);  
        } catch (Throwable e) {  
            e.printStackTrace();  
        }  
    }  
    /**  
    * 钩针活套观察者  
    *  
    * @param context 上下文  
    */  
    public static void unHookLooperObserver(@Nullable Context context) {  
        Log.d(TAG, "unHookLooperObserver: " + Build.VERSION.SDK_INT);  
        try {  
            checkHooked(context);  
            @SuppressLint("PrivateApi") final Class<?> sObserverClass = Class.forName("android.os.Looper$Observer");  
            final ObserverInvocation invocation = new ObserverInvocation();  
            final Object o = sObserverClass.cast(Proxy.newProxyInstance(sObserverClass.getClassLoader(), new Class[]{sObserverClass}, invocation));  
            final Class<?> looperClass = Class.forName("android.os.Looper");  
            @SuppressLint("BlockedPrivateApi") final Field sObserver = looperClass.getDeclaredField("sObserver");  
            sObserver.setAccessible(true);  
            sObserver.set(getMainLooper(), null);  
            getMainLooper().setMessageLogging(null);  
        } catch (Throwable e) {  
            e.printStackTrace();  
        }  
    }  
    /**  
    * 挂钩主活套消息空闲处理程序  
    *  
    * @param context 上下文  
    */  
    public static void hookMainLooperMessageIdleHandlers(@Nullable Context context) {  
        Log.d(TAG, "hookMainLooperMessageIdleHandlers: " + Build.VERSION.SDK_INT);  
        try {  
            checkHooked(context);  
            final Class<?> looperClass = Class.forName("android.os.Looper");  
            final Field mQueueF = looperClass.getDeclaredField("mQueue");  
            mQueueF.setAccessible(true);  
            final Object mainMessageQueue = mQueueF.get(getMainLooper());  
            final Class<?> mQueueClass = Class.forName("android.os.MessageQueue");  
            final Field mainIdleHandlerF = mQueueClass.getDeclaredField("mIdleHandlers");  
            mainIdleHandlerF.setAccessible(true);  
            final Object o = mainIdleHandlerF.get(mainMessageQueue);  
            final ArrayList<MessageQueue.IdleHandler> mIdleHandlers = (ArrayList<MessageQueue.IdleHandler>) o;  
            Log.d(TAG, "hookMainLooperMessageIdleHandlers size: " + mIdleHandlers.size());  
            for (MessageQueue.IdleHandler mIdleHandler : mIdleHandlers) {  
                Log.d(TAG, "hookMainLooperMessageIdleHandlers content is: " + mIdleHandler.toString());  
            }  
        } catch (Throwable e) {  
            e.printStackTrace();  
        }  
    }  
}
        
小结
Observer相比于Printer可以直接拿到Message对象,并且不需要设置Printer可以避免每个消息调度时额外拼接字符串的成本。- 解决开发阶段 
Hidden API访问限制,可以通过很多种方式绕过,也可以考虑通过CompileOnly一个假工程来实现,假工程里面模拟相应的系统源码,从而实现Observer类的访问。不过我试过这种方法,同样也需要搞定系统的hidden api貌似也是绕不过去的,如果使用这个库 FreeReflection 的话,那么假工程的方式也是可以实现,在这里不做赘述,感兴趣的同学可以自己尝试。 Observer机制是在 Anroid 10 开始添加的,因此低版本还是需要用Printer的方式进行监听- Android 14 版本动态加载 
.dex文件需要设置file为只读模式,否则将会抛出安全异常。