joy keeps flowin'

Android WebView addJavaScriptInterface的实现

xx
目次

在工作中有和前端混合开发的项目(前端的代码运行在Webview中),混合开发涉及到前端和客户端之间方法的调用,花了点时间弄明白是实现调用的。

实现 #

我们都知道是通过WebView的addJavaScript方法将Android的方法添加进Webview,之后前端才可以调用到的,因此入口从它开始。

Java层 #

点进WebView的addJavascriptInterface方法里,添加和移除的方法实现都是通过WebViewProviderWebViewProvider才是WebView中功能实现的类。又是怎么创建的呢?

WebViewProvider的创建通过反射方式。在不同Android SDK中指向的不同的FactoryProvider类。详细过程可以参考【迁移博客】WebView深究之Android是如何实现webview初始化的

在我的环境下,WebViewProvider实现类是WebViewChromium

我们要找的addJavascriptInterface又被给到了AwContents了,AwContents又给JavascriptInjector

终于,对应到了content/public/android/java/src/org/chromium/content/browser/JavascriptInjectorImpl.javaaddPossiblyUnsafeInterface

addPossiblyUnsafeInterface方法接收到的四个参数分别是:

  1. 实现JS方法的对象
  2. addJavaScript时传入的name
  3. JavascriptInterface.class
  4. List.of("*")

观察到addPossiblyUnsafeInterface的实现是一个需要传入指针的,下面的Natices接口标注了NativeMethods,而且搜到的几个JavascriptInjectorImplJni#get方法路径都在out/…路径下,猜测编译时生成的C++代码。

或者,你也可以向我一样,用ChatGPT搜对应的实现是什么,再编一顿和上面类似的理由😄。

根据规则,对应的C++实现类content/browser/android/javascript_injector_android.cc。

native层 #

从这里开始,就到了C++的部分。

C++我不熟悉,也就是大致能看懂的水平,有看不懂的交给AI解释了的。

下面是添加方法的实现,将Android传递过来的方法封装成一个C++对象,用一个map存放着。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
void GinJavaBridgeDispatcherHost::AddNamedObject(
    const std::string& name,
    const base::android::JavaRef<jobject>& object,
    const base::android::JavaRef<jclass>& safe_annotation_clazz,
    net::SchemeHostPortMatcher matcher) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  GinJavaBoundObject::ObjectID object_id;
  NamedObjectMap::iterator iter = named_objects_.find(name);
  bool existing_object = FindObjectId(object, &object_id);
  if (existing_object && iter != named_objects_.end() &&
      iter->second.object_id == object_id) {
    // Nothing to do.
    return;
  }

  if (iter != named_objects_.end()) {
    RemoveNamedObject(iter->first);
  }
  if (existing_object) {
    base::AutoLock locker(objects_lock_);
    objects_[object_id]->AddName();
  } else {
    object_id = AddObject(object, safe_annotation_clazz, std::nullopt);
  }

  // We use the serialized string of the matcher and reconstruct it
  // in the render process. We pass this around like this because we can
  // then trust that all the rules being fed to the render process are well
  // formed rules.
  // TODO(crbug.com/407420300): Rely on OriginMatcher instead of a string here.
  named_objects_[name] = {object_id, matcher.ToString()};

  web_contents()
      ->GetPrimaryMainFrame()
      ->ForEachRenderFrameHostImplIncludingSpeculative(
          [&name, object_id, &matcher,
           this](RenderFrameHostImpl* render_frame_host) {
            if (!render_frame_host->IsRenderFrameLive()) {
              return;
            }

            GetJavaBridge(render_frame_host, /*should_create=*/true)
                ->AddNamedObject(name, object_id, matcher.ToString());
          });
}

