新建一個AS工程,編譯器會自動幫咱們建立了Gradle Wrapper相關的文件,目錄結構以下:java
├── gradle
│ └── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
└── gradlew.bat
複製代碼
這裏有幾個問題:git
這個gradlew和gradle有什麼區別?github
咱們在gradle-wrapper.properties中配置的信息是如何被解析的?能配置的項又有哪些?windows
若是網絡很差,如何本身手動下載gradle壓縮包配置版本庫?數組
下面會分析Gradle Wrapper的實現原理,解答這幾個問題。網絡
通常咱們編譯Android項目,不會直接執行gradle命令,而是執行gradlew xxxx,而這個gradlew,其實就是執行當前目錄下的gradlew.bat(windows平臺下),因此先看一下這個批處理文件究竟執行了什麼命令:app
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
複製代碼
這裏會去運行/gradle/wrapper/目錄下的gradle-wrapper.jar,而後執行其org.gradle.wrapper.GradleWrapperMain類的main方法:gradle
public static void main(String[] args) throws Exception {
File wrapperJar = wrapperJar();
File propertiesFile = wrapperProperties(wrapperJar);
File rootDir = rootDir(wrapperJar);
CommandLineParser parser = new CommandLineParser();
parser.allowUnknownOptions();
parser.option(GRADLE_USER_HOME_OPTION, GRADLE_USER_HOME_DETAILED_OPTION).hasArgument();
parser.option(GRADLE_QUIET_OPTION, GRADLE_QUIET_DETAILED_OPTION);
SystemPropertiesCommandLineConverter converter = new SystemPropertiesCommandLineConverter();
converter.configure(parser);
ParsedCommandLine options = parser.parse(args);
Properties systemProperties = System.getProperties();
systemProperties.putAll(converter.convert(options, new HashMap<String, String>()));
File gradleUserHome = gradleUserHome(options);
addSystemProperties(gradleUserHome, rootDir);
Logger logger = logger(options);
WrapperExecutor wrapperExecutor = WrapperExecutor.forWrapperPropertiesFile(propertiesFile);
wrapperExecutor.execute(
args,
new Install(logger, new Download(logger, "gradlew", UNKNOWN_VERSION), new PathAssembler(gradleUserHome)),
new BootstrapMainStarter());
}
複製代碼
調用wrapperProperties方法找到當前目錄下的gradle-wrapper.properties文件,該文件是用來指定一些配置信息的,後面會分析,先無論,接着會解析命令行傳遞過來的參數,最後一步是關鍵,會調用WrapperExecutor對象的execute方法,傳入了一個Install對象,這個是用來處理gradle的下載,若是指定版本的gradle在指定的gradle倉庫中沒有,就先下載解壓,execute方法傳入的另外一個對象是BootstrapMainStarter對象,這個是用來啓動執行gradle的幫助類,最終會執行。接下來詳細分析這兩步的內部實現原理:優化
Install對象下載安裝gradle庫url
public File createDist(final WrapperConfiguration configuration) throws Exception {
final URI distributionUrl = configuration.getDistribution();
final String distributionSha256Sum = configuration.getDistributionSha256Sum();
final PathAssembler.LocalDistribution localDistribution = pathAssembler.getDistribution(configuration);
final File distDir = localDistribution.getDistributionDir();
final File localZipFile = localDistribution.getZipFile();
return exclusiveFileAccessManager.access(localZipFile, new Callable<File>() {
public File call() throws Exception {
final File markerFile = new File(localZipFile.getParentFile(), localZipFile.getName() + ".ok");
if (distDir.isDirectory() && markerFile.isFile()) {
InstallCheck installCheck = verifyDistributionRoot(distDir, distDir.getAbsolutePath());
if (installCheck.isVerified()) {
return installCheck.gradleHome;
}
// Distribution is invalid. Try to reinstall.
System.err.println(installCheck.failureMessage);
markerFile.delete();
}
boolean needsDownload = !localZipFile.isFile();
URI safeDistributionUrl = Download.safeUri(distributionUrl);
if (needsDownload) {
File tmpZipFile = new File(localZipFile.getParentFile(), localZipFile.getName() + ".part");
tmpZipFile.delete();
logger.log("Downloading " + safeDistributionUrl);
download.download(distributionUrl, tmpZipFile);
tmpZipFile.renameTo(localZipFile);
}
List<File> topLevelDirs = listDirs(distDir);
for (File dir : topLevelDirs) {
logger.log("Deleting directory " + dir.getAbsolutePath());
deleteDir(dir);
}
verifyDownloadChecksum(configuration.getDistribution().toString(), localZipFile, distributionSha256Sum);
try {
unzip(localZipFile, distDir);
} catch (IOException e) {
logger.log("Could not unzip " + localZipFile.getAbsolutePath() + " to " + distDir.getAbsolutePath() + ".");
logger.log("Reason: " + e.getMessage());
throw e;
}
InstallCheck installCheck = verifyDistributionRoot(distDir, safeDistributionUrl.toString());
if (installCheck.isVerified()) {
setExecutablePermissions(installCheck.gradleHome);
markerFile.createNewFile();
return installCheck.gradleHome;
}
// Distribution couldn't be installed.
throw new RuntimeException(installCheck.failureMessage);
}
});
}
複製代碼
代碼比較長,但思路很清晰,主要是注意一些細節問題,有利於平時開發,主要步驟以下:
(1)首先是拿到distributionUrl,這個就是gradle的下載地址,也就是咱們在gradle-wrapper.properties文件中配置的那個連接。
(2)而後調用ExclusiveFileAccessManager的access方法,這個方法主要是去lock一個lck文件,這個文件的做用就是實如今某個時刻只能有一個使用者在使用該gradle庫,因此下載前須要lock住這個文件。
(3)lock後,繼續判斷文件是否存在,若是已經存在了,就返回,不然就開始下載,下載存放目錄是經過gradle-wrapper.properties文件的distributionPath屬性配置的,下載時會先下載到一個part文件,等下載完成了,就重命名爲最終的文件名,這裏有個細節,每次下載都會刪除掉以前的part文件,也就是說這裏並不支持斷點下載,若是文件比較大而後又由於網絡緣由下載失敗了,下次仍是得從新下載,可能又會繼續失敗,而後一直反反覆覆就會很煩,這裏咱們能夠直接修改代碼優化。
(4)下載後,就要校驗sha256,不過只有在gradle-wrapper.properties文件中配置了sha256碼的狀況下才會進行校驗,沒配置的就跳過。
(5)接着是解壓,由於下載下來是一個zip文件,解壓後文件的存放目錄是經過gradle-wrapper.properties文件的distributionPath屬性配置的。
(6)最後是簡單校驗解壓後的文件是否正確,好比是否有gradle-launcher-xxx.jar之類。若是校驗經過,還會建立一個ok文件,用於表示該gradle庫下載校驗經過,這個gradle庫能夠被正常使用。
(7)ExclusiveFileAccessManager的access方法unlock前面那個lck文件,至此流程結束,該gradle能夠被使用了。
下載流程說完了,這裏還有個問題,就是咱們去看了一下下載後的目錄,以下:
C:\Users\mai\.gradle\wrapper\dists\gradle-5.4.1-all\3221gyojl5jsh0helicew7rwx
複製代碼
咱們發現這個路徑跟distributionPath指定的仍是有點不一樣,gradle-5.4.1-all目錄還能夠解釋得通,不一樣版本確定要放到不一樣目錄下,因此乾脆就用版本名命名目錄就行了,但咱們還看到下一級目錄名爲3221gyojl5jsh0helicew7rwx,這個就不太容易看得出來了,這時咱們就去代碼裏查找:
public LocalDistribution getDistribution(WrapperConfiguration configuration) {
String baseName = getDistName(configuration.getDistribution());
String distName = removeExtension(baseName);
String rootDirName = rootDirName(distName, configuration);
File distDir = new File(getBaseDir(configuration.getDistributionBase()), configuration.getDistributionPath() + "/" + rootDirName);
File distZip = new File(getBaseDir(configuration.getZipBase()), configuration.getZipPath() + "/" + rootDirName + "/" + baseName);
return new LocalDistribution(distDir, distZip);
}
private String rootDirName(String distName, WrapperConfiguration configuration) {
String urlHash = getHash(Download.safeUri(configuration.getDistribution()).toString());
return distName + "/" + urlHash;
}
private String getHash(String string) {
try {
MessageDigest messageDigest = MessageDigest.getInstance("MD5");
byte[] bytes = string.getBytes();
messageDigest.update(bytes);
return new BigInteger(1, messageDigest.digest()).toString(36);
} catch (Exception e) {
throw new RuntimeException("Could not hash input string.", e);
}
}
複製代碼
getHash方法的返回值就是這個3221gyojl5jsh0helicew7rwx目錄名,能夠看到它是計算傳入的string的MD5值字節數組,而後轉成BigInteger,最後用36進製表示返回字符串,而這個string的值,就是gradle的下載地址https://services.gradle.org/distributions/gradle-5.4.1-all.zip,咱們能夠本身調用getHash方法,傳入這個地址,發現返回值就是3221gyojl5jsh0helicew7rwx,說明咱們的分析是正確的。
總結下,下載流程涉及到幾個文件,lck文件用於實現使用互斥,part文件是下載過程當中的臨時文件,ok文件表示該gradle庫能夠正常使用,若是沒有ok文件,執行gradlew命令後會致使從新解壓(不存在zip文件就先下載)。
BootstrapMainStarter對象啓動執行gradle庫
這個就比較簡單了,BootstrapMainStarter的start方法:
public void start(String[] args, File gradleHome) throws Exception {
File gradleJar = findLauncherJar(gradleHome);
if (gradleJar == null) {
throw new RuntimeException(String.format("Could not locate the Gradle launcher JAR in Gradle distribution '%s'.", gradleHome));
}
URLClassLoader contextClassLoader = new URLClassLoader(new URL[]{gradleJar.toURI().toURL()}, ClassLoader.getSystemClassLoader().getParent());
Thread.currentThread().setContextClassLoader(contextClassLoader);
Class<?> mainClass = contextClassLoader.loadClass("org.gradle.launcher.GradleMain");
Method mainMethod = mainClass.getMethod("main", String[].class);
mainMethod.invoke(null, new Object[]{args});
if (contextClassLoader instanceof Closeable) {
((Closeable) contextClassLoader).close();
}
}
複製代碼
能夠看到,這裏反射調用gradle-launcher-xxx.jar中的org.gradle.launcher.GradleMain的main方法,這個GradleMain類,也就是gradle的入口類,至於接下來的流程,後面的文章會繼續分析。
總結,gradlew的做用就是在執行gradle前,確保gradle倉庫中有項目所須要的gradle版本,若是沒有就會先下載解壓準備好。
這個文件用來指定gradle-wrapper.jar執行所須要的配置信息,主要有五種信息:
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
複製代碼
distributionBase
指定gradle庫解壓後存放目錄的根目錄,默認設置爲GRADLE_USER_HOME,這個其實就是環境變量名,若是沒有設定,就會採用默認的gradle倉庫根目錄,windows下爲C:\Users\用戶名.gradle,若是要本身指定到某個目錄,就去新建個叫GRADLE_USER_HOME的環境變量,而後路徑填本身的路徑就行,這段邏輯在GradleUserHomeLookup.java類裏,有興趣能夠本身查看。
distributionBase還有另一個取值就是PROJECT,表示當前工程的根目錄。
distributionPath
指定gradle庫解壓後存放目錄的子目錄。
zipStoreBase
指定下載的gradle壓縮包的存放目錄的根目錄。
zipStorePath
指定下載的gradle壓縮包的存放目錄的子目錄。
distributionUrl
指定gradle庫的下載地址。
有時由於各類緣由,執行gradlew後下載gradle比較慢,甚至失敗,根據上面對gradlew下載流程的分析,咱們能夠本身去網上下載,而後按照規則存放文件,就能夠直接使用了,好比說咱們要下載gradle-5.6.4-all,具體的步驟以下:
在C:\Users\mai.gradle\wrapper\dists下新建目錄gradle-5.6.4-all,進入目錄。
容許上面所說的getHash方法,傳入https://services.gradle.org/distributions/gradle-5.6.4-all.zip,獲得ankdp27end7byghfw1q2sw75f,以該字符串新建目錄。
去網上下載gradle-5.6.4-all.zip,放到ankdp27end7byghfw1q2sw75f目錄下,解壓。
新建gradle-5.6.4-all.zip.lck和gradle-5.6.4-all.zip.ok兩個文件。
這樣就配置好了,咱們把gradle-wrapper.properties的distributionUrl改成https://services.gradle.org/distributions/gradle-5.6.4-all.zip,而後執行gradlew命令,發現已經檢測到咱們配置的gradle庫了,不會再去下載。