Referenced Method Count Increased After App Modularization
Asked Answered
S

3

17

AS: 3.5.3; Android Gradle Plugin: 3.5.0; Gradle: 5.6.2;

We observed a drastic increase in the number of methods referenced in our app after splitting the 'app' module into several small modules. But the strange thing is that the addition of referenced methods by each class is less than the mentioned total in Android Apk Analyzer Tool.

For test purpose, I have moved WebActivity.class from 'app' module to 'adapters' module and referenced method count increased by 181 methods.

To Summarize:

app/WebActivity = 63546 Actual referenced methods but showing 65394 methods. adapter/WebActivity = 63543 Actual referenced methods but showing 65575 methods.

We have observed 'referenced method count' increased by almost 10k after adding/splitting 4 new modules.

What is the exact issue?

How app modularization can increase the referenced method count drastically so high?

Following are the screenshots I took of two different APKs-only difference is WebActivity moved from 'app' module to 'adapter' module and 181 referenced methods increased:

WebActivity in 'app' module enter image description here

Moved WebActivity to 'adapter' module enter image description here

In the screenshots, why the addition of referenced methods by each class (marked in red color) is not equal to the total given in Apk Analyzer?

Sirius answered 26/12, 2019 at 14:21 Comment(1)
I have created an issue, you can track it here issuetracker.google.com/issues/146957168Sirius
S
3

Answering my own question as the solution just clicked in my mind, though this is not tried but would work, definitely or most probably. :) The answer given by Mr.AF was very useful to reach a final solution. It talks about Why? but not how to avoid it or how to improve upon that.

Here is a way to get back the original/actual referenced method count-

It is not dependent on how we modularise the app but on how we add dependencies. If we add a dependency using 'implementation' then that dependency remains private to the module and no other module can use it. And if we add the same dependency using 'api' (equal to deprecated 'compile') then it becomes public and other dependent modules can use it. Since we are using 'implementation' to add dependencies in each module in a multi-module project, each module has all required dependencies as self-contained, this is the reason it can be compiled individually. This results in decreased build/compile time as only modified modules can be compiled. But, use of 'implementation' increases the referenced method count as there are so many duplicate referenced methods.

So, if build time is not your concern but referenced method count is then you can draw the dependency tree of all modules and avoid adding duplicate dependency by using 'api' in the base module. This way even top module can use dependency added by the base module which will avoid duplicates. Remember, this would increase the build time.

We can achieve both if we could distinguish dependencies for debug and release build. Add all dependencies using 'implementation' for debug build and add only required and optimized dependencies for release build by using 'api'. This way debug build will be faster and release build will be slower which is affordable.

Note: I would update this answer once I figure out how to provide separate dependencies for debug and release build.

Sirius answered 27/1, 2020 at 19:45 Comment(0)
M
9

I've been reading about code performance and tuning parameters for a long time. Indeed, Android programs are one of my focuses.

Let’s introduce at first the basic or most important concepts in which help us to reach a solution.

As Android Developer Has stated

module can be independently built, tested, and debugged

Therefore, Modules have their own Gradle & Dependencies. And you can explore it in the project Hierarchy Viewer.

Modularization emphasizes Maintenance matters. Unlike Performance Matters. Because Modularization has this important impact:

  • Increase Depth of inheritance

Here is a diagram that I did plot to make it clear. As you can see. while using the discrete module, to invoke Method A there are 2N micro secs compared to N micro secs without discrete module.

enter image description here

This question may come to your mind that Referenced Methods counts what is related to Depth of inheritance?

The answer is: Although using modularization increases Referenced Methods. but, it doesn’t affect the app performance and the main possible issue is Depth of inheritance which in most cases is ignorable.

I do emphasize that increased Referenced Methods in modularization is due to each Module Gradle & Dependencies

How app modularization can increase the referenced method count drastically so high?

Conditions in which impact APK analyzer importantly Referenced Methods

Also note that minification and code shrinking can each also considerably change the contents of a DEX file after source code is compiled.

In addition to the above official statement, I want to add another condition in which impact the APK analyzer that’s:

how much is the developer experienced in modularization?

modularization is like a home that architecture(developer) defines where should be the kitchen and where should be a restroom and where should be WC. What if the architecture decides to combine WC & Kitchen? Yea this is a disaster.

This may happen while modularization if the developer is not very much experienced.


