com.facebook.stetho.Stetho.java Source code

Java tutorial

Introduction

Here is the source code for com.facebook.stetho.Stetho.java

Source

/*
 * Copyright (c) 2014-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 */

package com.facebook.stetho;

import com.facebook.stetho.dumpapp.plugins.CrashDumperPlugin;
import com.facebook.stetho.dumpapp.plugins.FilesDumperPlugin;
import com.facebook.stetho.dumpapp.plugins.HprofDumperPlugin;
import com.facebook.stetho.inspector.console.RuntimeReplFactory;
import com.facebook.stetho.inspector.database.DatabaseFilesProvider;
import com.facebook.stetho.inspector.database.DefaultDatabaseFilesProvider;
import javax.annotation.Nullable;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;

import android.app.Application;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Build;

import com.facebook.stetho.common.LogUtil;
import com.facebook.stetho.common.Utf8Charset;
import com.facebook.stetho.common.Util;
import com.facebook.stetho.dumpapp.Dumper;
import com.facebook.stetho.dumpapp.DumperPlugin;
import com.facebook.stetho.dumpapp.RawDumpappHandler;
import com.facebook.stetho.dumpapp.StreamingDumpappHandler;
import com.facebook.stetho.dumpapp.plugins.SharedPreferencesDumperPlugin;
import com.facebook.stetho.inspector.ChromeDevtoolsServer;
import com.facebook.stetho.inspector.ChromeDiscoveryHandler;
import com.facebook.stetho.inspector.elements.Document;
import com.facebook.stetho.inspector.elements.DocumentProviderFactory;
import com.facebook.stetho.inspector.elements.android.ActivityTracker;
import com.facebook.stetho.inspector.elements.android.AndroidDocumentConstants;
import com.facebook.stetho.inspector.elements.android.AndroidDocumentProviderFactory;
import com.facebook.stetho.inspector.protocol.ChromeDevtoolsDomain;
import com.facebook.stetho.inspector.protocol.module.CSS;
import com.facebook.stetho.inspector.protocol.module.Console;
import com.facebook.stetho.inspector.protocol.module.DOM;
import com.facebook.stetho.inspector.protocol.module.DOMStorage;
import com.facebook.stetho.inspector.protocol.module.Database;
import com.facebook.stetho.inspector.protocol.module.DatabaseConstants;
import com.facebook.stetho.inspector.protocol.module.Debugger;
import com.facebook.stetho.inspector.protocol.module.HeapProfiler;
import com.facebook.stetho.inspector.protocol.module.Inspector;
import com.facebook.stetho.inspector.protocol.module.Network;
import com.facebook.stetho.inspector.protocol.module.Page;
import com.facebook.stetho.inspector.protocol.module.Profiler;
import com.facebook.stetho.inspector.protocol.module.Runtime;
import com.facebook.stetho.inspector.protocol.module.Worker;
import com.facebook.stetho.inspector.runtime.RhinoDetectingRuntimeReplFactory;
import com.facebook.stetho.server.LocalSocketHttpServer;
import com.facebook.stetho.server.RegistryInitializer;
import com.facebook.stetho.websocket.WebSocketHandler;

import org.apache.http.HttpException;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.entity.StringEntity;
import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.HttpRequestHandler;
import org.apache.http.protocol.HttpRequestHandlerRegistry;

/**
 * Initialization and configuration entry point for the Stetho debugging system.  Simple usage with
 * default plugins and features enabled:
 * <p />
 * <pre>
 *   Stetho.initializeWithDefaults(context)
 * </pre>
 * <p />
 * For more advanced configuration, see {@link #newInitializerBuilder(Context)} or
 * the {@code stetho-sample} for more information.
 */
public class Stetho {
    private static final String LISTENER_THREAD_NAME = "Stetho-Listener";

    private Stetho() {
    }

    /**
     * Construct a simple initializer helper which allows you to customize stetho behaviour
     * with additional features, plugins, etc.  See {@link DefaultDumperPluginsBuilder} and
     * {@link DefaultInspectorModulesBuilder} for more information.
     * <p />
     * For simple use cases, consider {@link #initializeWithDefaults(Context)}.
     */
    public static InitializerBuilder newInitializerBuilder(Context context) {
        return new InitializerBuilder(context);
    }

