場景
- 在構建
Android App
的時候,最常用的構建工具就是gradle
,這個工具使用一種.gradle
的文本進行任務描述. 最常見的就是根據build.gradle
的配置指定使用gradle
的版本。接着根據項目的配置下載所依賴的jar
包並編譯.
dependencies {
classpath 'com.android.tools.build:gradle:3.6.2'
}
- 我對
gradle
還沒系統的學習過. 今天在編譯google
的例子項目時在項目根目錄使用命令行運行gradlew.bat
就報以下錯誤. 項目都是用的java
代碼編寫,何來的kotlin
錯誤?
* What went wrong:
A problem occurred configuring project ':app'.
> kotlin.KotlinNullPointerException (no error message)
* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.
* Get more help at https://help.gradle.org
說明
- 注意這個
gradle
是針對android
的構建插件,而android
目前官方推薦使用kotlin
語言開發, 所以gradle
插件需要集成kotlin
包也不奇怪. 我們根據提示運行增加參數gradlew.bat --stacktrace
. 在命令行運行輸出了一段比較長的錯誤, 其中就有KotlinNullPointerException
拋出的源碼位置. 可見在文件DataBindingCompilerArguments.kt
的 170 行的createArguments
方法拋出了異常. 而這個 170 行就是sdkDir = globalScope.sdkComponents.getSdkFolder()!!,
獲取SDK
目錄時拋出的異常。很明顯了,就是獲取不到Android SDK
目錄.
Caused by: kotlin.KotlinNullPointerException
at com.android.build.gradle.internal.tasks.databinding.DataBindingCompilerArguments$Companio
n.createArguments(DataBindingCompilerArguments.kt:170)
-
那麼設置
Android SDK
我所知道的有3種方法.- 在我的電腦右鍵屬性->高級->環境變量->用戶變量增加
ANDROID_HOME
作爲變量名,E:\software\Android\sdk
你自己的Android SDK
安裝目錄. 這個變量值在所有新建的命令行有效. - 在當前命令行輸出回車
set ANDROID_HOME=E:\software\Android\sdk
,那麼這個變量值在當前的命令行有效. - 在當前的項目目錄增加一個
local.properties
的文本文件,增加文本值sdk.dir=E\:\\software\\Android\\sdk
即可.
- 在我的電腦右鍵屬性->高級->環境變量->用戶變量增加
-
設置完之後這個編譯步驟就會成功,當然剩下的下載
jar
包可能由於網絡不通造成下載失敗就是另外的問題了。 -
至於這個文件
DataBindingCompilerArguments.kt
源碼只能通過google
搜索,bing
是搜不到的. 至於這個類所在的包com.android.build.gradle.internal.tasks.databinding
和它所在的jar
在gradle
下載的包gradle-3.6.2.jar
裏, 當然根據依賴的gradle
插件版本判斷,我這裏是com.android.tools.build:gradle:3.6.2
. 所以在3.6.2
目錄下.
C:\Users\用戶名\.gradle\caches\modules-2\files-2.1\com.android.tools.build\gradle\3.6.2\a5e817cf4833326b12f3e599f97486a04fe62756\gradle-3.6.2.jar
順便說一下,gradle
下載的第三方 jar
,包括 gradle
的 android
插件包是在以下目錄裏:
C:\Users\用戶名\.gradle\caches\modules-2\files-2.1
而安裝的 gradle
,注意不是插件包, 是在以下目錄:
C:\Users\用戶名\.gradle\wrapper\dists
- 我們來看看這個項目的
.gitigore
忽略設置, 就包含了當前項目的local.properties
文件,所以這個項目裏這個文件的是提交不了的.
*.iml
.idea
.gradle
/local.properties
.DS_Store
build/
/captures
.externalNativeBuild
源碼參考
DataBindingCompilerArguments.kt
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.build.gradle.internal.tasks.databinding
import android.databinding.tool.CompilerArguments
import com.android.build.gradle.internal.scope.InternalArtifactType
import com.android.build.gradle.internal.scope.InternalArtifactType.DATA_BINDING_BASE_CLASS_LOG_ARTIFACT
import com.android.build.gradle.internal.scope.InternalArtifactType.DATA_BINDING_DEPENDENCY_ARTIFACTS
import com.android.build.gradle.internal.scope.InternalArtifactType.DATA_BINDING_LAYOUT_INFO_TYPE_MERGE
import com.android.build.gradle.internal.scope.InternalArtifactType.DATA_BINDING_LAYOUT_INFO_TYPE_PACKAGE
import com.android.build.gradle.internal.scope.InternalArtifactType.FEATURE_DATA_BINDING_BASE_FEATURE_INFO
import com.android.build.gradle.internal.scope.InternalArtifactType.FEATURE_DATA_BINDING_FEATURE_INFO
import com.android.build.gradle.internal.scope.VariantScope
import com.android.build.gradle.options.BooleanOption
import org.gradle.api.file.Directory
import org.gradle.api.file.RegularFile
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.PathSensitive
import org.gradle.api.tasks.PathSensitivity
import org.gradle.process.CommandLineArgumentProvider
import java.io.File
/**
* Arguments passed to data binding. This class mimics the [CompilerArguments] class except that it
* also implements [CommandLineArgumentProvider] for input/output annotations.
*/
@Suppress("MemberVisibilityCanBePrivate")
class DataBindingCompilerArguments constructor(
@get:Input
val incremental: Boolean,
@get:Input
val artifactType: CompilerArguments.Type,
// Use module package provider so that we can delay resolving the module package until execution
// time (for performance). The resolved module package is set as @Input (see getModulePackage()
// below), but the provider itself should be set as @Internal.
private val modulePackageProvider: () -> String,
@get:Input
val minApi: Int,
// We can't set the sdkDir as an @InputDirectory because it is too large to compute a hash. We
// can't set it as an @Input either because it would break cache relocatability. Therefore, we
// annotate it with @Internal, expecting that the directory's contents should be stable and this
// won't affect correctness.
@get:Internal
val sdkDir: File,
@get:InputFiles
@get:PathSensitive(PathSensitivity.RELATIVE)
val dependencyArtifactsDir: Provider<Directory>,
@get:InputFiles
@get:PathSensitive(PathSensitivity.RELATIVE)
val layoutInfoDir: Provider<Directory>,
@get:InputFiles
@get:PathSensitive(PathSensitivity.RELATIVE)
val classLogDir: Provider<Directory>,
@get:Optional
@get:InputFiles
@get:PathSensitive(PathSensitivity.RELATIVE)
val baseFeatureInfoDir: Provider<Directory>,
@get:Optional
@get:InputFiles
@get:PathSensitive(PathSensitivity.RELATIVE)
val featureInfoDir: Provider<Directory>,
@get:Optional
@get:OutputDirectory
val aarOutDir: Provider<Directory>,
@get:Optional
@get:OutputFile
val exportClassListOutFile: File?,
@get:Input
val enableDebugLogs: Boolean,
// We don't set this as an @Input because: (1) it doesn't affect the results of data binding
// processing, and (2) its value is changed between an Android Studio build and a command line
// build; by not setting it as @Input, we allow the users to get incremental/UP-TO-DATE builds
// when switching between the two modes (see https://issuetracker.google.com/80555723).
@get:Internal
val printEncodedErrorLogs: Boolean,
@get:Input
val isTestVariant: Boolean,
@get:Input
val isEnabledForTests: Boolean,
@get:Input
val isEnableV2: Boolean
) : CommandLineArgumentProvider {
@Input
fun getModulePackage() = modulePackageProvider()
override fun asArguments(): Iterable<String> {
val arguments = CompilerArguments(
incremental = incremental,
artifactType = artifactType,
modulePackage = getModulePackage(),
minApi = minApi,
sdkDir = sdkDir,
dependencyArtifactsDir = dependencyArtifactsDir.get().asFile,
layoutInfoDir = layoutInfoDir.get().asFile,
classLogDir = classLogDir.get().asFile,
baseFeatureInfoDir = baseFeatureInfoDir.orNull?.asFile,
featureInfoDir = featureInfoDir.orNull?.asFile,
aarOutDir = aarOutDir.orNull?.asFile,
exportClassListOutFile = exportClassListOutFile,
enableDebugLogs = enableDebugLogs,
printEncodedErrorLogs = printEncodedErrorLogs,
isTestVariant = isTestVariant,
isEnabledForTests = isEnabledForTests,
isEnableV2 = isEnableV2
).toMap()
// Don't need to sort the returned list as the order shouldn't matter to Gradle.
// Also don't need to escape the key and value strings as they will be passed as-is to
// the Java compiler.
return arguments.map { entry -> "-A${entry.key}=${entry.value}" }
}
companion object {
@JvmStatic
fun createArguments(
variantScope: VariantScope,
enableDebugLogs: Boolean,
printEncodedErrorLogs: Boolean
): DataBindingCompilerArguments {
val globalScope = variantScope.globalScope
val variantData = variantScope.variantData
val variantConfig = variantScope.variantConfiguration
val artifacts = variantScope.artifacts
return DataBindingCompilerArguments(
incremental = globalScope.projectOptions
.get(BooleanOption.ENABLE_INCREMENTAL_DATA_BINDING),
artifactType = getModuleType(variantScope),
modulePackageProvider = { variantConfig.originalApplicationId },
minApi = variantConfig.minSdkVersion.apiLevel,
sdkDir = globalScope.sdkComponents.getSdkFolder()!!,
dependencyArtifactsDir =
artifacts.getFinalProduct(DATA_BINDING_DEPENDENCY_ARTIFACTS),
layoutInfoDir = artifacts.getFinalProduct(getLayoutInfoArtifactType(variantScope)),
classLogDir = artifacts.getFinalProduct(DATA_BINDING_BASE_CLASS_LOG_ARTIFACT),
baseFeatureInfoDir = artifacts.getFinalProduct(
FEATURE_DATA_BINDING_BASE_FEATURE_INFO
),
featureInfoDir = artifacts.getFinalProduct(FEATURE_DATA_BINDING_FEATURE_INFO),
aarOutDir = artifacts.getFinalProduct(InternalArtifactType.DATA_BINDING_ARTIFACT),
exportClassListOutFile = variantScope.generatedClassListOutputFileForDataBinding
.takeIf { variantData.type.isExportDataBindingClassList },
enableDebugLogs = enableDebugLogs,
printEncodedErrorLogs = printEncodedErrorLogs,
isTestVariant = variantData.type.isTestComponent,
isEnabledForTests = globalScope.extension.dataBinding.isEnabledForTests,
isEnableV2 = true
)
}
/**
* Returns the module type of a variant. If it is a testing variant, return the module type
* of the tested variant.
*/
@JvmStatic
fun getModuleType(variantScope: VariantScope): CompilerArguments.Type {
val variantData = if (variantScope.variantData.type.isTestComponent) {
variantScope.testedVariantData!!
} else {
variantScope.variantData
}
return if (variantData.type.isAar) {
CompilerArguments.Type.LIBRARY
} else {
if (variantData.type.isBaseModule) {
CompilerArguments.Type.APPLICATION
} else {
CompilerArguments.Type.FEATURE
}
}
}
/**
* Returns the appropriate artifact type of the layout info directory so that it does not
* trigger unnecessary computations (see bug 133092984 and 110412851).
*/
@JvmStatic
fun getLayoutInfoArtifactType(variantScope: VariantScope): InternalArtifactType<Directory> {
return if (variantScope.variantData.type.isAar) {
DATA_BINDING_LAYOUT_INFO_TYPE_PACKAGE
} else {
DATA_BINDING_LAYOUT_INFO_TYPE_MERGE
}
}
}
}
參考
DataBindingCompilerArguments.kt
kotlin.KotlinNullPointerException (no error message) while building APK using Jenkins