18、Launcher的日志系统:Logcat的Tag规范、自定义Logger工具、关键节点的日志埋点
做Launcher开发,最头疼的是什么?
我个人觉得,不是UI布局,也不是动画卡顿,而是——出了问题你根本不知道它发生在哪一步。Launcher这东西,启动流程长、事件交互多、状态切换频繁。没有一套好的日志系统,你就像在黑夜里摸路。
今天我们就来聊聊,怎么给Launcher装上一双“眼睛”。
18.1 为什么Launcher需要一套独立的日志体系?
你可能会想:Android不是自带Logcat吗?直接用不就行了?
嗯,理论上可以。但实际项目中你会发现,Launcher的日志量太大了。系统服务、输入事件、窗口管理、应用启动……各种日志混在一起。你想看Launcher自己的行为,得从几千行日志里翻,效率极低。
我在项目中遇到过这种情况:线上用户反馈说“桌面偶尔卡死”,但我们抓到的日志全是系统级的,根本定位不到Launcher内部哪个环节出了问题。后来我们花了三天时间,把日志体系重新梳理了一遍,问题才浮出水面。
所以,Launcher必须有自己的日志规范。说白了,就是三个目标:
- 可过滤:一眼就能从Logcat里挑出Launcher的日志
- 可分级:区分DEBUG、INFO、WARN、ERROR,别把调试信息当错误打
- 可追溯:关键节点必须有日志,能还原整个执行链路
18.2 Logcat的Tag规范:给日志打上“标签”
Logcat里过滤日志靠什么?靠Tag。Tag就是日志的身份证。
我建议Launcher项目里统一使用这样的Tag命名规则:
| 模块 | Tag格式 | 示例 |
|---|---|---|
| 桌面启动 | Launcher_Startup | Launcher_Startup_onCreate |
| 工作区 | Launcher_Workspace | Launcher_Workspace_addItem |
| 拖拽 | Launcher_Drag | Launcher_Drag_startDrag |
| 应用列表 | Launcher_AllApps | Launcher_AllApps_onSearch |
| 数据库 | Launcher_DB | Launcher_DB_queryFavorites |
| 动画 | Launcher_Anim | Launcher_Anim_springTo |
为什么要加“Launcher_”前缀?因为这样你在Logcat里直接搜“Launcher_”就能过滤出所有Launcher相关日志。简单粗暴,但有效。
tag:~Launcher_。这样每次打开Android Studio,直接点一下就能看到Launcher的日志,不用重新输入。
18.3 自定义Logger工具:让日志更好用
Android自带的Log类功能太基础了。我们得自己封装一个Logger工具,加一些“私货”。
来看看我项目中用的Logger工具长什么样:
public class LauncherLogger {
private static final String TAG_PREFIX = "Launcher_";
private static final boolean DEBUG = BuildConfig.DEBUG;
public static void v(String tag, String msg) {
if (DEBUG) Log.v(TAG_PREFIX + tag, msg);
}
public static void d(String tag, String msg) {
if (DEBUG) Log.d(TAG_PREFIX + tag, msg);
}
public static void i(String tag, String msg) {
Log.i(TAG_PREFIX + tag, msg);
}
public static void w(String tag, String msg) {
Log.w(TAG_PREFIX + tag, msg);
}
public static void e(String tag, String msg) {
Log.e(TAG_PREFIX + tag, msg);
}
public static void e(String tag, String msg, Throwable tr) {
Log.e(TAG_PREFIX + tag, msg, tr);
}
// 带方法名的日志,方便追踪调用位置
public static void dWithMethod(String tag, String msg) {
if (DEBUG) {
StackTraceElement[] stack = Thread.currentThread().getStackTrace();
String methodName = stack.length > 4 ? stack[4].getMethodName() : "unknown";
Log.d(TAG_PREFIX + tag, "[" + methodName + "] " + msg);
}
}
}
这个工具有几个好处:
- 自动加前缀,不用每个地方都写“Launcher_”
- DEBUG模式下才输出Verbose和Debug级别日志,Release包自动关闭
- 带方法名的版本,能直接看到是哪个函数打的日志
18.4 关键节点的日志埋点:把“眼睛”装对地方
日志工具有了,接下来就是埋点。埋点不是到处乱打,而是挑关键节点。
我总结了一下,Launcher里必须埋点的几个位置:
18.4.1 启动流程
Launcher启动是最容易出问题的环节。我建议在以下位置打日志:
// Launcher.java
@Override
protected void onCreate(Bundle savedInstanceState) {
LauncherLogger.i("Startup", "onCreate start");
// ... 初始化代码 ...
LauncherLogger.i("Startup", "onCreate end");
}
@Override
protected void onResume() {
LauncherLogger.i("Startup", "onResume start");
// ... 恢复代码 ...
LauncherLogger.i("Startup", "onResume end");
}
// 绑定工作区完成
private void onWorkspaceBound() {
LauncherLogger.i("Startup", "workspace bound, items count=" + mWorkspace.getChildCount());
}
为什么要打“start”和“end”?因为这样你就能算出每个阶段耗时。如果onCreate到onResume之间超过2秒,那肯定有问题。
18.4.2 应用安装/卸载/更新
Launcher需要监听应用变化。这个环节的日志很重要:
// 应用安装
@Override
public void onPackageAdded(String packageName, UserHandle user) {
LauncherLogger.i("Package", "added: " + packageName);
// 重新加载图标
LauncherLogger.d("Package", "reloading icon for " + packageName);
}
// 应用卸载
@Override
public void onPackageRemoved(String packageName, UserHandle user) {
LauncherLogger.w("Package", "removed: " + packageName);
// 移除桌面图标
}
// 应用更新
@Override
public void onPackageChanged(String packageName, UserHandle user) {
LauncherLogger.i("Package", "changed: " + packageName);
}
18.4.3 拖拽操作
拖拽是Launcher里交互最复杂的操作。埋点要覆盖整个生命周期:
// 开始拖拽
public void onDragStart(DragSource source, Object info, int dragAction) {
LauncherLogger.d("Drag", "start, source=" + source.getClass().getSimpleName());
}
// 拖拽中
public void onDragMove(float x, float y) {
// 这里不要打日志!太频繁了
}
// 拖拽结束
public void onDragEnd(boolean dropped) {
LauncherLogger.d("Drag", "end, dropped=" + dropped);
if (!dropped) {
LauncherLogger.w("Drag", "item not dropped, returning to original position");
}
}
你发现没有?onDragMove我没打日志。为什么?因为拖拽过程中坐标变化太频繁,一秒几十次,打日志会严重影响性能。我见过有人在这里打日志,结果拖拽卡成PPT。
18.4.4 数据库操作
Launcher的数据库操作虽然不频繁,但一旦出错,后果很严重:
// 查询
public ArrayList<ItemInfo> loadFavorites() {
LauncherLogger.d("DB", "loadFavorites start");
ArrayList<ItemInfo> items = queryFavorites();
LauncherLogger.d("DB", "loadFavorites end, count=" + items.size());
return items;
}
// 写入
public void addItemToDatabase(ItemInfo item) {
LauncherLogger.i("DB", "addItem: " + item.title + ", id=" + item.id);
try {
// 写入操作
} catch (Exception e) {
LauncherLogger.e("DB", "addItem failed", e);
}
}
18.5 知识体系总览
下面这张图,把Launcher日志系统的核心脉络画出来了:
18.6 避坑指南
最后,分享几个我踩过的坑:
- 别在循环里打日志:尤其是ListView的getView、动画的onAnimationUpdate这些高频回调。我曾经在getView里打了一个d级别的日志,结果列表滑动时手机发烫,帧率掉到20帧。
- Release包一定要做日志过滤:用BuildConfig.DEBUG控制,或者用ProGuard把Log代码删掉。不然用户手机里全是你的调试日志。
- 敏感信息别打日志:比如用户输入的搜索关键词、包名列表等。我之前见过有人把用户安装的所有应用包名打到了日志里,这其实涉及隐私问题。
- 日志别太长:Logcat单条日志有长度限制(好像是4KB左右)。超过会被截断。如果你要打印一个列表,建议只打数量,别打全部内容。
好了,Launcher的日志系统就聊到这里。这套规范我在多个项目中实践过,效果还不错。你可以在自己的项目里试试,有问题随时交流。
公众号:蓝海资料掘金营,微信deep3321