Answering OP questions in Addition to extra information

Here I answer op asked questions in the comments

Why would separate Gradle add to the referenced method count? And for separate dependency, if the final result is a single APK then I do not think duplicate dependencies in 'app' and feature module would add to referenced method count.

Because modules can be built, tested, and debugged then they MUST have their own Gradle & Dependencies.

While multi-module project is being complied , compiler generates several .dex files including:

  • a .dex file for total-integrated dependencies
  • modules .dexs

dependencies .dex file is an integrate of all the modules gradles

Let's look at how a module gradle impacts final Referenced Methods Count?!

there are 2 APKs with the same result but differences in Referenced Methods counts.

figure 1 figure 2

They are both empty activities that have a 1.7k difference in Referenced Methods Count that is very high depending on their functionality. The key difference is on their Module's Gradle one of them was configured to

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
}

Another one configured to

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'androidx.appcompat:appcompat:1.2.0-alpha01'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta4'
}

Although they are just empty activities, a minimal difference in Gradle caused a 1.7k difference in Referenced Method Counts.

And App Gradle is

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    implementation project(path: ':module')
}

major concern is why the addition of individually referenced method count is different than the total referenced method count in Apk Analyzer?

This is just an IDE filter nothing else. for sure, if you only select a .dex file Reference Method Counts is equal to SUM of each row Referenced Method Counts but if you multi-select .dex files, You will see the difference in SUM and actual Count that because of equality in References that Analyzer preferred to filter them.

in your screenshots, you've selected multiple .dex files then Analyzer filter equality.

in our project we are using centralized dependencies. gradle file so there is no chance of a different version. So, do you think even if we have the same/exact set of dependencies and their versions in feature modules, it will increase referenced method count?

Theoretically, it should NOT increase referenced method counts.BUT, As I explained it, Developer Experience highly impacts the final result.

Team Analyzer should check and fix performance issues before release like

  • proguard rules
  • shrunk & minified resources
  • androidManifest.xml
  • gradle settings

Now I want to clarify how Developer Experience and code maintenance affects the final result. EVEN if your APK uses Centralized Dependencies

figure 3

in the above example, I've increased 5.1k in Referenced Methods Count EVEN IF, I had Centralized Dependencies !!!!!

How it's possible ?

The answer is: I just added a useless and hidden .jar file in the libs directory of the project. just as easy as you can see I affected the final result.

As you can see Developer Experience affects the final result. as a result, Practically it's possible that referenced methods counts to be increased Although Theoretically Should NOT.

And why there is no difference in referenced method count when I compile only 'app' module by disabling parallel compilation? It should have decreased as only 'app' module's dependencies would have been used, right?

the compilation has not any relation to referenced methods counts.it complies with what the developer wants to have complied.


Conclusion

I have covered all the possibilities around the issue. Indeed, it can emerge from different situations, and a developer by using this guideline can fix the issue.

  • I would hope that you found why Referenced Methods was increased and why in some cases it might be drastically increased.
  • Modules have their Gradle & Dependencies and modularization increase modules. therefore, these Method References.
  • Modularization impacts app performance ignorable but makes your app Maintenance highly better.
  • Developer experience in modularization also highly impacts the final result.

IMPORTANT NOTE: almost all of the statements are my investigation & researches. indeed, there may be errors and faults and will be updated to add much more information in the future.


