Plugin Registry

The Plugin Registry and Method Dispatch

The native registry is a Map<String, PluginHandle> in Bridge.java. At startup the bridge registers its built-in plugins, then the classes cap sync lists in assets/capacitor.plugins.json, then any class you pass to registerPlugin() in MainActivity.onCreate, before super.onCreate(). The probe registers its plugin that way:

ShelfPlugin.java: the probe's in-app plugin (package and imports omitted)Java
@CapacitorPlugin(name = "Shelf")
public class ShelfPlugin extends Plugin {
  @PluginMethod
  public void getBook(PluginCall call) {
    int id = call.getInt("id", 0);
    Log.d("Shelf", "getBook(" + id + ") on " + Thread.currentThread().getName());
    if (id != 3) {
      call.reject("No book with id " + id, "NOT_FOUND");
      return;
    }
    call.resolve(new JSObject().put("id", id).put("title", "Salt and Saffron")
        .put("price", 24.00).put("seenAt", call.getString("at")));
  }
  @PluginMethod
  public void restock(PluginCall call) {
    call.resolve();
    JSObject event = new JSObject().put("id", call.getInt("id")).put("inStock", true);
    new Handler(Looper.getMainLooper()).postDelayed(
        () -> notifyListeners("stockChanged", event), 500);
  }
}

The annotation's name must match registerPlugin('Shelf') in JavaScript. PluginHandle indexes the class once by reflection, so dispatch is a hash lookup and a Method.invoke(). The bridge also exports every method list into the page as window.Capacitor.PluginHeaders. The proxy that registerPlugin() returns reads it: a method with rtype "promise" becomes a promise call, anything else a callback call, and a name missing from the header fails in JavaScript without crossing the bridge:

The registry as the running app sees itShell
adb -s emulator-5558 logcat -d -v tag -s Capacitor Capacitor/Console \
  | grep -E "Registering|header|UNIMPL" | sed 's/File: .* Msg: //'
Output
D/Capacitor: Registering plugin instance: CapacitorCookies
D/Capacitor: Registering plugin instance: WebView
D/Capacitor: Registering plugin instance: CapacitorHttp
D/Capacitor: Registering plugin instance: SystemBars
D/Capacitor: Registering plugin instance: Shelf
I/Capacitor/Console: header [{"name":"getBook","rtype":"promise"},{"name":"removeAllListeners",
  "rtype":"promise"},{"name":"checkPermissions","rtype":"promise"},{"name":"requestPermissions"
    ,"rtype":"promise"},{"name":"restock","rtype":"promise"},{"name":"removeListener"},{"name":
      "addListener"}]
I/Capacitor/Console: UNIMPLEMENTED: "Shelf.lendOut()" is not implemented on android

Every plugin inherits addListener and the permission methods from Plugin. Bridge.callPluginMethod posts plugin.invoke(methodName, call) to a HandlerThread named "CapacitorPlugins", and the Shelf log tag printed getBook(3) on CapacitorPlugins: plugin code never blocks the WebView. All plugins share that thread, so a slow method stalls every call behind it: move long work to your own executor and resolve the call from there.