    /**
     * Start the listening server.  Most of the heavy lifting initialization is deferred until the
     * first socket connection is received, allowing this to be safely used for debug builds on
     * even low-end hardware without noticeably affecting performance.
     */
    public static void initializeWithDefaults(final Context context) {
        initialize(new Initializer(context) {
            @Override
            protected Iterable<DumperPlugin> getDumperPlugins() {
                return new DefaultDumperPluginsBuilder(context).finish();
            }

            @Override
            protected Iterable<ChromeDevtoolsDomain> getInspectorModules() {
                return new DefaultInspectorModulesBuilder(context).finish();
            }
        });
    }

    /**
     * Start the listening service, providing a custom initializer as per
     * {@link #newInitializerBuilder}.
     *
     * @see #initializeWithDefaults(Context)
     */
    public static void initialize(final Initializer initializer) {
        // Hook activity tracking so that after Stetho is attached we can figure out what
        // activities are present.
        boolean isTrackingActivities = ActivityTracker.get()
                .beginTrackingIfPossible((Application) initializer.mContext.getApplicationContext());
        if (!isTrackingActivities) {
            LogUtil.w("Automatic activity tracking not available on this API level, caller must invoke "
                    + "ActivityTracker methods manually!");
        }

        Thread listener = new Thread(LISTENER_THREAD_NAME) {
            @Override
            public void run() {
                LocalSocketHttpServer server = new LocalSocketHttpServer(initializer);
                try {
                    server.run();
                } catch (IOException e) {
                    LogUtil.e(e, "Could not start Stetho");
                }
            }
        };
        listener.start();
    }

    public static DumperPluginsProvider defaultDumperPluginsProvider(final Context context) {
        return new DumperPluginsProvider() {
            @Override
            public Iterable<DumperPlugin> get() {
                return new DefaultDumperPluginsBuilder(context).finish();
            }
        };
    }

    public static InspectorModulesProvider defaultInspectorModulesProvider(final Context context) {
        return new InspectorModulesProvider() {
            @Override
            public Iterable<ChromeDevtoolsDomain> get() {
                return new DefaultInspectorModulesBuilder(context).finish();
            }
        };
    }

    private static class PluginBuilder<T> {
        private final Set<String> mProvidedNames = new HashSet<>();
        private final Set<String> mRemovedNames = new HashSet<>();

        private final ArrayList<T> mPlugins = new ArrayList<>();

        private boolean mFinished;

        public void provide(String name, T plugin) {
            throwIfFinished();
            mPlugins.add(plugin);
            mProvidedNames.add(name);
        }

        public void provideIfDesired(String name, T plugin) {
            throwIfFinished();
            if (!mRemovedNames.contains(name)) {
                if (mProvidedNames.add(name)) {
                    mPlugins.add(plugin);
                }
            }
        }

        public void remove(String pluginName) {
            throwIfFinished();
            mRemovedNames.remove(pluginName);
        }

        private void throwIfFinished() {
            if (mFinished) {
                throw new IllegalStateException("Must not continue to build after finish()");
            }
        }

        public Iterable<T> finish() {
            mFinished = true;
            return mPlugins;
        }
    }

    /**
     * Convenience mechanism to extend the default set of dumper plugins provided by Stetho.
     *
     * @see #initializeWithDefaults(Context)
     */
    public static final class DefaultDumperPluginsBuilder {
        private final Context mContext;
        private final PluginBuilder<DumperPlugin> mDelegate = new PluginBuilder<>();

        public DefaultDumperPluginsBuilder(Context context) {
            mContext = context;
        }

        public DefaultDumperPluginsBuilder provide(DumperPlugin plugin) {
            mDelegate.provide(plugin.getName(), plugin);
            return this;
        }

        private DefaultDumperPluginsBuilder provideIfDesired(DumperPlugin plugin) {
            mDelegate.provideIfDesired(plugin.getName(), plugin);
            return this;
        }

        public DefaultDumperPluginsBuilder remove(String pluginName) {
            mDelegate.remove(pluginName);
            return this;
        }

        public Iterable<DumperPlugin> finish() {
            provideIfDesired(new HprofDumperPlugin(mContext));
            provideIfDesired(new SharedPreferencesDumperPlugin(mContext));
            provideIfDesired(new CrashDumperPlugin());
            provideIfDesired(new FilesDumperPlugin(mContext));
            return mDelegate.finish();
        }
    }

    /**
     * Configuration mechanism to customize the behaviour of the standard set of inspector
     * modules satisfying the Chrome DevTools protocol.  Note that while it is still technically
     * possible to manually control these modules, this API is strongly discouraged and will not
     * necessarily be supported in future releases.
     */
    public static final class DefaultInspectorModulesBuilder {
        private final Application mContext;
        private final PluginBuilder<ChromeDevtoolsDomain> mDelegate = new PluginBuilder<>();

        @Nullable
        private DocumentProviderFactory mDocumentProvider;
        @Nullable
        private RuntimeReplFactory mRuntimeRepl;
        @Nullable
        private DatabaseFilesProvider mDatabaseFiles;

        public DefaultInspectorModulesBuilder(Context context) {
            mContext = (Application) context.getApplicationContext();
        }

        /**
         * Provide a custom document provider factory which can operate on the logical DOM exposed to
         * Chrome in the Elements tab.  An Android View hierarchy instance is provided by
         * default if this method is not called.
         * <p />
         * <i>Experimental.</i>  This API may be changed or removed in the future.
         */
        public DefaultInspectorModulesBuilder documentProvider(DocumentProviderFactory factory) {
            mDocumentProvider = factory;
            return this;
        }

        /**
         * Provide a custom runtime REPL (read-eval-print loop) implementation for the Console tab.
         * By default an implementation will be provided for you that automatically detects
         * the existence of {@code stetho-js-rhino} (Mozilla's Rhino engine) and uses it if available.
         * <p />
         * To customize the Rhino implementation, see {@code stetho-js-rhino} documentation.
         */
        public DefaultInspectorModulesBuilder runtimeRepl(RuntimeReplFactory factory) {
            mRuntimeRepl = factory;
            return this;
        }

        /**
         * Customize the location of database files that Stetho will propogate in the UI.  Android's
         * {@link Context#getDatabasePath} method will be used by default if not overridden here.
         */
        public DefaultInspectorModulesBuilder databaseFiles(DatabaseFilesProvider provider) {
            mDatabaseFiles = provider;
            return this;
        }

        /**
         * Provide either a new domain module or override an existing one.
         *
         * @deprecated This fine-grained control of the devtools modules is no longer supportable
         *     given the lack of isolation of modules in the actual protocol (many cross dependencies
         *     emerge when you implement more and more of the real protocol).
         */
        @Deprecated
        public DefaultInspectorModulesBuilder provide(ChromeDevtoolsDomain module) {
            mDelegate.provide(module.getClass().getName(), module);
            return this;
        }

        private DefaultInspectorModulesBuilder provideIfDesired(ChromeDevtoolsDomain module) {
            mDelegate.provideIfDesired(module.getClass().getName(), module);
            return this;
        }

        /**
         * Remove an existing domain module.
         *
         * @deprecated This fine-grained control of the devtools modules is no longer supportable
         *     given the lack of isolation of modules in the actual protocol (many cross dependencies
         *     emerge when you implement more and more of the real protocol).
         */
        @Deprecated
        public DefaultInspectorModulesBuilder remove(String moduleName) {
            mDelegate.remove(moduleName);
            return this;
        }

        public Iterable<ChromeDevtoolsDomain> finish() {
            provideIfDesired(new Console());
            provideIfDesired(new Debugger());
            DocumentProviderFactory documentModel = resolveDocumentProvider();
            if (documentModel != null) {
                Document document = new Document(documentModel);
                provideIfDesired(new DOM(document));
                provideIfDesired(new CSS(document));
            }
            provideIfDesired(new DOMStorage(mContext));
            provideIfDesired(new HeapProfiler());
            provideIfDesired(new Inspector());
            provideIfDesired(new Network(mContext));
            provideIfDesired(new Page(mContext));
            provideIfDesired(new Profiler());
            provideIfDesired(new Runtime(
                    mRuntimeRepl != null ? mRuntimeRepl : new RhinoDetectingRuntimeReplFactory(mContext)));
            provideIfDesired(new Worker());
            if (Build.VERSION.SDK_INT >= DatabaseConstants.MIN_API_LEVEL) {
                provideIfDesired(new Database(mContext,
                        mDatabaseFiles != null ? mDatabaseFiles : new DefaultDatabaseFilesProvider(mContext)));
            }
            return mDelegate.finish();
        }

        @Nullable
        private DocumentProviderFactory resolveDocumentProvider() {
            if (mDocumentProvider != null) {
                return mDocumentProvider;
            }
            if (Build.VERSION.SDK_INT >= AndroidDocumentConstants.MIN_API_LEVEL) {
                return new AndroidDocumentProviderFactory(mContext);
            }
            return null;
        }
    }

