oynix

于无声处听惊雷,于无色处见繁花

使用非绑定版本的Gradle自动编译UnityEditor导出的Android工程

使用Editor导出或者直接编译Android工程时,需要借助几个外部工具,JDK、SDK、NDK和Gradle,在Preferences中的External Tools中可以看到它们。每个版本的Editor,绑定了固定版本的外部工具。

比如2020.3.41f1版本的Editor,所依赖的外部工具版本是,

  • JDK:1.8.0
  • SDK:根据build.gradle按需
  • NDK:19.0.5232133
  • Gradle:6.1.1

当在External Tools中配置了这些对应版本的工具,并且这些版本可以满足项目的需求时,这两个条件需同时存在,这个时候是可以通过Editor导出工程,也可以直接打包(APK/AAB)。

当工程中引用了较新版本的第三方库依赖时,其他工具还好,6.1.1版本的Gradle就无法满足需求了。

比如16.0的Facebook,最低需要4.2.1的Gradle Plugin编译,而这个版本的Plugin,需要最低6.7的Gradle,Editor绑定的6.1的Gradle无法编译通过。

对于自动化编译打包流程,需要对此情况做个处理。

1. 策略

直线不可行,曲线救国就好。

绑定版本的Gradle不可用,那么便只通过Editor导出Android工程,在自动化流程中增加一步,将导出的工程编译打包成对应的文件,APK/AAB,即可。

过程:导出工程 -> 添加编译脚本 -> 执行脚本

2. Gradle Plugin和Gradle

Gradle常用来编译项目,Android通过Gradle Plugin来使用Gradle,这两者的版本之间也有一定的关联。比如上面所提到的,4.0.1的Gradle Plugin可以配合6.1.1的Gradle使用,而4.2.1的Gradle Plugin最低需要配合6.7的Gradle使用。

如何确认所需的版本,需要结合项目中引入的依赖Dependency。建议使用可用版本中最低的,高版本中的新特性可能会导致一些未可知的问题。

3. 配置Gradle Plugin版本

开启Player Settings中Publishing Settings下的Custom Base Gradle Template选项,然后可以在Assets/Plugins/Android路径看到一个名为baseProjectTemplate.gradle的文件,这个文件里可以配置Gradle Plugin的版本。

不同版本的Editor,这个文件的内容有所区别,较老版本的里面是这样的(省略了其他与此无关的配置代码),

1
classpath 'com.android.tools.build:gradle:4.2.1'

较新版本的里面是这样的(省略了其他与此无关的配置代码)

1
2
id 'com.android.application' version '4.2.1' apply false
id 'com.android.library' version '4.2.1' apply false

形式不影响本质,按需修改即可。

以上方式是在导出Android工程前修改,除此之外,也可以在导出工程后修改:直接修改导出工程根目录下的build.gradle文件即可,内容一致。

4. 配置Gradle版本

与Plugin不同,这个版本只能在导出工程之后修改,配置文件具体位置如下,

1
ExportProjectRoot/gradle/wrapper/gradle-wrapper.properties

看后缀便可知,这是个纯文本文件,里面有一行是这样的,

1
2
3
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip
// 或者
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-all.zip

将末尾修改成需要的版本即可,一般有两种形式,区别在于从gradle官网下载的文件不同,使用上无区别,建议使用all,都下载下来。

5. 增加执行编译命令的jar包

通过gradle编译、构建Android工程非常简洁,每种操作只需要一个命令。比如,assembleRelease命令可以输出Release的APK安装包,bundleRelease命令可以输出Release的AAB安装包,同样assembleDebug命令可以输出Debug的APK安装包,等等,gradle plugin同时支持了很多常用的构建命令。

gradle-wrapper.jar则是解析并处理这些构建命令的,所以我们需要把这个工具jar包放到导出的Android里。一般通过Android Studio创建的Android工程里面已经存在了这个jar包,但是通过Editor导出的Android工程中并不存在这个jar包。每个版本的gradle都包含这个jar文件,找到对应版本的jar文件,放到Android工程这个路径下即可,注意文件名需要保持一致

1
ExportProjectRoot/gradle/wrapper/gradle-wrapper.jar

那么,去哪找gradle-wrapper.jar呢?

通常情况下,可以到GRADLE_USER_HOME下找到本机中存在的所有版本的gradle,这个路径在这里,

1
~/.gradle/wrapper/dists/

找到了可以直接用,找不到也没关系,去官网下载也可以,地址是:https://gradle.org/releases/

注意,需要找到对应版本的gradle,比如我说的这个例子中,涉及的版本是6.7.1。

不论是本机的,还是刚下载的,里面都会有一个lib的目录,这个目录下有很多jar文件,我们只需要找到包含版本名的wrapper文件,比如,6.7.1版本对应的是gradle-wrapper-6.7.1.jar。虽然名字有点和上面我们所需要的jar文件名类似,但这个并不是,我们需要给它解压,通过以下命令,

1
jar -xvf gradle-wrapper.6.7.1.jar

