• Android WebView 的 addJavascriptInterface 探究


    一、前言

    Java和JS交互的方式有多种,这里探讨的方式是通过以下方式进行的交互。

    webView.addJavascriptInterface(this, "JSBridge")

    这篇文章是想弄明白 JavaScript 和 Java是如何实现这种方式互调的,就从源码角度开始分析 。

    二、分析

    1. 图示调用关系

    上面这张调用关系流程图,关于源码是基于Android4.4 源码进行分析,Android在4.4将WebView内核改为 chromium ,在Android4.4以上的系统源码会有所调整。在 翻阅Android 6.0的时候,源码中提示 :Building the Chromium-based WebView in AOSP is no longer supported. WebView can now be built entirely from the Chromium source code. 这个句话的意思是基于chromium 的WebView不再在安卓开放源代码项目,所以如果你在6.0以上的版本中将找不到下面即将分析的源代码。具体的调整你可以自行查阅。

    2. 源码分析

      调用入口:

    private WebViewProvider mProvider;

    /** * Injects the supplied Java object into this WebView. The object is * injected into the JavaScript context of the main frame, using the * supplied name. This allows the Java object's methods to be * accessed from JavaScript. */ public void addJavascriptInterface(Object object, String name) { checkThread(); mProvider.addJavascriptInterface(object, name); }

      从源码可以看到实际调用为内部成员变量 WebViewProvider 的 addJavascriptInterface方法。继续深入发现  WebViewProvider  是一个接口,我们要找到具体的实现类。那么要看 mProvider 是怎么实例化的。

    private static synchronized WebViewFactoryProvider getFactory() {
        return WebViewFactory.getProvider();
    }
    private void ensureProviderCreated() { checkThread(); if (mProvider == null) { // As this can get called during the base class constructor chain, pass the minimum // number of dependencies here; the rest are deferred to init(). mProvider = getFactory().createWebView(this, new PrivateAccess()); } }

    通过源码可以知道实际创建 WebViewProvider 实例的是通过调用 WebViewFactory的 getProvider()方法,获取到 WebViewFactoryProvider 对象(注意和前者WebViewProvider的区别),然后再调用 WebViewFactoryProvider 的 createWebView 方法。那我们继续往下分析它的 getProvider() 方法。从这里也可以看出源码基本上都是基于接口编程,好处谁用谁知道。

    • WebViewFactory 
    private static final String CHROMIUM_WEBVIEW_FACTORY = "com.android.webview.chromium.WebViewChromiumFactoryProvider";
    
    static WebViewFactoryProvider getProvider() {
            synchronized (sProviderLock) {
                // For now the main purpose of this function (and the factory abstraction) is to keep
               // us honest and minimize usage of WebView internals when binding the proxy.
                if (sProviderInstance != null) return sProviderInstance;
    
                Class<WebViewFactoryProvider> providerClass;
                try {
                    providerClass = getFactoryClass();
                } catch (ClassNotFoundException e) {
                    Log.e(LOGTAG, "error loading provider", e);
                    throw new AndroidRuntimeException(e);
                }
    
                // This implicitly loads Preloader even if it wasn't preloaded at boot.
                if (Preloader.sPreloadedProvider != null &&
                    Preloader.sPreloadedProvider.getClass() == providerClass) {
                    sProviderInstance = Preloader.sPreloadedProvider;
                    if (DEBUG) Log.v(LOGTAG, "Using preloaded provider: " + sProviderInstance);
                    return sProviderInstance;
                }
    
                // The preloaded provider isn't the one we wanted; construct our own.
                StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
                try {
                    sProviderInstance = providerClass.newInstance();
                    if (DEBUG) Log.v(LOGTAG, "Loaded provider: " + sProviderInstance);
                    return sProviderInstance;
                } catch (Exception e) {
                    Log.e(LOGTAG, "error instantiating provider", e);
                    throw new AndroidRuntimeException(e);
                } finally {
                    StrictMode.setThreadPolicy(oldPolicy);
                }
            }
        }
    
        private static Class<WebViewFactoryProvider> getFactoryClass() throws ClassNotFoundException {
            return (Class<WebViewFactoryProvider>) Class.forName(CHROMIUM_WEBVIEW_FACTORY);
    }

    上面标红的即为关键代码,通过源码我们知道 WebViewFactoryProvider 是通过反射  "com.android.webview.chromium.WebViewChromiumFactoryProvider" 创建的,那么我们需要找到 WebViewChromiumFactoryProvider 这个类,并且找到它的 createWebView 方法,看看里面做了什么,使用什么实例化了上面的 mProvider。

    public class WebViewChromiumFactoryProvider implements WebViewFactoryProvider {
        ....
    @Override
    public WebViewProvider createWebView(WebView webView, WebView.PrivateAccess privateAccess) { WebViewChromium wvc = new WebViewChromium(this, webView, privateAccess); synchronized (mLock) { if (mWebViewsToStart != null) { mWebViewsToStart.add(new WeakReference<WebViewChromium>(wvc)); } } ResourceProvider.registerResources(webView.getContext()); return wvc; }

      ... }

    看得出  mProvider 就是引用了 WebViewChromium 实例,也就是说在一开始入口处  mProvider.addJavascriptInterface(object, name) 就是调用 WebViewChromium 的 addJavascriptInterface 。终于找到了本尊,哈哈,兜兜转转就是为了抽象。接下来我们就看WebViewChromium

    • WebViewChromium
    class WebViewChromium implements WebViewProvider, WebViewProvider.ScrollDelegate, WebViewProvider.ViewDelegate {
         ......
    // The WebView wrapper for ContentViewCore and required browser compontents.
       private AwContents mAwContents;
         ......
        private void initForReal() {
            mAwContents = new AwContents(mFactory.getBrowserContext(), mWebView,
                    new InternalAccessAdapter(), mContentsClientAdapter, new AwLayoutSizer(),
                    mWebSettings.getAwSettings());
    
            if (mAppTargetSdkVersion >= Build.VERSION_CODES.KITKAT) {
                // On KK and above, favicons are automatically downloaded as the method
                // old apps use to enable that behavior is deprecated.
                AwContents.setShouldDownloadFavicons();
            }
        }
         ......
    
        @Override
        public void loadUrl(final String url, Map<String, String> additionalHttpHeaders) {
            // TODO: We may actually want to do some sanity checks here (like filter about://chrome).
    
            // For backwards compatibility, apps targeting less than K will have JS URLs evaluated
            // directly and any result of the evaluation will not replace the current page content.
            // Matching Chrome behavior more closely; apps targetting >= K that load a JS URL will
            // have the result of that URL replace the content of the current page.
            final String JAVASCRIPT_SCHEME = "javascript:";
            if (mAppTargetSdkVersion < Build.VERSION_CODES.KITKAT &&
                    url != null && url.startsWith(JAVASCRIPT_SCHEME)) {
                mFactory.startYourEngines(true);
                if (checkNeedsPost()) {
                    mRunQueue.addTask(new Runnable() {
                        @Override
                        public void run() {
                            mAwContents.evaluateJavaScriptEvenIfNotYetNavigated(
                                    url.substring(JAVASCRIPT_SCHEME.length()));
                        }
                    });
                } else {
                    mAwContents.evaluateJavaScriptEvenIfNotYetNavigated(
                            url.substring(JAVASCRIPT_SCHEME.length()));
                }
                return;
            }
    
            LoadUrlParams params = new LoadUrlParams(url);
            if (additionalHttpHeaders != null) params.setExtraHeaders(additionalHttpHeaders);
            loadUrlOnUiThread(params);
        }
    
        ......
    
      @Override
        public void addJavascriptInterface(final Object obj, final String interfaceName) {
            if (checkNeedsPost()) {
                mRunQueue.addTask(new Runnable() {
                    @Override
                    public void run() {
                        addJavascriptInterface(obj, interfaceName);
                    }
                });
                return;
            }
            Class<? extends Annotation> requiredAnnotation = null;
            if (mAppTargetSdkVersion >= Build.VERSION_CODES.JELLY_BEAN_MR1) { //SDK 17 android 4.2
               requiredAnnotation = JavascriptInterface.class;
            }
            mAwContents.addPossiblyUnsafeJavascriptInterface(obj, interfaceName, requiredAnnotation);
        }
    
    ......
        
    }

    源码中我保留了 loadUrl ,就是为了引起你的注意,你能探索的不止这一点点。源码中高亮标红的代码,是我们特别关注的。  WebViewChromium 的 addJavascriptInterface 先做了一个版本判断,判断是否需要 JavascriptInterface 注解标记。然后调用了 AwContents 的addPossiblyUnsafeJavascriptInterface 方法。好家伙,这个路是真长,继续往下看。

    @JNINamespace("android_webview")
    public class AwContents {
    ......
    private ContentViewCore mContentViewCore;
    ......
    /**
        * @see ContentViewCore#addPossiblyUnsafeJavascriptInterface(Object, String, Class)
         */
        public void addPossiblyUnsafeJavascriptInterface(Object object, String name,
                Class<? extends Annotation> requiredAnnotation) {
            mContentViewCore.addPossiblyUnsafeJavascriptInterface(object, name, requiredAnnotation);
        }
    
    ......
    
    }

    废话不多说,继续往下。

    @JNINamespace("content")
        public class ContentViewCore implements MotionEventDelegate,
                                                NavigationClient,
                                                AccessibilityStateChangeListener {
    
        ......
    
     /**
         * This method injects the supplied Java object into the ContentViewCore.
         * The object is injected into the JavaScript context of the main frame,
         * using the supplied name. This allows the Java object to be accessed from
         * JavaScript. Note that that injected objects will not appear in
         * JavaScript until the page is next (re)loaded. For example:
         * <pre> view.addJavascriptInterface(new Object(), "injectedObject");
         * view.loadData("<!DOCTYPE html><title></title>", "text/html", null);
         * view.loadUrl("javascript:alert(injectedObject.toString())");</pre>
         * <p><strong>IMPORTANT:</strong>
         * <ul>
         * <li> addJavascriptInterface() can be used to allow JavaScript to control
         * the host application. This is a powerful feature, but also presents a
         * security risk. Use of this method in a ContentViewCore containing
         * untrusted content could allow an attacker to manipulate the host
         * application in unintended ways, executing Java code with the permissions
         * of the host application. Use extreme care when using this method in a
         * ContentViewCore which could contain untrusted content. Particular care
         * should be taken to avoid unintentional access to inherited methods, such
         * as {@link Object#getClass()}. To prevent access to inherited methods,
         * pass an annotation for {@code requiredAnnotation}.  This will ensure
         * that only methods with {@code requiredAnnotation} are exposed to the
         * Javascript layer.  {@code requiredAnnotation} will be passed to all
         * subsequently injected Java objects if any methods return an object.  This
         * means the same restrictions (or lack thereof) will apply.  Alternatively,
         * {@link #addJavascriptInterface(Object, String)} can be called, which
         * automatically uses the {@link JavascriptInterface} annotation.
         * <li> JavaScript interacts with Java objects on a private, background
         * thread of the ContentViewCore. Care is therefore required to maintain
         * thread safety.</li>
         * </ul></p>
         *
         * @param object             The Java object to inject into the
         *                           ContentViewCore's JavaScript context. Null
         *                           values are ignored.
         * @param name               The name used to expose the instance in
         *                           JavaScript.
         * @param requiredAnnotation Restrict exposed methods to ones with this
         *                           annotation.  If {@code null} all methods are
         *                           exposed.
         *
         */
        public void addPossiblyUnsafeJavascriptInterface(Object object, String name,
                Class<? extends Annotation> requiredAnnotation) {
            if (mNativeContentViewCore != 0 && object != null) {
                mJavaScriptInterfaces.put(name, object);
                nativeAddJavascriptInterface(mNativeContentViewCore, object, name, requiredAnnotation,
                        mRetainedJavaScriptObjects);
            }
        }
    
        ......

        private native void nativeAddJavascriptInterface(int nativeContentViewCoreImpl, Object object, String name, Class requiredAnnotation, HashSet<Object> retainedObjectSet);

      ......

    }

    恭喜,我们走完了Java层的代码,现在我们要去native层了。我们要找到对应的C代码。

    • content_view_core_impl.cc  源码位置:/external/chromium_org/content/browser/android/content_view_core_impl.cc

    namespace content {
    
    namespace {
    
        ......
    
    // Enables a callback when the underlying WebContents is destroyed, to enable
    // nulling the back-pointer.
    class ContentViewCoreImpl::ContentViewUserData
        : public base::SupportsUserData::Data {
     public:
      explicit ContentViewUserData(ContentViewCoreImpl* content_view_core)
          : content_view_core_(content_view_core) {}
    }
    
        ......
    
    void ContentViewCoreImpl::AddJavascriptInterface(
        JNIEnv* env,
        jobject /* obj */,
        jobject object,
        jstring name,
        jclass safe_annotation_clazz,
        jobject retained_object_set) {
      ScopedJavaLocalRef<jobject> scoped_object(env, object);
      ScopedJavaLocalRef<jclass> scoped_clazz(env, safe_annotation_clazz);
      JavaObjectWeakGlobalRef weak_retained_object_set(env, retained_object_set);
    
      // JavaBoundObject creates the NPObject with a ref count of 1, and
      // JavaBridgeDispatcherHostManager takes its own ref.
      JavaBridgeDispatcherHostManager* java_bridge =
          web_contents_->java_bridge_dispatcher_host_manager();
      java_bridge->SetRetainedObjectSet(weak_retained_object_set);
      NPObject* bound_object = JavaBoundObject::Create(scoped_object, scoped_clazz,
                                                       java_bridge->AsWeakPtr());
      java_bridge->AddNamedObject(ConvertJavaStringToUTF16(env, name),
                                  bound_object);
      WebKit::WebBindings::releaseObject(bound_object);
    }
    
        ......
    }

    ContentViewCoreImpl::AddJavascriptInterface 就是我们要找的Native代码,看标注我们需要继续看  JavaBridgeDispatcherHostManager。

    • JavaBridgeDispatcherHostManager  源码位置:/external/chromium_org/content/browser/renderer_host/java/java_bridge_dispatcher_host_manager.cc

    namespace content {
    
    JavaBridgeDispatcherHostManager::JavaBridgeDispatcherHostManager(
        WebContents* web_contents)
        : WebContentsObserver(web_contents) {
    }
    
    JavaBridgeDispatcherHostManager::~JavaBridgeDispatcherHostManager() {
      for (ObjectMap::iterator iter = objects_.begin(); iter != objects_.end();
          ++iter) {
        WebKit::WebBindings::releaseObject(iter->second);
      }
      DCHECK_EQ(0U, instances_.size());
    }
    
    void JavaBridgeDispatcherHostManager::AddNamedObject(const string16& name,
                                                         NPObject* object) {
      // Record this object in a map so that we can add it into RenderViewHosts
      // created later. The JavaBridgeDispatcherHost instances will take a
      // reference to the object, but we take one too, because this method can be
      // called before there are any such instances.
      WebKit::WebBindings::retainObject(object);
      objects_[name] = object;
    
      for (InstanceMap::iterator iter = instances_.begin();
          iter != instances_.end(); ++iter) {
        iter->second->AddNamedObject(name, object);
      }
    }

    配合上面的代码,需要附上 java_bridge_dispatcher_host_manager.h 对 objects_ 以及 instances_ 的声明

    namespace content {
    class JavaBridgeDispatcherHost;
    class RenderViewHost;
    
    // This class handles injecting Java objects into all of the RenderViews
    // associated with a WebContents. It manages a set of JavaBridgeDispatcherHost
    // objects, one per RenderViewHost.
    class JavaBridgeDispatcherHostManager
        : public WebContentsObserver,
          public base::SupportsWeakPtr<JavaBridgeDispatcherHostManager> {
    
    ......
    
    private:
      typedef std::map<RenderViewHost*, scoped_refptr<JavaBridgeDispatcherHost> >
          InstanceMap;
      InstanceMap instances_;
      typedef std::map<string16, NPObject*> ObjectMap;
      ObjectMap objects_;
      JavaObjectWeakGlobalRef retained_object_set_;
    
    ......
    
    }

    结合.h和.c的代码,ContentViewCoreImpl::AddJavascriptInterface中关于 instances_的遍历,遍历的对象是  JavaBridgeDispatcherHost,并调用其  AddNamedObject 方法。好,继续往下。

    
    
    #if !defined(OS_ANDROID)
    #error "JavaBridge currently only supports OS_ANDROID"
    #endif

    namespace
    content { namespace { // The JavaBridge needs to use a Java thread so the callback // will happen on a thread with a prepared Looper. class JavaBridgeThread : public base::android::JavaHandlerThread { public: JavaBridgeThread() : base::android::JavaHandlerThread("JavaBridge") { Start(); } virtual ~JavaBridgeThread() { Stop(); } }; ...... void JavaBridgeDispatcherHost::AddNamedObject(const string16& name, NPObject* object) { NPVariant_Param variant_param; CreateNPVariantParam(object, &variant_param); if (!is_renderer_initialized_) { is_renderer_initialized_ = true; Send(new JavaBridgeMsg_Init(routing_id())); } Send(new JavaBridgeMsg_AddNamedObject(routing_id(), name, variant_param)); } ...... bool JavaBridgeDispatcherHost::Send(IPC::Message* msg) { return RenderViewHostObserver::Send(msg); } ...... } }

    哈哈,看到这露出了姨母笑。IPC ,嘿嘿嘿。继续看  RenderViewHostObserver 。源码中有一句话,JavaBridge currently only supports OS_ANDROID 

    // render_view_host_observer.h
    RenderViewHostImpl* render_view_host_;
    
    
    namespace content {
    
    RenderViewHostObserver::RenderViewHostObserver(RenderViewHost* render_view_host)
        : render_view_host_(static_cast<RenderViewHostImpl*>(render_view_host)),
          routing_id_(render_view_host_->GetRoutingID()) {
    
        ......
    
    bool RenderViewHostObserver::Send(IPC::Message* message) {
      if (!render_view_host_) {
        delete message;
        return false;
      }
    
      return render_view_host_->Send(message);
    }
    
        ......
    
    }
    }
    • RenderViewHostImpl 发现该类中并没有 Send 方法,然后查看其父类是 RenderWidgetHostImpl ,然后查看 RenderWidgetHostImpl 中是否有 Send 方法。
    • RenderWidgetHostImpl
    // .h
    // Created during construction but initialized during Init*(). Therefore, it
     // is guaranteed never to be NULL, but its channel may be NULL if the
     // renderer crashed, so you must always check that.
      RenderProcessHost* process_;
    //.c
    
    bool RenderWidgetHostImpl::Send(IPC::Message* msg) {
      if (IPC_MESSAGE_ID_CLASS(msg->type()) == InputMsgStart)
        return input_router_->SendInput(msg);
    
      return process_->Send(msg);
    }
    #ifndef IPC_IPC_SENDER_H_
    #define IPC_IPC_SENDER_H_
    
    #include "ipc/ipc_export.h"
    
    namespace IPC {
    
    class Message;
    
    class IPC_EXPORT Sender {
     public:
      // Sends the given IPC message.  The implementor takes ownership of the
      // given Message regardless of whether or not this method succeeds.  This
      // is done to make this method easier to use.  Returns true on success and
      // false otherwise.
      virtual bool Send(Message* msg) = 0;
    
     protected:
      virtual ~Sender() {}
    };
    
    }  // namespace IPC
    
    #endif  // IPC_IPC_SENDER_H_

    走到这里就是终点了。可以看出,开始的 WebView addJavascriptInterface  方法调用,其实就是通过 chromium 的IPC 机制发送了一条消息出去。chromium 的IPC 是通过 unix socket 进行通信的。那这个消息发送给谁,消息是如何发送、接收的,这个可以看下面两篇罗老师的文章。

     这里有一个自己的疑惑,第一个点:chrome浏览器是多进程架构,Chromium for Android WebView 是单进程架构。第二个点:上面分析到的IPC的通信是进程间通信。单进程架构和IPC进程间通信是不是有点别扭,内心很矛盾。希望能够看到的童鞋,能够给我个解答。

  • 相关阅读:
    协方差
    小世界网络和无标度网络
    复杂网络谱分析
    图谱
    复杂网络基本概念
    Smarty模板引擎的使用
    ThinkPHP6使用过程中的一些总结。
    ThinkPHP6.0使用富文本编辑器wangEditor3
    ThinkPHP6.0在phpstorm添加查询构造器和模型的代码提示
    在线生成二维码API接口
  • 原文地址:https://www.cnblogs.com/aimqqroad-13/p/13893588.html
Copyright © 2020-2023  润新知