安卓14剖析SystemUI的ShadeLogger/LogBuffer日志动态控制输出dumpsy机制

背景:

看SystemUI的锁屏相关代码时候发现SystemUI有一个日志打印相关的方法调用,相比于常规的Log.i直接可以logcat查看方式还是比较新颖。

具体日志打印代码如下: 下面就来介绍一下这个ShadeLogger到底是如何打印的。

分析源码:

源码位置: frameworks/base/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt

明显是一个kt类,这里就只拿一个logEndMotionEvent方法来进行源码分析

cpp 复制代码
fun logEndMotionEvent(
    msg: String,
    forceCancel: Boolean,
    expand: Boolean,
)
{
    buffer.log(
        TAG,
        LogLevel.VERBOSE,
        {
            str1 = msg
            bool1 = forceCancel
            bool2 = expand
        },
        { "$str1; force=$bool1; expand=$bool2" }
    )
}

可以看到这里看到实际是调用的buffer.log方法,也有对应TAG和LogLevel等级。 那么下面来看看这个buffer.log中的buffer哪里来的,但是因为这构造都是采用了很多注解drag2方式,所以不方便找,这里找到了一个NotificationPanelViewControllerBaseTest一个测试类有手动进行构造,这里也可以看出相关过程

frameworks/base/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java

具体过程如下: 再看看mShadeLog构造 再看看logcatLogBuffer方法 这里调用到了LogBuffer类,注意这里test类给的是50,实际的Shader类给的是500 frameworks/base/packages/SystemUI/log/src/com/android/systemui/log/LogBuffer.kt 看看log方法:

cpp 复制代码
inline fun log(
    tag: String,
    level: LogLevel,
    messageInitializer: MessageInitializer,
    noinline messagePrinter: MessagePrinter,
    exception: Throwable? = null,
) {
    val message = obtain(tag, level, messagePrinter, exception)
    messageInitializer(message)
    commit(message)
}

看看obtain方法:

cpp 复制代码
@Synchronized
    override fun obtain(
        tag: String,
        level: LogLevel,
        messagePrinter: MessagePrinter,
        exception: Throwable?,
    ): LogMessage {
        if (!mutable) {
            return FROZEN_MESSAGE
        }
        val message = buffer.advance()//可以看到这里是buffer中获取,
        message.reset(tag, level, System.currentTimeMillis(), messagePrinter, exception)
        return message
    }

这里其实只是看出来了buffer中搞出了一个message,根据传递来的tag和msg 接下来重点看看commit方法

cpp 复制代码
override fun commit(message: LogMessage) {
    if (echoMessageQueue != null && echoMessageQueue.remainingCapacity() > 0) {
        try {
            echoMessageQueue.put(message)//主要就是放入队列
        } catch (e: InterruptedException) {
            // the background thread has been shut down, so just log on this one
            echoToDesiredEndpoints(message)
        }
    } else {
        echoToDesiredEndpoints(message)
    }
}

commit主要就是实现对message放入到echoMessageQueue,那么什么时候取这个队列呢?

这里要看最开始的init方法中有启动一个线程

cpp 复制代码
init {
    if (logcatEchoTracker.logInBackgroundThread && echoMessageQueue != null) {
        thread(start = true, name = "LogBuffer-$name", priority = Thread.NORM_PRIORITY) {
            try {
                while (true) {//死循环的取出队列
                    echoToDesiredEndpoints(echoMessageQueue.take())//调用echoToDesiredEndpoints来处理消息
                }
            } catch (e: InterruptedException) {
                Thread.currentThread().interrupt()
            }
        }
    }
}

具体echoToDesiredEndpoints方法如下:

cpp 复制代码
 private fun echoToDesiredEndpoints(message: LogMessage) {
 //获取log打印level,即其实可以通过命令来控制打印level,这里的level本质是来自settings,具体啥settings值下面操作时候会讲解
        val includeInLogcat =
            logcatEchoTracker.isBufferLoggable(name, message.level) ||
                logcatEchoTracker.isTagLoggable(message.tag, message.level)
        echo(message, toLogcat = includeInLogcat, toSystrace = systrace)//有了上面level后,echo开始处理
    }
   private fun echo(message: LogMessage, toLogcat: Boolean, toSystrace: Boolean) {
        if (toLogcat || toSystrace) {
            val strMessage = message.messagePrinter(message)
            if (toSystrace) {
                echoToSystrace(message, strMessage)//可以看这个日志还支持systrace相关
            }
            if (toLogcat) {
                echoToLogcat(message, strMessage)//这里就是最普通的logcat打印出来
            }
        }
    }