既然存放在一个map里,调用时也一定是通过这个map取到的,顺着找到了content/browser/android/java/gin_java_bridge_dispatcher_host.cc#onInvokeMethod,看到参数中使用方法名、参数和结果的,结合之前和前端打交道的经验,十有八九就是它了。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
void GinJavaBridgeDispatcherHost::OnInvokeMethod(
    const GlobalRenderFrameHostId& routing_id,
    GinJavaBoundObject::ObjectID object_id,
    const std::string& method_name,
    const base::Value::List& arguments,
    base::Value::List* wrapped_result,
    content::mojom::GinJavaBridgeError* error_code) {
  DCHECK(JavaBridgeThread::CurrentlyOn());
  DCHECK(routing_id);
  scoped_refptr<GinJavaBoundObject> object = FindObject(object_id);
  if (!object.get()) {
    wrapped_result->Append(base::Value());
    *error_code = mojom::GinJavaBridgeError::kGinJavaBridgeUnknownObjectId;
    return;
  }

  auto result = base::MakeRefCounted<GinJavaMethodInvocationHelper>(
      std::make_unique<GinJavaBoundObjectDelegate>(object), method_name,
      arguments);
  result->Init(this);
  result->Invoke();
  *error_code = result->GetInvocationError();
  if (result->HoldsPrimitiveResult()) {
    *wrapped_result = result->GetPrimitiveResult().Clone();
  } else if (!result->GetObjectResult().is_null()) {
    GinJavaBoundObject::ObjectID returned_object_id;
    if (FindObjectId(result->GetObjectResult(), &returned_object_id)) {
      base::AutoLock locker(objects_lock_);
      objects_[returned_object_id]->AddHolder(routing_id);
    } else {
      returned_object_id =
          AddObject(result->GetObjectResult(), result->GetSafeAnnotationClass(),
                    routing_id);
    }
    wrapped_result->Append(base::Value::FromUniquePtrValue(
        GinJavaBridgeValue::CreateObjectIDValue(returned_object_id)));
  } else {
    wrapped_result->Append(base::Value());
  }
}

接着是调用Java方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
void GinJavaMethodInvocationHelper::Invoke() {
  JNIEnv* env = AttachCurrentThread();
  const JavaMethod* method =
      object_->FindMethod(method_name_, arguments_.size());
  if (!method) {
    SetInvocationError(mojom::GinJavaBridgeError::kGinJavaBridgeMethodNotFound);
    return;
  }

  if (object_->IsObjectGetClassMethod(method)) {
    base::android::EventLogWriteInt(kObjectGetClassInvocationAttemptLogTag,
                                    getuid());
    SetInvocationError(mojom::GinJavaBridgeError::
                           kGinJavaBridgeAccessToObjectGetClassIsBlocked);
    return;
  }

  ScopedJavaLocalRef<jobject> obj;
  ScopedJavaLocalRef<jclass> cls;
  if (method->is_static()) {
    cls = object_->GetLocalClassRef(env);
  } else {
    obj = object_->GetLocalRef(env);
  }
  if (obj.is_null() && cls.is_null()) {
    SetInvocationError(mojom::GinJavaBridgeError::kGinJavaBridgeObjectIsGone);
    return;
  }

  mojom::GinJavaBridgeError coercion_error =
      mojom::GinJavaBridgeError::kGinJavaBridgeNoError;
  std::vector<jvalue> parameters(method->num_parameters());
  for (size_t i = 0; i < method->num_parameters(); ++i) {
    const base::Value& argument = arguments_[i];
    parameters[i] = CoerceJavaScriptValueToJavaValue(
        env, argument, method->parameter_type(i), true, object_refs_,
        &coercion_error);
  }

  if (coercion_error == mojom::GinJavaBridgeError::kGinJavaBridgeNoError) {
    if (method->is_static()) {
      InvokeMethod(nullptr, cls.obj(), method->return_type(), method->id(),
                   parameters.data());
    } else {
      InvokeMethod(obj.obj(), nullptr, method->return_type(), method->id(),
                   parameters.data());
    }
  } else {
    SetInvocationError(coercion_error);
  }

  // Now that we're done with the jvalue, release any local references created
  // by CoerceJavaScriptValueToJavaValue().
  for (size_t i = 0; i < method->num_parameters(); ++i) {
    ReleaseJavaValueIfRequired(env, &parameters[i], method->parameter_type(i));
  }
}

总结 #

现在重新梳理一下完成的流程,Android中为一个WebView添加JS方法时,会通过反射创建不同的WebViewProvider实现,用的最多的是WebViewChromium。添加的JS方法会以一个对象的形式,存储在CPP的一个map中,调用时通过name和方法名称找到添加的方法。

Android和JS通信要借助Native层完成。

工具 #

Chromium代码搜索:Chromium Code Search

参考 #

标签:
Categories: