[Android]_[gradle]_[gradlew.bat報錯KotlinNullPointerException]

場景

  1. 在構建 Android App 的時候,最常用的構建工具就是 gradle ,這個工具使用一種.gradle 的文本進行任務描述. 最常見的就是根據 build.gradle 的配置指定使用 gradle 的版本。接着根據項目的配置下載所依賴的jar包並編譯.
dependencies {
        classpath 'com.android.tools.build:gradle:3.6.2'
    }
  1. 我對 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

說明

  1. 注意這個 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)
  1. 那麼設置 Android SDK 我所知道的有3種方法.

    1. 在我的電腦右鍵屬性->高級->環境變量->用戶變量增加 ANDROID_HOME 作爲變量名, E:\software\Android\sdk 你自己的 Android SDK 安裝目錄. 這個變量值在所有新建的命令行有效.
    2. 在當前命令行輸出回車 set ANDROID_HOME=E:\software\Android\sdk,那麼這個變量值在當前的命令行有效.
    3. 在當前的項目目錄增加一個 local.properties 的文本文件,增加文本值sdk.dir=E\:\\software\\Android\\sdk即可.
  2. 設置完之後這個編譯步驟就會成功,當然剩下的下載 jar包可能由於網絡不通造成下載失敗就是另外的問題了。

  3. 至於這個文件 DataBindingCompilerArguments.kt 源碼只能通過 google 搜索, bing 是搜不到的. 至於這個類所在的包 com.android.build.gradle.internal.tasks.databinding 和它所在的 jargradle 下載的包 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,包括 gradleandroid 插件包是在以下目錄裏:

C:\Users\用戶名\.gradle\caches\modules-2\files-2.1

而安裝的 gradle,注意不是插件包, 是在以下目錄:

C:\Users\用戶名\.gradle\wrapper\dists
  1. 我們來看看這個項目的.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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章