Stetho

repository·main·Indexed 10 days ago

https://github.com/facebook/stetho

A debug bridge for Android applications that enables the use of Chrome Developer Tools to inspect app internals, including network traffic and the JavaScript console. It provides support for OkHttp 3.x and HttpURLConnection, a JavaScript runtime via Rhino, and a custom framing protocol for the dumpapp CLI tool.

Tokens
3.4K
Snippets
10
Records
13
Agent score
95%

What's inside Stetho

  1. Install Stetho via Gradle or Maven

    main

    To use Stetho in your Android project, add the core dependency to your build configuration. You can also optionally add network inspection helpers or JavaScript console support.

    Core Dependency: Required for basic Stetho functionality.

    Network Helpers:

    • stetho-okhttp3: For OkHttp 3.x users.
    • stetho-urlconnection: For HttpURLConnection users.

    JavaScript Console:

    • stetho-js-rhino: Enables a JavaScript console via Rhino.
    // Gradle
    implementation 'com.facebook.stetho:stetho:1.6.0'
    implementation 'com.facebook.stetho:stetho-okhttp3:1.6.0'
    implementation 'com.facebook.stetho:stetho-urlconnection:1.6.0'
    implementation 'com.facebook.stetho:stetho-js-rhino:1.6.0'
    <!-- Maven -->
    <dependency>
      <groupId>com.facebook.stetho</groupId>
      <artifactId>stetho</artifactId>
      <version>1.6.0</version>
    </dependency>
  2. Install Stetho's JavaScript Module

    main

    To use the JavaScript console in Stetho, add the stetho-js-rhino dependency to your project. You must also include the main stetho dependency.

    Gradle:

    implementation 'com.facebook.stetho:stetho-js-rhino:1.4.2'

    Maven:

    <dependency>
      <groupId>com.facebook.stetho</groupId>
      <artifactId>stetho-js-rhino</artifactId>
      <version>1.4.2</version>
    </dependency>
    implementation 'com.facebook.stetho:stetho-js-rhino:1.4.2'
  3. Enable the JavaScript console in Stetho

    main

    The Rhino JavaScript integration is automatically detected by Stetho once the dependency is added. To configure the JavaScript environment (e.g., adding custom variables or classes), use Stetho.newInitializerBuilder with an InspectorModulesProvider that utilizes JsRuntimeReplFactoryBuilder.

        Stetho.initialize(Stetho.newInitializerBuilder(context)
            .enableWebKitInspector(new InspectorModulesProvider() {
              @Override
              public Iterable<ChromeDevtoolsDomain> get() {
                return new DefaultInspectorModulesBuilder(context).runtimeRepl(
                    new JsRuntimeReplFactoryBuilder(context)
                        // Pass to JavaScript: var foo = "bar";
                        .addVariable("foo", "bar")
                        .build()
                ).finish();
              }
            })
            .build();
  4. Enable network inspection for OkHttp 3.x

    main

    If your application uses OkHttp 3.x, you can inspect network traffic by adding the StethoInterceptor to your OkHttpClient builder.

    Best Practice: Add the Stetho interceptor after all other interceptors. Since interceptors can modify requests and responses, placing Stetho last ensures you see the actual traffic being sent/received by the network.

    new OkHttpClient.Builder()
        .addNetworkInterceptor(new StethoInterceptor())
        .build()
  5. Enable network inspection for HttpURLConnection

    main

    For applications using HttpURLConnection, use StethoURLConnectionManager to integrate Stetho.

    Caveat: You must explicitly add Accept-Encoding: gzip to your request headers and manually handle compressed responses. This is required for Stetho to correctly report compressed payload sizes.

  6. Initialize Stetho in your Application class

    main

    To enable the debug bridge, call Stetho.initializeWithDefaults(this) within your Application class's onCreate method.

    Important: You must ensure your custom Application class is registered in your AndroidManifest.xml using the android:name attribute, otherwise the device will not appear in chrome://inspect.

    public class MyApplication extends Application {
      public void onCreate() {
        super.onCreate();
        Stetho.initializeWithDefaults(this);
      }
    }
    <!-- AndroidManifest.xml -->
    <application
            android:name="MyApplication"
            ...>
     </application>
  7. Understand the dumpapp framing protocol

    main

    The dumpapp CLI tool uses a custom framing protocol over sockets to simulate a standard command-line environment (stdin, stdout, stderr, exit codes, and arguments). The protocol uses 5-byte fixed headers followed by an optional variable-size content body.

    Grammar

    Frame TypePrefixDescription
    STDIN_REQUEST_FRAME_A request for a specific number of bytes (BIG_ENDIAN_INT).
    STDIN_FRAME-Contains a BLOB of data (BIG_ENDIAN_INT size + data).
    STDOUT_FRAME1Contains a BLOB of data (BIG_ENDIAN_INT size + data).
    STDERR_FRAME2Contains a BLOB of data (BIG_ENDIAN_INT size + data).
    ENTER_FRAME!Contains the number of arguments (BIG_ENDIAN_INT) followed by zero or more UTF-8 strings (BIG_ENDIAN_SHORT size + string).
    EXIT_FRAMExContains the exit code (BIG_ENDIAN_INT).

    Data Types

    • BIG_ENDIAN_INT: 4 bytes (as written by DataOutputStream.writeInt). Used for blob sizes, argument counts, or exit codes.
    • BIG_ENDIAN_SHORT: 2 bytes (as written by DataOutputStream.writeShort). Used for string lengths.
    • BLOB: A variable-size byte array.
    • STRING: A variable-size UTF-8 string.
  8. Run JavaScript commands in the app

    main

    Once enabled, you can run live JavaScript commands from the console. The runtime includes built-in utilities like importClass, importPackage, and a console.log() method. By default, your application's package is also imported, allowing access to resources like R.string.app.

    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();
    });
  9. Generate Java classes from protocol.json using the scraper

    main

    The scraper.js tool can be used to generate Java class definitions based on the Chrome DevTools protocol (protocol.json). By providing a specific class name (e.g., Debugger.FunctionDetails), the scraper parses the protocol file and produces the corresponding Java source code with Jackson annotations (@JsonProperty).

    To use the scraper, run the following command from your terminal:

    node scraper.js protocol.json Debugger.FunctionDetails

    Arguments:

    • protocol.json: The path to the protocol definition file (typically downloaded from Google Code).
    • Debugger.FunctionDetails: The specific namespace and class you wish to generate.

    Note: If the domain is not explicitly specified in the command, it is assumed to be implicit based on the protocol file content.

  10. Extend dumpapp with custom DumperPlugins

    main

    You can extend the dumpapp command-line tool by providing custom DumperPlugin implementations. Instead of using initializeWithDefaults, use Stetho.initialize with a custom InitializerBuilder to register your plugins via a DumperPluginsProvider.

    Stetho.initialize(Stetho.newInitializerBuilder(context)
        .enableDumpapp(new DumperPluginsProvider() {
          @Override
          public Iterable<DumperPlugin> get() {
            return new Stetho.DefaultDumperPluginsBuilder(context)
                .provide(new MyDumperPlugin())
                .finish();
          }
        })
        .enableWebKitInspector(Stetho.defaultInspectorModulesProvider(context))
        .build())
  11. Customize the JavaScript runtime with JsRuntimeReplFactoryBuilder

    main

    You can enhance the JavaScript runtime scope by preloading classes, packages, variables, and functions using JsRuntimeReplFactoryBuilder. This allows your Java classes and objects to be accessed directly from the JavaScript console.

    Import a class

    Use importClass(Class<?> clazz) to make a specific Java class available.

    Import a package

    Use importPackage(String packageName) to make all classes in a Java package available.

    Variable binding

    Use addVariable(String name, Object value) to bind a variable. Note that Java primitive types are autoboxed; only objects can be passed.

    Function binding

    Use addFunction(String name, BaseFunction function) to define a top-level JavaScript function. You must implement the call method from BaseFunction.

    // Initialize builder
    JsRuntimeReplFactoryBuilder jsRuntimeBuilder = new JsRuntimeReplFactoryBuilder(context);
    
    // Import a class
    jsRuntimeBuilder.importClass(R.class);
    
    // Import a package
    jsRuntimeBuilder.importPackage("android.content");
    
    // Bind a variable
    jsRuntimeBuilder.addVariable("flag", new AtomicBoolean(true));
    
    // Bind a function
    jsRuntimeBuilder.addFunction("toast", new BaseFunction() {
      @Override
      public Object call(org.mozilla.javascript.Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
        final String message = args[0].toString();
        // ... implementation ...
        return org.mozilla.javascript.Context.getUndefinedValue();
      }
    });
  12. Configure Proguard for Stetho and Rhino

    main

    Because Rhino is a large library, it significantly increases the Dex method count. It is highly recommended to use Proguard to shrink the footprint.

    Standard Proguard rules:

    # stetho
    +keep class com.facebook.stetho.** { *; }
    
    # rhino (javascript)
    -dontwarn org.mozilla.javascript.**
    -dontwarn org.mozilla.classfile.**
    -keep class org.mozilla.javascript.** { *; }

    Aggressive Proguard rules (to remove the tools package):

    # 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.** { *; }