原理总结
1. 启动链路
KSU post-fs-data.sh
└─ detached start.sh(不阻塞开机)
├─ 等 surfaceflinger 进程出现
├─ 从 /sys/class/drm/card0-DSI-1/modes 读 1440x3200
└─ app_process + klog.dex 启动 KLogTop(uid=0)
service.sh 再执行一次 start.sh 作为兜底(幂等,按 pid 判断)
不安装 APK,也不经过 ActivityManager/WindowManager。app_process 直接启动 ART 和 main(),因此在 Zygote 之后、系统 UI 还没完全起来时就能运行。早期阶段不需要 system_server,只依赖 SurfaceFlinger、/dev/kmsg 和 DRM 面板模式。
2. 为什么能显示在最顶层
KLogTop 用 SurfaceControl.Builder 创建一个 BLAST Layer,然后通过 SurfaceControl.Transaction:
- setLayer(INT_MAX):放到最高 Z 序
- setLayerStack(0):挂到主显示
- show() / apply():提交给 SurfaceFlinger
这个 Layer 没有父 Layer。uid 0 在 SurfaceFlinger 的 addToRoot 检查中拥有 ACCESS_SURFACE_FLINGER(PermissionCache 对 root 直接放行),因此它成为 display 的根层,位于 bootanimation、SystemUI、状态栏、导航栏之上。之前的 dumpsys 里能看到它在 Composition list 的第一个。
3. 为什么触摸能穿透
全屏 Layer 默认会被 InputDispatcher 当成“不可信遮挡层”:
- 它虽然 NOT_TOUCHABLE,但 alpha=1 且覆盖全屏
- 遮挡判断用的是 Layer 的 frame,不是 touchableRegion
- BLOCK_UNTRUSTED 模式下,下方窗口的触摸会被拦截
所以必须调用:
transaction.setTrustedOverlay(sc, true)
加上 TRUSTED_OVERLAY 后,InputDispatcher.canBeObscuredBy() 会直接跳过它,触摸继续传给下面的应用。这个操作同样依赖 root 的 ACCESS_SURFACE_FLINGER 权限。
4. 透明背景与字体
- Layer 使用 PixelFormat.TRANSLUCENT,并设置 setOpaque(false)
- 每帧用 PorterDuff.CLEAR 清空 Surface,像素缓冲区背景填 0x00000000
- 只有文字、阴影、光标和半透明状态条有 alpha,其余区域完全透明,由 SurfaceFlinger 与下层内容混合
- 文字加了 2px 黑色阴影,保证在透明背景上也可读
字体没有使用 Android Typeface(在 app_process 环境下字体系统未初始化,会抛异常甚至 native abort),而是内置了一套 public domain 8x8 点阵字库:
- Base64 解码成 128 个 ASCII 字形
- 每个像素放大 4 倍(32px 字高)
- 先写进 int[],再 Bitmap.setPixels(),最后一次性 drawBitmap() 到 Surface
- 当前布局为 45 列 × 96 行日志
5. 内核日志来源
- 以 root 打开 /dev/kmsg,它按记录返回整个内核环形缓冲区,之后阻塞等待新日志
- 每条格式为:priority,seq,usec,flags;message
- 解析出:
- priority:决定颜色(错误红、警告黄、notice 青、info 白、debug 灰)
- usec:格式化成 [秒.微秒]
- 清除 caller=...;、ANSI 转义和控制字符,按 45 列自动换行
- 读者线程维护一个最多 96 行的环形缓冲,UI 线程在内容变化或光标闪烁时重绘