When we develop Android projects, we don’t build all the features by ourselves. We often use a variety of other packages, including various Support packages provided by Google and various third-party function libraries. Sometimes, we also encapsulates some functions into packages ourselves. These packages exist and can be imported in a variety of forms, such as remote repositories, direct copies to the local, JAR packages, AAR packages, so packages, etc. Fortunately, we can manage it in the main project and in the build.gradle of each Module. This article summarizes these uses in the Context of Android Studio3.0.

Preliminary knowledge

Android Gradle Plugin 3.0 introduces dependencies:

Implementation

For dependencies compiled using this command, any program in the dependencies compiled using this command will not be accessible to projects that have dependencies on the project, that is, the dependency will be hidden internally rather than exposed externally.

Using implementation makes compilation faster: for example, if I use implementation in a library that relies on the GSON library, and my main project relies on the library, then my main project cannot access methods in the GSON library. The benefit of this is that compilation speeds up. I changed to a version of the Gson library, but as long as the library code was not changed, I did not recompile the main project code.

api

It’s equivalent to the compile directive

compileOnly

It’s the same thing as provided, it only works at compile time, it’s not packaged, it’s not included in an APK file. Can be used to resolve conflicts over duplicate import libraries (described below).

More details can be found in this article

Remote warehouse dependency

Let’s first look at the build.gradle file in the main project

buildscript {
    ext.kotlin_version = '1.1.51'
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath 'com. Android. Tools. Build: gradle: 3.0.1'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        google()
        jcenter()
    }
}
Copy the code

It is convenient to introduce a remote repository dependency, but first we need to declare the address of the remote repository. There are two repository addresses declared above, one at buildScript {} and one at Repositories {}. If you look at the comments in the code, you will know that the former is the dependency required by the gradle script itself (gradle plug-in) and the latter is the dependency required by the project itself (normal code base). So if you don’t introduce the remote Gradle plugin, you don’t need to add dependencies under buildScript {}.

For more information on Gradle plugin development, see this article: Gradle Custom Plugin

Here are some ways to add remote dependencies:

 implementation 'Commons - lang: the Commons - lang: 2.6'
 
 implementation group: 'com.google.code.guice', name: 'guice', version: '1.0'
 
 implementation('3.1' org. Hibernate: hibernate.) {// If different versions are being relied on at the same time, then force dependencies on this version, defaultfalse
        force = true//exclude: //exclude: //exclude: //exclude: //exclude: //exclude:'cglib' 
        exclude group: 'org.jmock' 
        exclude group: 'org.unwanted', module: 'iAmBuggy'To disable dependency passing, gradle automatically adds child dependencies (dependencies required by the dependency package), set tofalse, you need to manually add each child dependency. The default istrue. transitive =false
    }
Copy the code

If a version conflict occurs in the same configuration, the latest version is automatically used. Gradle synchronization will directly report errors when the version conflicts in different configurations. You can use exclude and force to resolve conflicts. Let’s say you rely on two versions of v7:

implementation 'com. Android. Support: appcompat - v7:26.1.0'
implementation 'com. Android. Support: appcompat - v7:23.1.1'
Copy the code

Only version 26.1.0 will be used. But as implementation ‘com. Android. Support: appcompat – v7:23.1.1’, And androidTestImplementation ‘. Com. Android support. Test. Espresso: espresso – core: 2.1 ‘, Rely on com. Android. Support: support – annotations version is different, can lead to conflict. In addition to exclude and force, you can also specify a support package version for all dependencies. You do not need to exclude each dependency separately.

configurations.all {
    resolutionStrategy.eachDependency { DependencyResolveDetails details ->
        def requested = details.requested
        if (requested.group == 'com.android.support') {
            if(! requested.name.startsWith("multidex")) {
                details.useVersion '26.1.0'}}}}Copy the code

Dependencies on compile-time annotations –annotationProcessor

Those of you who have used Butterknife or Dagger may be familiar with the annotationProcessor introduction, in which only the dependent libraries are executed at compile time, but the libraries are not ultimately packaged into the APK. Combined with compile-time annotations, they are used to generate code that is not needed at run time.

Relying on local

The jar package

Importing jar dependencies is relatively simple:

  • implementation files('hibernate.jar', 'libs/spring.jar')// List the relative path of each JAR package
  • implementation fileTree(dir: 'libs', include: ['*.jar'])// List the path to the folder containing the JAR package

However, it is different from the importing method of remote repository dependency. If two different JAR packages exist locally, or if you want to remotely rely on different versions of JAR packages, an error will be reported.

compileOnly
implementation
compileOnly

Aar package

Unlike jar packages, aar packages contain separate path declarations and dependency imports:

repositories {
    flatDir {
        dir ".. /${project.name}/libs"
    }
}
dependencies {  
    implementation(name: 'the aar name', ext: 'aar')}Copy the code

If there are many aar packages, you can add all packages in the same folder as jar packages:

    def dir = new File('app/libs')
    dir.traverse(
            nameFilter: ~/.*\.aar/
    ) { file ->
        def name = file.getName().replace('.aar'.' ')
        implementation(name: name, ext: 'aar')}Copy the code

If a module of the library type needs to reference an AAR file, add this to its build.gradle file. However, if another module references an AAR file of the library type, add the following configuration to its build.gradle. Otherwise, you will be prompted that the file cannot be found:

repositories {  
    flatDir {  
        dirs 'libs'.'.. / name of the module containing the AAR package /libs'}}Copy the code

If the current Module requires the contents of an AAR package, declare the path in build.gradle whether the aar package is in the current Module or not. If there are many such modules in your project, you need to declare a path for each of them. If it is not easy to manage, it is recommended to add a path to the root of your project, build.gradle, and list all the modules that contain the AAR package. In this way, neither this Module nor other modules need to configure a separate path:

allprojects {
    repositories {
        jcenter()
        google()
        flatDir {
             dirs ".. /moudle-A/libs,.. /moudle-B/libs,.. /moudle-C/libs".split(",")}}}Copy the code

So the file

This is similar to the jar package, declare the so file stored in the path:

   sourceSets {
        main {
            jniLibs.srcDirs = ['libs']}}Copy the code

Or create a jniLibs directory directly under main, which is the default but less common location for so files. It is worth noting that an AAR package can also contain so files, but there is no specific configuration required to rely on an AAR package that contains so files. At compile time, the SO file is automatically included in the APK that references the AAR package.

In particular, the so file needs to be placed in a specific ABI directory, not in a libs directory, so you might see something like this:

All x86/x86_64/ Armeabi-V7A/ARM64-V8A devices support armeabi architecture SO files. So in order to reduce the package size, in order to reduce the APK size, you can keep armeabi as a folder. However, if you want to import multiple platforms, you need to keep the number of SO files consistent. This means that each SO file under armeabi will have its corresponding SO file under armeabi-v7a, but the apK package will increase in size.

Another way to do this is to generate a specific ABI version of APK and then upload it to the APP store on demand, allowing users to choose which version is suitable for their phone. This may be more useful for Android game apps.

android {
    ... 
    splits {
        abi {
            enable true// Enable the ABI splitting mechanism reset() // reset the ABI list to include an empty string, include'x86'.'x86_64'.'armeabi-v7a'.'arm64-v8a'// Use this with include to indicate which ABI universalApk to usetrue// Whether to pack a generic version (including all the ABIs). The default value isfalse. }} // ABI code project.ext. VersionCodes = ['armeabi': 1, 'armeabi-v7a': 2.'arm64-v8a': 3.'mips': 5, 'mips64': 6, 'x86': 8, 'x86_64': 9] android. ApplicationVariants. All {variant - > / / final mark variant. Outputs. Each {output - > output. VersionCodeOverride = project.ext.versionCodes.get(output.getFilter(com.android.build.OutputFile.ABI), 0) * 1000000 + android.defaultConfig.versionCode } } }Copy the code

Questions and summary

1. Resource files in the AAR package are duplicated

1. Resource files in the AAR package are duplicated

The resource file of the main project will directly overwrite the files in the AAR package without any error or prompt. Finally, the aar package will also directly use the resource file of the main project, so pay attention to the naming method. There is no better solution for now.

2.AndroidManifest merge error

As with aar packages, Android Studio projects can have one AndroidManifest.xml file per module, but the final APK file can only contain one Androidmanifest.xml file. When building an application, the Gradle build merges all manifest files into a single manifest file encapsulated in APK. When the aar manifest file conflicts with the properties of our app manifest file: use tools:replace=” property name “to resolve this.

3. Differences between annotationProcessor and compileOnly

So annotationProcessor and compileOnly are both compiled and not typed into APK, so what’s the difference between them? AnnotationProcessor is for generating code at compile time so you really don’t need it. CompileOnly is for having duplicate libraries so you can shave off only one library that you need eventually.

4. Module dependency analysis

If our project indirectly relies on different versions of the same library, it will report an error at compile time:

/gradlew -q < module name >:dependenciesCopy the code

Print out all the dependency tree information for that module:

Can see com. Android. Support. Test: runner: 1.0.2 and com. The android. Support: appcompat – v7:26.1.0 rely on library version is different, And com. Android. Support. Test. Espresso: espresso – core: 3.0.2 again rely on the former, so the conflict of the two libraries depend on rule out line:

androidTestImplementation('com. Android. Support. Test: runner: 1.0.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    androidTestImplementation('com. Android. Support. Test. Espresso: espresso - core: 3.0.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })

Copy the code

Or:

 implementation ('com. Android. Support: appcompat - v7:26.1.0', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
Copy the code

Choose one or the other, and the conflict is resolved.