While the build-time linker prefers a strong symbol definition to a weak definition, I don't think the runtime loader has that preference. It always uses the first symbol it finds (with a binding of either STB_GLOBAL or STB_WEAK). I
this is common behavior for ELF linkers.
Maybe it works on (glibc) Linux because libA.so is loaded with RTLD_GLOBAL, which would make its symbols available when loading any other library. The linker could then use libA.so's copy for both libA.so and libB.so. Android's dlopen recognizes the RTLD_GLOBAL flag, and it affects dlsym, but it doesn't make a shared object's symbols available during later dlopen calls:
We might want to change this behavior eventually, but that wouldn't be useful for a while. We have a bug filed internally for that --
http://b/110188176.
As the comment in that code indicates, you could mark the libraries with DF_1_GLOBAL, but unfortunately, that only works with P and up. (Technically M and up, but there's a bug with O MR1.)
For M and up, if you could load all of your shared objects with a single dlopen call, that would avoid the problem, but I'm not sure if that's practical. (e.g. Maybe use System.loadLibrary on one shared object that links against every other shared object in your app?)
I suspect placing each singleton instance in only one shared object is the best fix. If the variable instances are generated using templates, then I think you can use "extern template" declarations to control where specific instantiations are emitted.
-Ryan