这个操作很快,完成之后,就可以看到我们需要的gradle-wrapper.jar文件了,每个版本的gradle对应的这个wrapper文件是固定的,所以这个操作只需要执行一次,获取到之后就可以一直重复用了。

6. 编译脚本

简单来说,这个脚本就是用来调用gradle-wrapper.jar的。正如上面所提到的,使用AndroidStudio创建的工程,也会自动创建脚本,所有的工程创建出来的都是一样的,可以复用。

Windows使用gradlew.bat

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem

@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################

@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal

set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%

@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi

@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"

@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome

set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute

echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.

goto fail

:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe

if exist "%JAVA_EXE%" goto execute

echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.

goto fail

:execute
@rem Setup the command line

set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar


@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*

:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd

:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1

:mainEnd
if "%OS%"=="Windows_NT" endlocal

:omega

非Windows使用gradlew

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
#!/usr/bin/env sh

#
# Copyright 2015 the original author or authors.
#
# 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
#
# https://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.
#

##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################

# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null

APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`

# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'

# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"

warn () {
echo "$*"
}

die () {
echo
echo "$*"
echo
exit 1
}

# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac

CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar


# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME

Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.

Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi

# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi

# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi

# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`

JAVACMD=`cygpath --unix "$JAVACMD"`

# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option

if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi

# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`

# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"

exec "$JAVACMD" "$@"

将这两个文件放到导出项目根目录下即可,注意保持文件名一致。

1
2
ExportProjectRoot/gradlew.bat
ExportProjectRoot/gradlew

7. 编译

一般情况下,输出文件的类型会从两个维度区别,一个是Release/Debug,一个是APK/AAB,两两组合下,就会有4个命令:

Windows

1
2
3
4
5
6
7
8
9
10
11
# 输出Release APK
gradlew.bat assembleRelease

# 输出Debug APK
gradlew.bat assembleDebug

# 输出Release AAB
gradlew.bat bundleRelease

# 输出Debug AAB
gradlew.bat bundleDebug

非Windows

1
2
3
4
5
6
7
8
9
10
11
# 输出Release APK
gradlew assembleRelease

# 输出Debug APK
gradlew assembleDebug

# 输出Release AAB
gradlew bundleRelease

# 输出Debug AAB
gradlew bundleDebug

8. 总结

如上所述,由Editor直接编译输出安装包,到只由Editor导出工程后,手动编译输出安装包,中间增加了修改配置文件、增加编译工具的步骤,将这几个步骤集成到自动化流程中,便可以一步出包了。

附录一:Windows CMD不识别转义控制字符\033

gradle编译过程中,会使用转义控制字符\033来完成一些操作,比如清屏、控制输出光标位置、改变输出字符颜色等等。直接通过CMD窗口执行bat脚本,显示是没有问题的,但是如果通过Process类调用,存在不识别转义字符的可能,以至于控制台输出十分杂乱。

通过在注册表中增加一条配置,可解决这个问题:

1
reg add HKCU\Console /v VirtualTerminalLevel /t REG_DWORD /d 1 /f

如果想把这个值恢复为0,可通过以下命令,当然也可以直接执行regedit,找到这个配置然后删除

1
reg add HKCU\Console /v VirtualTerminalLevel /t REG_DWORD /d 0

附录二:下载历史版本的NDK

开头提到过,Editor的版本和NDK的版本是绑定的,比如2020.3.41f1的Editor,需要19.0.5232133版本的NDK,这个可以在External Tools中看到。去到NDK的官网下载,https://github.com/android/ndk/wiki/Unsupported-Downloads,可以看到19版本的NDK只有一个r19c,也就是19.2.5345600,我需要的是r19b,也就是上一个版本。

虽然页面不提供,但是历史文件是存在的,复制一下r19c的下载链接,

1
https://dl.google.com/android/repository/android-ndk-r19c-darwin-x86_64.zip

只需要将链接中的r19c,改成r19b,即可。下载完成之后,可以看到解压后的根目录下有个source.properties文件,其中记录的版本即为需要的版本19.0.5232133。

1
2
3
4
// source.properties
Pkg.Desc = Android NDK

Pkg.Revision = 19.0.5232133

用同样的方法,也可以下载其他历史版本的NDK。

附录三:执行不支持Apple Silicon的NDK

有些较老版本的NDK,在Apple Silicon上执行时会报错,说该NDK不支持Apple Silicon。可通过修改ndk-build文件来解决这个问题,这个文件在NDK解压后的根目录,以文本文件的形式打开即可,其中内容很简洁,如下,

1
2
3
#!/bin/sh
DIR="$(cd "$(dirname "$0")" && pwd)"
$DIR/build/ndk-build "$@"

修改如下即可,

1
2
3
#!/bin/sh
DIR="$(cd "$(dirname "$0")" && pwd)"
arch -x86_64 $DIR/build/ndk-build "$@"
------------- (完) -------------
  • 本文作者: oynix
  • 本文链接: https://oynix.com/2024/04/9098ecdaa243/
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!

欢迎关注我的其它发布渠道