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 packageimplementation 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.