使用Android Studio構建Cordova項目
現在Android開發的IDE用戶漸漸轉投到Android Studio下了。而在用Cordova開發時,雖然多數時候是web開發,但有些情況,比如開發自定義插件時,還是要進行Android開發。那么就需要在Android Studio中打開Cordova項目中的Android部分。
在我以前關于Cordova的例子中,如果遇到Android開發工作,IDE用的都是Eclipse。現在Cordova 5已經在創建項目時為我們生成Gradle腳本了。這樣在Android Studio打開時就可以按現有的Android Studio項目打開。
而關于Gradle基礎,本來想詳細的寫一下。但一位叫neu的作者的譯作《Gradle for Android》(以下簡稱G文)已經寫的很詳細了,而且同名書也是一本好書,所以推薦,不再贅述。
用以前一篇叫《構建一個完整的Cordova應用》的代碼為例,打開后項目結構如下圖:
項目結構
從圖1中可以看到導入的項目結構。可以看到和在Android Studio新建的安裝項目的結構有些不同。它包括CordovaLib模塊、android模塊和一些gradle腳本。
一般Android項目結構和目錄結構一樣,是這樣的:
MyApp
|--build.gradle |--settings.gradle |--app |-- build.gradle |-- build |-- libs |-- src |-- main |--java | |-- com.package.myapp |--res |-- drawable |-- layout |-- etc
由Cordova創建的項目的目錄結構是這樣的(項目結構見上圖)
看起來很亂是不是。但是不影響使用,實際上也沒必要非要改成標準格式。因為Gradle已經幫我們在配置腳本中寫好了相關配置,它知道如何找到需要的文件。
按照標準的Gradle教程,項目即使沒有任何模塊(module),Android Studio也會為我們生成一個對應于項目的build.gradle腳本文件的。而觀察圖1,Android Studio打開的Cordova項目中卻沒有,編譯構建工作也可以正常進行,說明這個文件不是必須的。
它是在andorid、CordovaLib模塊中的build.gradle腳本中的android任務的sourceSets的main屬性,其中定義了Android Studio項目目錄結構和真實目錄的對應關系。比如android模塊下的build.gradle是這樣的:
sourceSets { main { manifest.srcFile 'AndroidManifest.xml' java.srcDirs = ['src'] resources.srcDirs = ['src'] aidl.srcDirs = ['src'] renderscript.srcDirs = ['src'] res.srcDirs = ['res'] assets.srcDirs = ['assets'] jniLibs.srcDirs = ['libs'] } }
詳細請參考G文第一篇 '保持舊的Eclipse文件結構' 部分。
可以看到,Android Studio中目錄的邏輯結構和系統中的文件夾結構是分離的,依靠的就是Gradle的配置能力。對比標準的項目目錄結構,Cordova項目把android模塊直接放在了項目的根下,而不是其他模塊那樣(比如CordovaLib模塊)作為子文件夾存在,這也說明了它為什么不需要項目的build.gradle腳本,因為項目的腳本就是android模塊的腳本。之后添加的模塊,都是作為根目錄的子文件夾存在的。
此外還有一個多出來的cordova.gradle,以及和G文名稱不一致的local.properties。
Gradle腳本的詳細說明
上面部分讓我們對用Android Studio打開的Cordova項目有了些感性了解。它有其自已的獨特性:一方面Cordova創建的項目有自己的Gradle配置腳本,另一方面又保留了自己獨特的結構。接下來讓我們對Gradle腳本的各個部分作較為詳細的了解,同樣的基礎知識請參考G文或《Gradle for Android》一書。
我們看到Gradle腳本相關的文件有build.gradle(CordovaLib模塊)、build.gradle(android模塊)、cordova.gradle(CordovaLib模塊)、settings.gradle(項目)、local.properties(SDK Location)。看起來有些不一樣。
不過沒關系,我們知道Gradle構建時首先要去找build.gradle腳本的。通過上面的項目結構知道,首先執行的應該是邏輯上屬于android模塊,但實際上位于項目根目錄下的build.gradle。就從這里開始。
打開build.gradle。首先看到一些Cordova生成的一些注釋。原文就不照抄了,大意是一些授權說明信息。最后的單行注釋很關鍵:"生成文件!請不要編輯!"。 雖然不能編輯,但一則其他的文件并沒有這么寫,二則做為入口文件,我們還是需要對它做一個了解。
注釋后緊接著就是buildscript方法。在它里面首先是repositories方法,它告訴我們使用的庫是mavenCentral。接下來是依照已經在本地安裝的gradle的版本選擇使用的Gradle插件版本,并且語句上面有注釋可以參考,以后幾乎在每條語句上面都有注釋幫助我們理解并告訴我們相關參考資料的位置。
// 列表1-1。 buildscript { repositories { mavenCentral() } // Switch the Android Gradle plugin version requirement depending on the // installed version of Gradle. This dependency is documented at // http://tools.android.com/tech-docs/new-build-system/version-compatibility // and https://issues.apache.org/jira/browse/CB-8143 if (gradle.gradleVersion >= "2.2") { dependencies { classpath 'com.android.tools.build:gradle:1.0.0+' } } else if (gradle.gradleVersion >= "2.1") { dependencies { classpath 'com.android.tools.build:gradle:0.14.0+' } } else { dependencies { classpath 'com.android.tools.build:gradle:0.12.0+' } } }
隨后又使用了一個repositories任務。注釋說:允許插件通過build-extras.gradle聲明Maven依賴。通過extra這個名字,以及前面不要編輯的警告,它的作用可能是對這個build.gradle文件的修改或補充。其實在下面的代碼中可以看到更多這方面的信息。
// 列表1-2。 repositories { mavenCentral() }
接下來有一個wrapper任務,查閱文檔得知它用于定制Gradle Wrapper的。如果對Wrapper不了解,請參考《G》文的第一篇"使用Gradle Wrapper"部分。
// 列表1-3。 task wrapper(type: Wrapper) { gradleVersion = '2.2.1' }
接下來定義了一個ext屬性,其中定義了一些額外屬性。注釋也說明了其中定義的屬性需要通過環境變量、build-extras.gradle或gradle.properties設置。
ext屬性中首先引用了cordova.gradle。這樣就知道項目中這個文件用在哪里了。但下面并沒有用到它。通過后面的代碼推測它可能是供build-extras.gradle調用的。接下來一系列條件判斷語句分別定義了一些屬性并把它們初為null。這些屬性是都以cdv開頭,表示一些Cordova構建屬性。接下來的代碼中會看到它們的作用。最后定義了一個cdvPluginPostBuildExtras數組變量,用來向里面追加Gradle插件擴展。
// 列表1-4。 ext { apply from: 'CordovaLib/cordova.gradle' // The value for android.compileSdkVersion. if (!project.hasProperty('cdvCompileSdkVersion')) { cdvCompileSdkVersion = null; } // The value for android.buildToolsVersion. if (!project.hasProperty('cdvBuildToolsVersion')) { cdvBuildToolsVersion = null; } // Sets the versionCode to the given value. if (!project.hasProperty('cdvVersionCode')) { cdvVersionCode = null } // Sets the minSdkVersion to the given value. if (!project.hasProperty('cdvMinSdkVersion')) { cdvMinSdkVersion = null } // Whether to build architecture-specific APKs. if (!project.hasProperty('cdvBuildMultipleApks')) { cdvBuildMultipleApks = null } // .properties files to use for release signing. if (!project.hasProperty('cdvReleaseSigningPropertiesFile')) { cdvReleaseSigningPropertiesFile = null } // .properties files to use for debug signing. if (!project.hasProperty('cdvDebugSigningPropertiesFile')) { cdvDebugSigningPropertiesFile = null } // Set by build.js script. if (!project.hasProperty('cdvBuildArch')) { cdvBuildArch = null } // Plugin gradle extensions can append to this to have code run at the end. cdvPluginPostBuildExtras = [] }
接下來這部分代碼,首先判斷build-extras.gradle文件是否存在,如果存在則把它應用到構建腳本中。下面的判斷語句檢查build-extras.gradle是否定義了列表1-4的屬性。如果有就使用build-extras.gradle中的,如果沒有,則按下面語句設置:
// 列表1-5。 // Set property defaults after extension .gradle files. if (ext.cdvCompileSdkVersion == null) { ext.cdvCompileSdkVersion = privateHelpers.getProjectTarget() } if (ext.cdvBuildToolsVersion == null) { ext.cdvBuildToolsVersion = privateHelpers.findLatestInstalledBuildTools() } if (ext.cdvDebugSigningPropertiesFile == null && file('debug-signing.properties').exists()) { ext.cdvDebugSigningPropertiesFile = 'debug-signing.properties' } if (ext.cdvReleaseSigningPropertiesFile == null && file('release-signing.properties').exists()) { ext.cdvReleaseSigningPropertiesFile = 'release-signing.properties' } // Cast to appropriate types. ext.cdvBuildMultipleApks = cdvBuildMultipleApks == null ? false : cdvBuildMultipleApks.toBoolean(); ext.cdvMinSdkVersion = cdvMinSdkVersion == null ? null : Integer.parseInt('' + cdvMinSdkVersion) ext.cdvVersionCode = cdvVersionCode == null ? null : Integer.parseInt('' + cdvVersionCode)
上述代碼表示如果沒有設置列表1-4中的屬性時設置它們,其中調用了cordova.gradle腳本中的方法。如前所述之后會說明這些變量的作用。
接下來這段代碼,注釋告訴我們,要讓cdvBuild的任務依賴于debug/arch-specific,即平臺相關,解決的是不同平臺構建的問題。首先分成debug和release版,然后根據前面遇到過的cdvBuildMultipleApks和cdvBuildArch的變量判斷是否是跨平臺構建,如果是則返回一個根據架構名生成的結果。注意到這個cdvBuildArch變量,在上面列表1-4的注釋中標明它由build.js設置。這個腳本位于項目目錄的cordova/lib/文件夾中,沒有納入到構建中,是cordova cli構建腳本,在這里先不做探討。
def computeBuildTargetName(debugBuild) { def ret = 'assemble' if (cdvBuildMultipleApks && cdvBuildArch) { def arch = cdvBuildArch == 'arm' ? 'armv7' : cdvBuildArch ret += '' + arch.toUpperCase().charAt(0) + arch.substring(1); } return ret + (debugBuild ? 'Debug' : 'Release') } // Make cdvBuild a task that depends on the debug/arch-sepecific task. task cdvBuildDebug cdvBuildDebug.dependsOn { return computeBuildTargetName(true) } task cdvBuildRelease cdvBuildRelease.dependsOn { return computeBuildTargetName(false) }
接下來定義了一個任務,顯然作用是輸出列表1-4以及后面定義過的屬性值。
task cdvPrintProps << { println('cdvCompileSdkVersion=' + cdvCompileSdkVersion) println('cdvBuildToolsVersion=' + cdvBuildToolsVersion) println('cdvVersionCode=' + cdvVersionCode) println('cdvMinSdkVersion=' + cdvMinSdkVersion) println('cdvBuildMultipleApks=' + cdvBuildMultipleApks) println('cdvReleaseSigningPropertiesFile=' + cdvReleaseSigningPropertiesFile) println('cdvDebugSigningPropertiesFile=' + cdvDebugSigningPropertiesFile) println('cdvBuildArch=' + cdvBuildArch) println('computedVersionCode=' + android.defaultConfig.versionCode) android.productFlavors.each { flavor -> println('computed' + flavor.name.capitalize() + 'VersionCode=' + flavor.versionCode) } }
接下來就是構建的核心部分,每個android構建都會有有android方法。
未完待續。