开发工具移动开发【免费下载链接】stethoStetho is a debug bridge for Android applications, enabling the powerful Chrome Developer Tools and much more.项目地址https://gitcode.com/gh_mirrors/st/stetho点击查看免费下载导读stetho-js-rhino是 Stetho 官方提供的 JavaScript 运行时插件它把 Mozilla Rhino 引擎嵌入到 Android 应用中让开发者可以在 Chrome DevTools 的 Console 面板里对正在运行的应用执行实时 JavaScript 命令。本文基于仓库中的 stetho-js-rhino/README.md 展开完整覆盖依赖引入、自动检测与手动装配、默认作用域与内置对象、类/包/变量/函数绑定以及 Dex 方法数限制与 ProGuard 瘦身规则并结合stetho-js-rhino模块与核心stetho模块的源码逐层印证其工作原理读完即可在 Debug 构建中落地一套可交互的 JavaScript 控制台。模块定位Stetho 生态中的 JavaScript 控制台Stetho 本身是一套 Android 调试桥Debug Bridge它通过本地 Socket 暴露 Chrome DevTools 协议从而复用 Chrome 开发者工具的 Network、DOM、Database 等面板。但默认情况下Console 面板只能看到日志无法执行代码——因为 Stetho 核心并不内置任何 JavaScript 引擎。stetho-js-rhino补上的正是这一环它通过嵌入 Mozilla 的 Rhino 引擎Rhino 是 JVM 平台上的 JavaScript 实现官方文档原文将其描述为 JavaScript runtime provided by Mozillas Rhino初始化一个可在 Android 应用内工作的 JavaScript 运行时最终以RuntimeRepl的形式接入 Stetho 的Runtime域RuntimeChrome DevTools 域负责 Console 面板的表达式求值。整个模块只有 4 个 Java 类职责非常清晰全部位于stetho-js-rhino/src/main/java/com/facebook/stetho/rhino/类职责JsRuntimeReplFactoryBuilder构建器负责配置 JavaScript 环境导入类/包、绑定变量/函数并产出RuntimeReplFactoryJsRuntimeRepl实际执行 JavaScript 表达式的RuntimeRepl实现JsConsole向 JavaScript 暴露console对象的宿主类JsFormat模拟浏览器console.log()格式化的解析器依赖引入与快速启用Gradle / Maven 坐标按官方文档在模块的build.gradle中加入implementation com.facebook.stetho:stetho-js-rhino:1.4.2或使用 Mavendependency groupIdcom.facebook.stetho/groupId artifactIdstetho-js-rhino/artifactId version1.4.2/version /dependency官方文档特别强调必须同时依赖主模块stetho因为stetho-js-rhino只负责提供 Rhino 运行时而 DevTools 协议、Socket 服务等基础设施全部来自核心stetho模块。从依赖方向看JsRuntimeReplFactoryBuilder大量引用了com.facebook.stetho.inspector.console.RuntimeRepl、RuntimeReplFactory以及CLog等核心接口反过来也印证了这一点。零配置自动启用stetho-js-rhino最省事的地方在于只要把依赖加进工程Stetho 就能自动探测到它并启用 Console 求值能力无需修改任何初始化代码。这一机制的实现位于核心模块的 RhinoDetectingRuntimeReplFactory.java首次调用时通过反射查找com.facebook.stetho.rhino.JsRuntimeReplFactoryBuilder类找到后调用其静态方法defaultFactory(Context)获取工厂找不到即未引入stetho-js-rhino依赖则降级为一个提示性RuntimeRepl在 Console 里返回Not supported without stetho-js-rhino dependency。而在核心模块 Stetho.java 中DefaultInspectorModulesBuilder装配Runtime域时遵循如下优先级开发者通过runtimeRepl(...)显式指定的工厂优先未指定时才回退到RhinoDetectingRuntimeReplFactory。这就是加依赖即生效传自定义工厂即接管两条路径的底层依据。手动装配传入自定义运行时如果希望自定义 JavaScript 环境比如预置变量、函数则需要手动装配。官方文档给出了完整示例Stetho.initialize(Stetho.newInitializerBuilder(context) .enableWebKitInspector(new InspectorModulesProvider() { Override public IterableChromeDevtoolsDomain get() { return new DefaultInspectorModulesBuilder(context).runtimeRepl( new JsRuntimeReplFactoryBuilder(context) // Pass to JavaScript: var foo bar; .addVariable(foo, bar) .build() ).finish(); } }) .build();对应到源码DefaultInspectorModulesBuilder.runtimeRepl(RuntimeReplFactory)定义于 Stetho.java其类型RuntimeReplFactory是一个延迟工厂接口声明于 RuntimeReplFactory.java。接口注释还透露了一个设计细节每个连接上来的对端peer都会 new 一个独立的RuntimeRepl实例这样对端断开后其持有的 JavaScript 作用域Scope就能被 GC 回收避免跨会话状态泄漏。工作原理为什么 Rhino 必须在解释模式下运行这是本插件最核心的技术决策。JsRuntimeRepl.enterJsContext()见 JsRuntimeRepl.java在创建 RhinoContext时做了两件事jsContext.setLanguageVersion(Context.VERSION_1_8); // We cant let Rhino to optimize the JS and to use a JIT because it would generate JVM bytecode // and android runs on DEX bytecode. Instead we need to go in interpreted mode. jsContext.setOptimizationLevel(-1);setLanguageVersion(Context.VERSION_1_8)把语言版本设为 ES5Rhino 的 1.8 对应版本setOptimizationLevel(-1)禁用 Rhino 的优化与 JIT。原因如源码注释所述Rhino 的高级优化会在运行期即时生成 JVM 字节码而 Android 虚拟机ART/Dalvik只认 Dex 字节码无法加载这些 JVM 字节码。因此插件强制走解释执行模式。官方文档也补充说明由于这是调试工具解释模式带来的性能差异在通用场景下可以忽略不计。求值入口evaluate(String)JsRuntimeRepl.java的逻辑同样值得注意进入 RhinoContext调用jsContext.evaluateString(mJsScope, expression, chrome, 1, null)求值仿照 Chrome 的行为把上一次表达式的结果保存到$_变量源码注释 Google chrome automatically saves the last expression to$_, we do the same通过Context.jsToJava(result, Object.class)把 JavaScript 结果转回 Java 对象返回给 DevTools 渲染。顺带一提SOURCE_NAME chrome这个细节也很有意思Rhino 用它来标记求值来源因此 JavaScript 编译/运行时错误在 DevTools 里会显示为来自 chrome 这一源文件。默认作用域开箱即用的 JavaScript 环境官方文档明确指出标准 JavaScript 解释器的默认作用域是空的——只有语言规范定义的东西没有浏览器提供的document、console等对象那些是浏览器运行时特有的。而stetho-js-rhino默认作用域做了多方面的增强具体实现见JsRuntimeReplFactoryBuilder.initJsScope(Context)JsRuntimeReplFactoryBuilder.java使用 ImporterTopLevel 增强运行时ImporterTopLevel importerTopLevel new ImporterTopLevel(jsContext); ScriptableObject scope jsContext.initStandardObjects(importerTopLevel, false);ImporterTopLevel是 Rhino 提供的脚本化 Java运行时它额外内置了importClass与importPackage这两个便捷函数正是文档所说的 Rhino offers the possibility to use an enhanced runtime where some utilities have been added in order to help the integration with the java runtime。自动注入的对象与变量结合构造器与initJsScope的代码默认环境包含contextScriptableObject.putProperty(scope, context, Context.javaToJS(mContext, scope))即应用上下文被绑定为全局变量context——这正是示例代码里Toast.makeText(context, ...)能直接引用context的原因$_预置为Undefined后续每次求值后被覆盖为上一个表达式的结果见上文应用包名自动导入构造器里执行mPackages.add(context.getPackageName())所以R.string.app这类引用开箱即用console对象通过JsConsole类注册到作用域且属性被标记为DONTENUM不可枚举。文档明确说明当前console仅支持log()一个方法。导入失败时的降级策略importClasses/importPackages都采用先常规导入、失败后用Packages.前缀再试的双通道策略JsRuntimeReplFactoryBuilder.java两者都失败则抛出StethoJsException并在初始化时写入 logcat 与 DevTools ConsoleCLog.writeToConsole(ERROR, JAVASCRIPT, message)不会让整个初始化崩溃。自定义 JavaScript 环境类、包、变量与函数官方文档的 Customization 部分给出了四类定制能力全部通过JsRuntimeReplFactoryBuilder链式 API 提供。先创建一个构建器实例// context is your application context JsRuntimeReplFactoryBuilder jsRuntimeBuilder new JsRuntimeReplFactoryBuilder(context);导入单个 Java 类jsRuntimeBuilder.importClass(R.class);对应源码importClass(Class? aClass)JsRuntimeReplFactoryBuilder.java内部先尝试importClass(完整类名)失败后再尝试importClass(Packages.完整类名)。导入后该类在 JavaScript 中即可直接以类名引用。导入整个 Java 包jsRuntimeBuilder.importPackage(android.content);对应源码importPackage(String packageName)JsRuntimeReplFactoryBuilder.java文档注明这会导入包内所有类含枚举与接口。由于应用包名已在构造器中默认导入开发者通常只需额外导入自己用到的框架包。绑定变量jsRuntimeBuilder.addVariable(flag, new AtomicBoolean(true));注意文档明确警告——Java 基本类型会被自动装箱autobox但只有对象才能传入 JavaScript 运行时。源码importVariablesJsRuntimeReplFactoryBuilder.java通过Context.javaToJS(varValue, scope)完成转换若值本身已是ScriptableRhino 宿主对象或Undefined则直接透传。因此addVariable(flag, new AtomicBoolean(true))之后控制台里就能读写flag这个AtomicBoolean实例。绑定顶层函数函数绑定需要开发者自己实现 Rhino 的BaseFunction。官方文档给出的示例是把一个 Java 方法包装成 JavaScript 顶层函数toast(String)// Your application context final Context context this; final Handler handler new Handler(Looper.getMainLooper()); // Add the function: void toast(String) jsRuntimeBuilder.addFunction(toast, new BaseFunction() { Override public Object call(org.mozilla.javascript.Context cx, Scriptable scope, Scriptable thisObj, Object[] args) { // javascript passes the arguments as varags final String message args[0].toString(); handler.post(new Runnable() { Override public void run() { Toast.makeText(context, message, Toast.LENGTH_LONG).show(); } }); // return undef in javascript return org.mozilla.javascript.Context.getUndefinedValue(); } });文档提醒这是一个相对复杂的例子因为 Toast 必须切回主线程执行所以示例把Looper.getMainLooper()的Handler捕获到闭包里在call()中通过handler.post(...)调度 UI 更新最后返回Context.getUndefinedValue()即 JavaScript 里的undefined。绑定后控制台里直接调用toast(hello)即可弹出 Toast。一个完整的控制台实战弹出 Hello from JavaScript官方文档给出的端到端示例——在 Console 面板直接运行importPackage(android.widget); importPackage(android.os); var handler new Handler(Looper.getMainLooper()); handler.post(function() { Toast.makeText(context, Hello from JavaScript, Toast.LENGTH_LONG).show() });这个示例一次性用到了上文提到的所有默认能力importPackage内置函数、全局context变量、以及解释模式下对 Java 对象Handler、Looper、Toast的无缝互操作。console.log 的格式化实现兼容浏览器行为console.log()在浏览器里支持%s、%d、%f等格式占位符。JsConsole.log()JsConsole.java先把参数交给JsFormat.parse(rawArgs)格式化再调用CLog.writeToConsole(MessageLevel.LOG, MessageSource.JAVASCRIPT, message)把消息写入 DevTools Console 面板。JsFormat之所以要自研格式化器而不是直接用String.format()源码注释给出了关键原因JavaScript 的数值全是浮点数%d在 Java 的String.format()中要求 int/Integer会直接抛异常。因此JsFormat用正则解析%[argument_index$][flags][width][.precision]conversion格式串支持d、i、f、s四种转换符并针对 JavaScript 数值做了宽容处理%d取整、%f保留精度、%s直接toString无法解析的占位符原样输出多余的参数追加在末尾。这些行为都有单元测试兜底见 JsFormatTest.java覆盖了带索引的参数%2$s、精度控制%.2f、越界占位符、非法转换符%.2a、%%转义、多余参数拼接等大量边界场景。例如assertEquals(1234.57, JsFormat.parse(%.2f, 1234.5678))验证了%f的精度舍入assertEquals(NaN, JsFormat.parse(%.2f, hello))验证了非法字符串转为 NaN。已知限制与体积优化为什么只能解释执行如工作原理一节所述受限于 Android 运行时不识别 JVM 字节码Rhino 必须运行在解释模式无法启用 JIT 优化。文档强调这对调试场景没有实质性能影响。Dex 方法数Rhino 的体积代价Rhino 不是一个小库。官方文档给出的数据基于stetho-sample编译统计指标Original未加 JSJavaScript加 JSDex 方法数15,46122,749APK 大小1.0M1.6M加入 Rhino 后 Dex 方法数增加了 7,000 以上。其中 Rhino 标准发行版里捆绑的tools包本插件并不需要但依然被打进包里仅该包就贡献了 1,200 个方法。官方文档期待 Rhino 官方拆分更小的构件并建议先用 ProGuard 压缩方法数。开启 ProGuard 后的数据指标OriginalJavaScriptJavaScript去掉toolsDex 方法数8,01315,31614,043APK 大小847K1.5M1.4M可以看到tools包约占 1,200 方法剔除后从 15,316 降到 14,043。ProGuard 规则官方文档给出的标准规则# stetho keep class com.facebook.stetho.** { *; } # rhino (javascript) -dontwarn org.mozilla.javascript.** -dontwarn org.mozilla.classfile.** -keep class org.mozilla.javascript.** { *; }如需激进瘦身、剔除tools包改用按子包精确 keep 的规则# stetho keep class com.facebook.stetho.** { *; } # rhino (javascript) -dontwarn org.mozilla.javascript.** -dontwarn org.mozilla.classfile.** -keep class org.mozilla.classfile.** { *; } -keep class org.mozilla.javascript.* { *; } -keep class org.mozilla.javascript.annotations.** { *; } -keep class org.mozilla.javascript.ast.** { *; } -keep class org.mozilla.javascript.commonjs.module.** { *; } -keep class org.mozilla.javascript.commonjs.module.provider.** { *; } -keep class org.mozilla.javascript.debug.** { *; } -keep class org.mozilla.javascript.jdk13.** { *; } -keep class org.mozilla.javascript.jdk15.** { *; } -keep class org.mozilla.javascript.json.** { *; } -keep class org.mozilla.javascript.optimizer.** { *; } -keep class org.mozilla.javascript.regexp.** { *; } -keep class org.mozilla.javascript.serialize.** { *; } -keep class org.mozilla.javascript.typedarrays.** { *; } -keep class org.mozilla.javascript.v8dtoa.** { *; } -keep class org.mozilla.javascript.xml.** { *; } -keep class org.mozilla.javascript.xmlimpl.** { *; }注意两套规则的区别第一套整体-keep class org.mozilla.javascript.**简单但会连带保留tools包第二套逐个列出需要的子包annotations、ast、commonjs、debug、json、optimizer、regexp、serialize、typedarrays、v8dtoa、xml等未被 keep 的tools包即可被 ProGuard 移除。在示例工程中体验仓库的示例工程 stetho-sample 默认不启用该模块——在 stetho-sample/build.gradle 中stetho-js-rhino以注释形式保留// Uncomment if you wish to play with the Console evaluation // features of the Rhino JS implementation. Disabled by default // because it is a large JAR by comparison to the rest of // Stetho. //debugCompile project(:stetho-js-rhino)注释说明了关闭原因与 Stetho 其余部分相比它是个大 JAR故默认不引入。在 Debug 构建中解开这行注释配合 SampleDebugApplication.java 的 Stetho 初始化即可体验 Console 求值。这也是把该插件限定在 Debug 构建debugImplementation/debugCompile的官方示范以避免把 Rhino 打进 Release 包。小结stetho-js-rhino以极小的接入成本一个依赖、零配置自动启用为 Stetho 补上了 Chrome DevTools Console 的实时求值能力核心是JsRuntimeReplFactoryBuilder环境配置与JsRuntimeRepl解释执行的组合辅以ImporterTopLevel增强作用域、自动注入的context/$_/console、以及兼容浏览器的console.log格式化。需要权衡的则是 Rhino 带来的 Dex 方法数增长——官方文档给出的 ProGuard 规则和tools包剔除方案是控制体积的标准做法。对于需要在真机调试阶段快速操作对象、调用方法、验证业务逻辑的开发者这是一个即插即用的调试利器。赞分享开发工具移动开发【免费下载链接】stethoStetho is a debug bridge for Android applications, enabling the powerful Chrome Developer Tools and much more.项目地址https://gitcode.com/gh_mirrors/st/stetho点击查看免费下载相关推荐容器管理新选择为什么Podman Desktop Companion用户纷纷转向Container Desktop容器管理新选择为什么Podman Desktop Companion用户纷纷转向Container Desktop Podman Desktop Compa5分钟解锁电影级视频生成WAN2.2-14B极速工作流深度解析5分钟解锁电影级视频生成WAN2.2 14B极速工作流深度解析 阿里云通义万相团队推出的WAN2.2 14B Rapid AllInOne模型正在重新定义视频人工智能计算机视觉基础模型5分钟上手srvx从安装到启动跨平台服务器的快速教程5分钟上手srvx从安装到启动跨平台服务器的快速教程 srvx是一款基于Web标准的跨平台通用服务器能够帮助开发者快速构建和部署服务器应用。本教程将带你在5创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考