    /**
     * Callers can choose to subclass this directly to provide the initialization configuration
     * or they can construct a concrete instance using {@link #newInitializerBuilder(Context)}.
     */
    public static abstract class Initializer implements RegistryInitializer {
        private final Context mContext;

        protected Initializer(Context context) {
            mContext = context.getApplicationContext();
        }

        @Override
        public final HttpRequestHandlerRegistry getRegistry() {
            HttpRequestHandlerRegistry registry = new HttpRequestHandlerRegistry();

            Iterable<DumperPlugin> dumperPlugins = getDumperPlugins();
            if (dumperPlugins != null) {
                Dumper dumper = new Dumper(dumperPlugins);

                registry.register("/dumpapp", new StreamingDumpappHandler(mContext, dumper));
                registry.register("/dumpapp-raw", new RawDumpappHandler(mContext, dumper));
            }

            Iterable<ChromeDevtoolsDomain> inspectorModules = getInspectorModules();
            if (inspectorModules != null) {
                ChromeDiscoveryHandler discoveryHandler = new ChromeDiscoveryHandler(mContext,
                        ChromeDevtoolsServer.PATH);
                discoveryHandler.register(registry);
                registry.register(ChromeDevtoolsServer.PATH,
                        new WebSocketHandler(mContext, new ChromeDevtoolsServer(inspectorModules)));
            }

            addCustomEntries(registry);

            registry.register("/*", new LoggingCatchAllHandler());

            return registry;
        }

        @Nullable
        protected abstract Iterable<DumperPlugin> getDumperPlugins();

        @Nullable
        protected abstract Iterable<ChromeDevtoolsDomain> getInspectorModules();

        protected void addCustomEntries(HttpRequestHandlerRegistry registry) {
            // Override to add stuff...
        }

        private static class LoggingCatchAllHandler implements HttpRequestHandler {
            @Override
            public void handle(HttpRequest request, HttpResponse response, HttpContext context)
                    throws HttpException, IOException {
                LogUtil.w("Unsupported request received: " + request.getRequestLine());
                response.setStatusCode(HttpStatus.SC_NOT_FOUND);
                response.setReasonPhrase("Not Found");
                response.setEntity(new StringEntity("Endpoint not implemented\n", Utf8Charset.NAME));
            }
        }
    }

    /**
     * Configure what services are to be enabled in this instance of Stetho.
     */
    public static class InitializerBuilder {
        final Context mContext;

        @Nullable
        DumperPluginsProvider mDumperPlugins;
        @Nullable
        InspectorModulesProvider mInspectorModules;

        private InitializerBuilder(Context context) {
            mContext = context.getApplicationContext();
        }

        /**
         * Enable use of the {@code dumpapp} system.  This is an extension to Stetho which allows
         * developers to configure custom debug endpoints as tiny programs embedded inside of a larger
         * running Android application.  Examples of this would be simple utilities to visualize and
         * edit {@link SharedPreferences} data, kick off sync or other background tasks, inject custom
         * data temporarily into the process for debugging/reproducibility, upload error reports,
         * etc.
         * <p>
         * See {@code ./scripts/dumpapp} for more information on how to use this system once
         * enabled.
         *
         * @param plugins The set of plugins to use.
         */
        public InitializerBuilder enableDumpapp(DumperPluginsProvider plugins) {
            mDumperPlugins = Util.throwIfNull(plugins);
            return this;
        }

        public InitializerBuilder enableWebKitInspector(InspectorModulesProvider modules) {
            mInspectorModules = modules;
            return this;
        }

        public Initializer build() {
            return new BuilderBasedInitializer(this);
        }
    }

    private static class BuilderBasedInitializer extends Initializer {
        @Nullable
        private final DumperPluginsProvider mDumperPlugins;
        @Nullable
        private final InspectorModulesProvider mInspectorModules;

        private BuilderBasedInitializer(InitializerBuilder b) {
            super(b.mContext);
            mDumperPlugins = b.mDumperPlugins;
            mInspectorModules = b.mInspectorModules;
        }

        @Nullable
        @Override
        protected Iterable<DumperPlugin> getDumperPlugins() {
            return mDumperPlugins != null ? mDumperPlugins.get() : null;
        }

        @Nullable
        @Override
        protected Iterable<ChromeDevtoolsDomain> getInspectorModules() {
            return mInspectorModules != null ? mInspectorModules.get() : null;
        }
    }
}