As I'm sure you're aware, Android is sensitive to method count and accessing private members across nested class boundaries causes javac to create synthetic accessor methods. Dagger subcomponents are implemented using nested classes in the generated source. These subcomponents access providers from the enclosing component which are stored in private fields. As a result, lots of synthetic accessor methods are created when subcomponents are used.
A simple fix here is to make all fields package-private.
While this does increase the visibility, user code never references subcomponent implementation instances directly where they could potentially access them. We only reference the static builder() and the nested Builder class. The Builder's build() method returns the interface type, not the implementation type. While you could in theory cast back to the implementation type, doing so is highly unlikely and isn't really different than your ability to use reflection to get at the fields currently.
For some numbers, a small app using Dagger with a large component and two medium-sized subcomponents receives ~100 of these synthetic accessor methods. Square Register, a non-trivial large app, receives ~1000 of these synthetic accessor methods which itself is larger than some of the third-party libraries it uses and nearly 2% of the 65k single dex limit.
Does proguard not take care of that? IIRC, it includes an optimization for increasing visibility, which is effectively the change you're looking for but for your entire application.
Only if you explicitly choose to enable optimization. Doing so requires ensuring that it doesn't do damaging things to your classfiles which either dex or the runtime will reject. I have tried multiple times to enable optimization for our application and failed to get it to a working state.
The default Android proguard rules include this statement:
# Optimization is turned off by default. Dex does not like code run
# through the ProGuard optimize and preverify steps (and performs some
# of these optimizations on its own).
-dontoptimize
-dontpreverify
A quick test of running optimization by disabling the default -dontoptimize flag and adding
-optimizations method/inlining/short
-optimizationpasses 5
-allowaccessmodification
removed only 6 of the 100 access methods from Dagger. And I didn't test whether it actually could be run on an Android device successfully without offending ART.
Bump. Either figuring out a Proguard configuration or removing these accessors would be great. Are there any thoughts on which strategy is preferred?
We're working on a new strategy for subcomponents which should eliminate this need.
Is there any idea of how many accessors these these add relative to the total component (and also relative to total code size)? Obviously this will differ from project to project, but it would be good to have _some_ data
@JakeWharton are you aware if R8 handles this better too?
I will try today
I _think_ it does, but it's removing so much it's hard to tell. In my first test Dagger disappeared completely! I'm building something less trivial now to try.
FYI, around that time, @JakeWharton filed:
https://issuetracker.google.com/issues/113859358
https://issuetracker.google.com/issues/114006540
Oh yeah thanks I forgot to follow up.
I did a bunch of experiments with R8 and it absolutely destroys Dagger (in the best way). I managed to write a super contrived example with two @Components, a component dependency between them, multiple @Modules, @Binds usage, static and instance @Provides, @BindsInstance usage, and members injection which all combined to create a single string and in some cases Dagger disappeared completely and all that was left was some StringBuilder usage (which is why I also filed https://issuetracker.google.com/issues/113859361). Varying the sample would cause certain things to be retained which led to those issues being filed. In general, though, I was amazed at what R8 could do.
But in every case all of the synthetic accessors were all eliminated!
Great! In that case I'm going to close this.
Most helpful comment
Oh yeah thanks I forgot to follow up.
I did a bunch of experiments with R8 and it absolutely destroys Dagger (in the best way). I managed to write a super contrived example with two
@Components, a component dependency between them, multiple@Modules,@Bindsusage, static and instance@Provides,@BindsInstanceusage, and members injection which all combined to create a single string and in some cases Dagger disappeared completely and all that was left was some StringBuilder usage (which is why I also filed https://issuetracker.google.com/issues/113859361). Varying the sample would cause certain things to be retained which led to those issues being filed. In general, though, I was amazed at what R8 could do.But in every case all of the synthetic accessors were all eliminated!