1. 为什么需要打印本地JS资源日志
在UniApp离线打包Android APK的开发过程中,打印本地JS资源日志是一个高频需求场景。当我们的应用在真机运行时,控制台日志(console.log)默认不会输出到Android Studio的Logcat中,这给调试带来了巨大障碍。想象一下,你正在开发一个复杂的跨端功能,在HBuilderX的模拟器中运行正常,但打包成APK后出现异常,却无法看到JS层的详细执行过程——这种"盲调"状态会让开发效率直线下降。
离线打包环境下,JS代码会被编译到assets/apps/[appid]/www目录下的静态文件中。与浏览器环境不同,这些JS资源的执行过程对开发者是黑盒状态。通过实践发现,主要有三类典型场景需要捕获JS日志:
- 运行时逻辑追踪:当业务逻辑在真机出现异常时,需要查看函数调用栈、变量状态等关键信息
- 性能问题定位:需要统计特定操作的执行耗时,分析性能瓶颈
- 原生交互调试:验证uni.postMessage、plus.bridge等跨端通信是否正常触发
提示:在Android 8.0及以上版本中,系统WebView默认禁用了console输出到logcat,这是很多开发者发现日志"消失"的根本原因。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 基础日志打印方案实现
2.1 修改WebView默认配置
核心解决思路是通过修改UniApp运行时的WebView配置,开启JavaScript控制台输出。在离线打包工程中,需要找到src/main/java/io/dcloud/xxx/AppActivity.java文件(具体路径可能因SDK版本不同而变化),在onCreate方法中添加如下配置:
java复制@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 关键配置开始
WebView.setWebContentsDebuggingEnabled(true);
WebView myWebView = new WebView(this);
myWebView.setWebChromeClient(new WebChromeClient() {
@Override
public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
Log.d("JSConsole", consoleMessage.message() + " -- Line: "
+ consoleMessage.lineNumber() + " of " + consoleMessage.sourceId());
return true;
}
});
// 关键配置结束
initHardwareAcceleration(this);
initEngine(this, getIntent());
}
这段代码做了三件事:
- 启用WebView调试模式(即使非调试版APK也生效)
- 创建自定义WebChromeClient实例
- 重写onConsoleMessage方法,将JS日志转发到Android系统日志
2.2 日志过滤与查看技巧
配置完成后,在Android Studio的Logcat中使用以下过滤标签查看日志:
tag:JSConsole过滤所有JS输出的日志level:verbose显示所有级别的日志package:your.app.package限定当前应用的日志
实测中发现几个实用技巧:
- 复杂对象输出:在JS中使用
JSON.stringify()转换对象后再console.log - 性能打点:用
console.time('tag')和console.timeEnd('tag')包裹代码块 - 错误边界:建议在main.js中全局捕获异常:
javascript复制window.onerror = function(message, source, lineno, colno, error) {
console.error(`GlobalError: ${message} at ${source}:${lineno}`);
};
3. 高级日志管理方案
3.1 日志分级控制
生产环境需要更精细的日志控制。建议在assets目录下创建logger_config.json:
json复制{
"level": "debug",
"persistent": false,
"maxHistory": 50
}
然后在JS中实现分级logger:
javascript复制const Logger = {
levels: { error: 1, warn: 2, info: 3, debug: 4 },
log(level, ...args) {
const config = require('../../logger_config.json');
if (this.levels[level] <= this.levels[config.level]) {
console[level](`[${level.toUpperCase()}]`, ...args);
}
}
}
// 使用示例
Logger.log('debug', 'Init complete', { version: '1.0.0' });
3.2 日志持久化方案
对于需要长期追踪的问题,可以将日志写入设备存储:
- 在AndroidManifest.xml添加存储权限:
xml复制<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
- 实现JS-Native双向通信:
javascript复制// JS调用原生写日志
const writeLog = (content) => {
if (window.plus) {
plus.io.requestFileSystem(plus.io.PRIVATE_WRITE, (fs) => {
fs.root.getFile('logs.txt', { create: true }, (fileEntry) => {
fileEntry.createWriter((writer) => {
writer.seek(writer.length);
writer.write(`${new Date().toISOString()} ${content}\n`);
});
});
});
}
}
- 原生层实现日志轮转:
java复制// 在AppActivity中添加
private void rotateLogs() {
File logFile = new File(getExternalFilesDir(null), "logs.txt");
if (logFile.length() > 1024 * 1024) { // 1MB
logFile.renameTo(new File(getExternalFilesDir(null),
"logs_" + System.currentTimeMillis() + ".txt"));
}
}
4. 常见问题与性能优化
4.1 高频日志的性能影响
在压力测试中发现,当每秒日志量超过500条时,WebView到Native的通信会成为性能瓶颈。优化方案包括:
- 使用批处理模式:
javascript复制let logQueue = [];
setInterval(() => {
if (logQueue.length > 0) {
console.log(logQueue.join('\n'));
logQueue = [];
}
}, 200); // 200ms批处理间隔
- 关键路径日志采样:
javascript复制function sampledLog(fn, sampleRate = 0.1) {
return Math.random() < sampleRate ? fn() : null;
}
// 使用示例
sampledLog(() => console.log('Render frame', performance.now()));
4.2 真机调试的特殊情况
遇到日志不输出的情况,可按此流程排查:
- 检查设备开发者选项是否开启USB调试
- 确认APK的android:debuggable=true(在build.gradle中配置)
- 对于华为等定制ROM,可能需要关闭"严格模式"
- Android 9+需要额外配置:
xml复制<application
android:usesCleartextTraffic="true"
android:networkSecurityConfig="@xml/network_security_config">
网络安全配置示例(res/xml/network_security_config.xml):
xml复制<network-security-config>
<base-config cleartextTrafficPermitted="true">
<trust-anchors>
<certificates src="system" />
</trust-anchors>
</base-config>
</network-security-config>
4.3 与现有日志系统的整合
如果项目已使用第三方日志系统(如Log4j、Timber),可以通过桥接方式统一管理:
java复制// 在AppActivity中
private static Logger bridgeLogger = Logger.getLogger("JSBridge");
@Override
public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
switch (consoleMessage.messageLevel()) {
case ERROR:
bridgeLogger.error(formatMessage(consoleMessage));
break;
case WARNING:
bridgeLogger.warn(formatMessage(consoleMessage));
break;
default:
bridgeLogger.info(formatMessage(consoleMessage));
}
return true;
}
我在实际项目中发现,合理配置日志系统可以将问题定位时间缩短60%以上。特别是在处理WebView内存泄漏问题时,通过日志回溯对象创建路径,能快速定位到未释放的DOM事件监听器。建议在复杂业务模块入口添加"指纹日志",如:
javascript复制console.log(`[MODULE_INIT] ${this.$options.name}`,
`uid=${this._uid}`,
`route=${this.$route.path}`);