//具体的echoToLogcat其实就是根据传递进来的等级进行普通log打印
  private fun echoToLogcat(message: LogMessage, strMessage: String) {
        when (message.level) {
            LogLevel.VERBOSE -> Log.v(message.tag, strMessage, message.exception)
            LogLevel.DEBUG -> Log.d(message.tag, strMessage, message.exception)
            LogLevel.INFO -> Log.i(message.tag, strMessage, message.exception)
            LogLevel.WARNING -> Log.w(message.tag, strMessage, message.exception)
            LogLevel.ERROR -> Log.e(message.tag, strMessage, message.exception)
            LogLevel.WTF -> Log.wtf(message.tag, strMessage, message.exception)
        }
    }

到此LogBuffer源码也就大概分析完成,可以得出以下几个结论:

1、所有的埋点日志会保存到buffer中,这个buffer只是在内存中的一个环形buffer,有固定大小

2、buffer中的日志是可以 实现输出到logcat和systrace的功能

那么具体如何控制输出到logcat,还有如何看buffer中的日志呢?接下来看看使用方法

使用方式:

在类的最开始部分有如下的使用注释:

bash 复制代码
/**
 * A simple ring buffer of recyclable log messages
 *
 * The goal of this class is to enable logging that is both extremely chatty and extremely
 * lightweight. If done properly, logging a message will not result in any heap allocations or
 * string generation. Messages are only converted to strings if the log is actually dumped (usually
 * as the result of taking a bug report).
 *
 * You can dump the entire buffer at any time by running:
 * ```
 * $ adb shell dumpsys activity service com.android.systemui/.SystemUIService <bufferName>
 * ```
 *
 * ...where `bufferName` is the (case-sensitive) [name] passed to the constructor.
 *
 * By default, only messages of WARN level or higher are echoed to logcat, but this can be adjusted
 * locally (usually for debugging purposes).
 *
 * To enable logcat echoing for an entire buffer:
 * ```
 * $ adb shell settings put global systemui/buffer/<bufferName> <level>
 * ```
 *
 * To enable logcat echoing for a specific tag:
 * ```
 * $ adb shell settings put global systemui/tag/<tag> <level>
 * ```
 *
 * In either case, `level` can be any of `verbose`, `debug`, `info`, `warn`, `error`, `assert`, or
 * the first letter of any of the previous.
 *
 * In SystemUI, buffers are provided by LogModule. Instances should be created using a SysUI
 * LogBufferFactory.
 *
 * @param name The name of this buffer, printed when the buffer is dumped and in some other
 *   situations.
 * @param maxSize The maximum number of messages to keep in memory at any one time. Buffers start
 *   out empty and grow up to [maxSize] as new messages are logged. Once the buffer's size reaches
 *   the maximum, it behaves like a ring buffer.
 */

其实上面已经写的很详细了,主要就是2个核心点,一个可以通过dumpsys看所有日志,一个是可以控制logcat输出

控制dumpsys查看方法

bash 复制代码
adb shell dumpsys activity service com.android.systemui/.SystemUIService <bufferName>

比如这里的对ShadeLog

bash 复制代码
adb shell dumpsys activity service com.android.systemui/.SystemUIService ShadeLog

dumpsys后可以查看到相关的Log: 看到这个dump日志就感觉非常详细的记录了Shade锁屏相关的操作,相关的tag等也是在ShadeLogger.kt定义的

如果想要普通logcat输出呢?

bash 复制代码
 adb shell settings put global systemui/tag/ShadeLog v

这里其实就是配置一个settings,然后上面的提到的echoToDesiredEndpoints的 logcatEchoTracker.isBufferLoggable就会去查询这个settings值。

总结图

更多framework技术干货,请关注下面"千里马学框架"

相关推荐
Ankkaya2 小时前
cloudflare + github 实现留言板
前端·github
是你的小橘呀2 小时前
单页应用路由怎么搞?React Router 从原理到实战全解析!
前端·javascript
xuedaobian2 小时前
2025年我是怎么用AI写代码的
前端·程序员·ai编程
风止何安啊2 小时前
Set/Map+Weak三剑客的骚操作:JS 界的 “去重王者” ,“万能钥匙”和“隐形清洁工”
前端·javascript·面试
saberxyL2 小时前
前端登录加密与Token管理实践
前端
3秒一个大2 小时前
React 中 Context 的作用与用法:从主题切换案例说起
前端·react.js
2501_944446002 小时前
Flutter&OpenHarmony文本输入组件开发
前端·javascript·flutter
AI前端老薛2 小时前
你了解react合成事件吗
前端·react.js·前端框架
贺今宵2 小时前
2025.electron-vue3-sqlite3使用
前端·javascript·electron