Adam Johns
Adam Johns

Reputation: 36343

Multi module library with code obfuscation

I have a suite of libraries distributed as 3 separate aars. Is there any way to be able to obfuscate each library independently, but keep the ability to call methods between modules internally?

E.g. if libA needs to call myMethod from libB, but I don't want myMethod exposed to clients integrating my libraries (yet all libraries must be obfuscated).

Right now I'm forced to make myMethod public and exclude it from obfuscation so it can be called from libA. Is there a better solution to this problem that won't expose myMethod to clients while still be obfuscated?

Upvotes: 1

Views: 1755

Answers (2)

JMHel
JMHel

Reputation: 11

I am unsure if you have already found a solution to your problem but I have played a little and find out a working solution. I made a small HelloWorld app to show how it is done: https://github.com/jmineraud/multi-module-android-app.

It contains the repo for the lib as a git submodule (https://github.com/jmineraud/multi-module-android-lib)

The trick is to obfuscate the libraries independently, but not to optimize them (we do not want to remove any function) and that all dependent submodule must reuse the same mapping.txt file.

For instant in my submodule proguard file, I added:

-dontshrink
-dontoptimize
#-useuniqueclassmembernames # Option does not work with R8
-repackageclasses "com.your.package"  # I do not want to obfuscate the whole hierarchy to keep my obfuscated library class separate and avoid obfuscation problems when the lib is used in the app project (which will be in turn obfuscated)
-applymapping "../lib-core/build/outputs/mapping/release/mapping.txt"

The classes I want to keep are annotated with @Keep from androidx.annotation.Keep

Upvotes: 1

Anis BEN NSIR
Anis BEN NSIR

Reputation: 2555

It's a best practice that library expose only interface and not Objects. Also, interface must respect interface segregation principle. If your client do not need to use the method, your library too. Your libA is the first customer of your LibB. Your libraries must be compliant with single responsibility principle. I think you have to re-work library and extract interface for your classes and expose only interfaces.

I think that this will be a tiny task, a quick solution is to add a proguard rules for each protected method if your are using java.

-keep public class mypackage.MyPublicClass {
    protected void myProtectedMethod();
}

Sample case for refactoring:

//libA
class LibA {
    lateinit var libB:LibB
    fun serviceA(){
        libB.checkLicence()
    }
}
//libB
class LibB{
    fun serviceB(){}
    //this method is needed by libA, due to obfuscation visibility is public instead of internal
    //internal fun checkLicence(){}
    fun checkLicence(){}
}

the refactored code maybe:

//new library distributed only internally not to customer 
// LibInternal
interface InternalLib {
    fun checkLicence()
}
internal class InternalLibImpl : InternalLib {
    override fun checkLicence() {
    }
}

object InternalLibFactory {
    fun create(): InternalLib = InternalLibImpl()
}

//LibB
interface LibB {
    fun serviceB()
}
class LibBImpl(private val internalLib: InternalLib) : LibB {
    override fun serviceB() {
        internalLib.checkLicence()
    }
}
object LibBFactory {
    fun create(): LibB = LibBImpl(InternalLibFactory.create())
}

//LibA
interface LibA {
    fun serviceA()
}
internal class LibAImpl(private val libB: LibB, private val internalLib: InternalLib) {
    fun serviceA() {
        internalLib.checkLicence()
        libB.serviceB()
    }
}
object LibAFactory {
    fun create(): LibB = LibBImpl(InternalLibFactory.create())
}

Upvotes: 0

Related Questions