Manche answered 8/1, 2020 at 19:20 Comment(15)
Thanks Mr.AF, I was hoping to get the answer after "This question my come to your mind that Referenced Methods counts what related to Depth of inheritance? The answer is ," but you did not answer that. Can you please elaborate on why depth of inheritance increase referenced method count? As in our case we have not added any extra layer but just split the 'app' module. There is a chance of increase in referenced method count in case a feature module accessing methods of another feature module through 'app' module, is this the reason?Sirius
@RohitSurwase answer is the rest of statement.Depth of inheritance do not increase Method References,modularization doing that and modularization cuz Depth of inheritance.Manche
@RohitSurwase, a feature accessing another feature in another module actually do not increase referenced methods count highly . the main reason for increasing in referenced method counts is Gradle & Dependencies that each module need .Manche
@RohitSurwase you point good tips about module to module relation . as a matter of fact , if 2 modules has too many relations and referenced methods then they should be combined for better performance . actually module need to be independents in term & concept.Manche
Why would separate Gradle add to the referenced method count? And for separate dependency, if the final result is single APK then I do not think duplicate dependencies in 'app' and feature module would add to referenced method count.Sirius
@RohitSurwase ,module can be independently built, tested, and debugged.how it is possible to built test debugged without dependencies ?Manche
I mean to say, even if we are adding a dependency twice; once in 'app' and also in feature module i.e. duplicating, this should not add to referenced method count, right? Because finally all dependencies gets to a single place, a single APK. Please correct me if I am wrong.Sirius
I have also tried disabling parallel builds (including limiting threads) for the project, still no difference. So I do not think duplicate dependency would add to referenced method count, right?Sirius
@RohitSurwase ,i wrote a book . check update. hope you will get your answerManche
Thanks for your time and effort. But this still does not answer my question. Yes, in your example two modules has same dependencies but different version and that might have caused increase in referenced method count. But in our project we are using centralized dependencies.gradle file so there is not chance of different version. And apart from the increase in the referenced method count my major concern is why addition of individually referenced method count is different than total referenced method count in Apk Analyzer as highlighted in the screenshot? I really appreciate your help.Sirius
Thanks, I checked and it's true. Now, the only remaining doubt, in your example two modules has same dependencies but different version and that might have caused increase in referenced method count. And even you have mentioned "minimal difference in Gradle caused 1.7k difference". But in our project we are using centralized dependencies.gradle file so there is not chance of different version. So, do you think even if we have same/exact set of dependencies and their versions in feature modules, it will increase referenced method count?Sirius
And why there is no difference in referenced method count when I compile only 'app' module by disabling parallel compilation? It should have decreased as only 'app' module's dependencies would have been used, right?Sirius
I am accepting/upvoting this answer. Thanks so much. But still I am a bit unsatisfied with why referenced method count increased even after having centralized gradle dependencies? To answer this you have added an unused jar but that is not the case, we do not have any unused code/jar. We have added few modules so far by just splitting 'app' module and each new module has added around 2K methods on average, few modules has added around 4K. Same code, no unused code or jar. Can you please elaborate on this?Sirius
@RohitSurwase as i saied , unused jar might not be your case .something that i mean is developer experience and it's possible , the possibility can be emerged from different sources. and i listed all possible sources that you need to search itManche
I have added an answer based on your response and a few other resources and findings. https://mcmap.net/q/708018/-referenced-method-count-increased-after-app-modularizationSirius
S
3

Answering my own question as the solution just clicked in my mind, though this is not tried but would work, definitely or most probably. :) The answer given by Mr.AF was very useful to reach a final solution. It talks about Why? but not how to avoid it or how to improve upon that.

Here is a way to get back the original/actual referenced method count-

It is not dependent on how we modularise the app but on how we add dependencies. If we add a dependency using 'implementation' then that dependency remains private to the module and no other module can use it. And if we add the same dependency using 'api' (equal to deprecated 'compile') then it becomes public and other dependent modules can use it. Since we are using 'implementation' to add dependencies in each module in a multi-module project, each module has all required dependencies as self-contained, this is the reason it can be compiled individually. This results in decreased build/compile time as only modified modules can be compiled. But, use of 'implementation' increases the referenced method count as there are so many duplicate referenced methods.

So, if build time is not your concern but referenced method count is then you can draw the dependency tree of all modules and avoid adding duplicate dependency by using 'api' in the base module. This way even top module can use dependency added by the base module which will avoid duplicates. Remember, this would increase the build time.

We can achieve both if we could distinguish dependencies for debug and release build. Add all dependencies using 'implementation' for debug build and add only required and optimized dependencies for release build by using 'api'. This way debug build will be faster and release build will be slower which is affordable.

Note: I would update this answer once I figure out how to provide separate dependencies for debug and release build.

Sirius answered 27/1, 2020 at 19:45 Comment(0)
G
0

I see all the difference in your 'com' package. You can expand and compare what exact classes were shrinked. If you build with latest R8 it can remove some code by default. When you put some classes into module shrinker don't know if public classes/methods can be removed or must stay for use in another module. enter image description here

Gyn answered 9/1, 2020 at 12:15 Comment(1)
Yes I did expand and check each package including 'com'. The major concern is why addition of individually referenced method count is different than total referenced method count in Apk Analyzer?Sirius

© 2022 - 2024 — McMap. All rights reserved.