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:
@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:
adb -s emulator-5558 logcat -d -v tag -s Capacitor Capacitor/Console \
| grep -E "Registering|header|UNIMPL" | sed 's/File: .* Msg: //'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 androidEvery 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.