使用命令列建立Android Studio專案
前言:前段時間,公司要開發一個移動開發平臺,有一個功能是在網頁端建立Android專案,填入專案名和包名,要能夠在後臺生成一個Android Studio目錄的工程,然後提供給使用者。接到這個需求的時候我是一臉懵逼的,開發者要這玩意有啥用啊,我用Android Studio建立工程不是更方便?然而領導說了要做,那就做唄。於是有了以下思路:
在後臺放一個標準的Android Studio工程,通過前臺傳遞過來的專案名和包名去修改專案中用到包名的檔案,替換成使用者輸入的包名就ok了。哇,多麼簡單,想好方案後彙報給領導。
領導說:“你這個確實可以這個功能,但是有沒有逼格更高一點的方法?
我:
好吧,領導發話了還能咋辦?原來的方法被擱下了,額,猜想出以下兩種高逼格的方式:
- 通過控制Android Studio開發工具去生成專案,當然不是手動操作,是通過某種方式,比如命令的方式去操作開發工具,進而生成專案。
- 通過一行命令的方式,命令中傳入包名、專案名,動態建立工程中所需要的各種檔案,進而生成完整的工程目錄。
好吧,最後的結果是:第一種pass,Android Studio好像沒有給我們提供這樣一個操作它的工具;至於第二種:好像可行,但是想想要自己建立那麼多檔案和資料夾就頭皮發麻,一度想要放棄-。-於是絕望的去百度搜尋:如何用命令列生成Android專案,一看,哇,有好多部落格呀,都是講如何用命令去生成Android工程:
然後點進去一看,執行了命令:
android create project -n HelloWorld -t android-25 -p HelloWorld -k top.overcode.helloworld -a HelloWorld
其中,-n指定要建立的專案的名稱,-t指定專案針對的Android的平臺,-p指定該專案的儲存路徑,-k指定該專案的包名,-a選項指定Activity的名稱。 然後生成了工程,目錄結構如下:
這尼瑪,是Eclipse結構的工程啊。。。。。。。。
好吧,搜尋良久無果,決定看看Android Studio到底如何生成工程的。於是在As安裝目錄下閒逛了良久,無意中發現以下目錄:
D:\Android\as\plugins\android\lib\templates
templates,模板,會不會有模板工程之類的東西呢?點進去的目錄如下:
gradle-projects?Android Studio不就是gradle專案麼?繼續深入
NewAndroidProject,新的Android專案,似乎有點像了,繼續
D:\Android\as\plugins\android\lib\templates\gradle-projects\NewAndroidProject\root
發現許多個.ftl結尾的檔案,
點開build.gradle.ftl檔案,發現長這樣
咦,這不就是我們的根目錄的build.gradle檔案麼?和專案中的一對比:
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.2.1'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
jcenter()
mavenCentral()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
發現classpath 'com.android.tools.build:gradle:3.2.1’中的版本號似乎是從外界傳遞進來的。於是去搜尋了.ftl檔案的用途,說是模板檔案,於是猜測這些檔案是專案檔案的模板檔案,Android Studio在建立專案的時候,把我們輸入的如包名、專案名等內容通過某種方式傳遞到模板檔案中來。於是在搜尋ftl的同時,有提到了FreeMarker,這又是啥呢?
嗯,大體意思就是用來改變模板中的資料的,是一個java類庫,可以操作.ftl檔案,向其中傳入引數,.ftl檔案可以獲取到傳遞過來的引數,這樣只需要模板檔案,然後通過FreeMarker操作模板檔案就可以生成Android專案中所需要的檔案了,到此研究似乎有了一些進展。那Android Studio是不是也是通過這種方式呢?於是在Android Studio的安裝目錄下瘋狂搜尋:
至此可以確定,Android Studio是通過這種方式來生成工程中的專案檔案的。即模板檔案+FreeMarker模板引擎。
於是看下標準的Android專案的目錄:
.gradle目錄和gradle目錄和目錄結構層次比較深,好像不好去建立這麼多的目錄和檔案,於是想到了gradle似乎有一個命令:
gradle init
前提是要配置gradle的環境變數哦,這裡就不說怎麼配置了,自行百度~執行完發現:
哇咔咔,根目錄下的資料夾幾乎都生成完了,只剩一個app目錄了,這個沒法通過命令去生成了。app目錄下:
這麼一看,我們只需要:
- 建立一個libs目錄和src目錄。
- 用模板生成文字檔案。
然後src中也一樣,有資料夾就建立資料夾,需要建立檔案就用模板檔案生成。依次遞進資料夾,直到該目錄下只剩檔案為止。看似很難,其實很簡單啦。舉個例子,比如src目錄下有如下路徑:
androidTest/java/com/suning/demo
為啥是這個路徑,因為我們的包名是com.suning.demo。前面androidTest/java是固定的。兩者拼接就是最終的檔案目錄,直到ExampleInstrumentedTest.java類
這個檔案需要替換的就這麼兩處。所以對於androidTest/java這個目錄下,我們只需要:
- 根據包名遞進建立目錄com/suning/demo。
- 模板檔案替換ExampleInstrumentedTest.java類中的包名。
然後AndroidManifest.xml、gradle檔案中中需要替換的包名同理。檔案和目錄的建立都搞定了,還有資原始檔,怎麼辦呢?這個有圖片啥的,不可能手動建立,去一個標準的工程下複製整個res目錄進來就行。好了,所需要的東西我們都有了,那麼建立資料夾、生成檔案、移動資源目錄到特定的資料夾,我們要如何去弄呢?當然是指令碼啦,在此之前我們要看下如何通過Freemarker去動態替換.ftl中的內容,需要Freemarker.jar,然後看如下程式碼:
/*
* 生成AndroidManifest檔案
* */
public static void createAndroidManifest() throws Exception {
Template template = getTemplate("AndroidManifest.xml.ftl");
FileWriter fileWriter = new FileWriter(new File(PROJECT_ROOT+"/app/src/main/AndroidManifest.xml"));
Map<Object, Object> map = new HashMap<>();
map.put("packageName", PACKGE_NAME);
template.process(map, fileWriter);
fileWriter.close();
}
上面程式碼是用來生成AndroidManifest.xml檔案的:
- 首先獲取模板檔案構造成Template物件。
- 一個輸入流指向要生成的AndroidManifest.xml檔案。
- 把需要傳遞的引數通過map集合存放。
- 以map和輸入流為引數,輸出最終生成的檔案。
- 關閉流。
再看下AndroidManifest.xml.ftl是如何獲取傳遞進來的引數的:
packageName就是我們map集合中的一個鍵,${packageName}即可取到這個鍵所對應的值。模板檔案的建立都同理。至於在命令列中如何獲取我們輸入的專案名和包名,並傳遞給java類去操作模板檔案,這又涉及到shell 命令的語法了。懂得忽略,不懂的私下百度,這裡因為篇幅的原因不去詳細介紹了。然後來總結下生成一個工程我們需要怎麼做:
- 根據命令列傳遞的專案名新建工程的根目錄。通過指令碼執行gradle init命令生成根目錄下gradle相關的東西。
- 編寫好操作各個模板檔案的java類(負責去操作所有需要通過模板生成的檔案,需要Freemarker.jar的支援)。
- 一份標準的資原始檔目錄備用。
- 各個模板檔案。
看下我的目錄:
其中marker目錄有以下模板檔案:
這是一個工程中需要生成的所有檔案,都需要通過模板去生成對應的檔案。
build指令碼的所有程式碼如下:
# @author Huxin 2019/2/2
echo "create project start..."
#check project name
#if empty
if [ -z "$1" ];then
echo "please input the project name,such as './bulid Test com.example '"
echo "create project fail !"
exit 0
fi
#if exist
if [ -d "$1" ]; then
echo "The project \"$1\" had exists, you must use another name"
echo "create project fail !"
exit 0
fi
#check project package name
if [ -z "$2" ];then
package="com.example.apps";
else
#package=$(echo $2 | tr '[A-Z]' '[a-z]' )
package=$2
fi
#get lowercase project name
var=$(echo $1 | tr '[A-Z]' '[a-z]' )
package_dir=$(echo $package | sed -e 's/\./\//g' )
#make project dir
mkdir $1
#into project root dir
cd $1
#init gradle
gradle init
if [ $? -eq 0 ]; then
#back tools root dir
cd ..
fi
#change code path
rm -rf $1/app/src/main/java/
mkdir -p $1/app/src/main/java/$package_dir
mkdir -p $1/app/src/test/java/$package_dir
mkdir -p $1/app/src/androidTest/java/$package_dir
cp -r res/ $1/app/src/main/res
sed -i "s/appName/$1/g" $1/app/src/main/res/values/strings.xml
#into project root dir
cd $1
echo "" >settings.gradle
echo "include ':app'" >settings.gradle
cd app
mkdir libs
#back project root dir
cd ..
cd ..
file=$4
# generate class
class=$(echo $file | awk -F '.' '{print $1}')
echo "start compile......."
# compile java class
javac -encoding UTF-8 -cp freemarker.jar Generate.java
if [ $? -eq 0 ]; then
echo "compile success,ready run..."
# run
java -cp ".;freemarker.jar" Generate "$1" "$2"
if [ $? -eq 0 ]; then
echo "run complete!"
else
echo "run error!"
fi
else
echo "compile error!"
fi
rm -f Generate.class
echo "All has been done !"
大體步驟如下:
- 通過傳遞的專案名建立專案根目錄。
- 執行gradle init命令,生成根目錄下gradle相關的目錄和資料夾。
- 建立對應的資料夾,並拷貝資原始檔。
- 通過java類去輸出所有需要生成的工程檔案,如build.gradle、AndroidManifest.xml、MainActivity.java等等。。。。
- 刪除編譯後的操作類的位元組碼檔案。
好吧,還有個Generate.java檔案,用來生成所有需要生成的檔案,程式碼如下:
import java.io.File;
import java.io.FileWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import freemarker.template.Configuration;
import freemarker.template.Template;
public class Generate {
//當前java檔案所在的路徑,即這個工具所在的根目錄。
public final static String ROOT_PATH = Generate.class.getResource("").getPath();
//模板檔案所在的路徑。
public final static String FTL_PATH = ROOT_PATH+"/marker";
//專案根目錄。
public static String PROJECT_ROOT = "";
//包名
public static String PACKGE_NAME = "";
public static void main(String[] args) throws Exception {
//傳遞進來三個引數,第一個是專案名,第二個是包名。
PROJECT_ROOT = args[0];
PACKGE_NAME = args[1];
createBuildGradle();
createAppBuildGradle(PROJECT_ROOT+"/app");
createAndroidManifest();
createIgnoreAndProguardFiles();
createTestFiles();
createMainActivity();
}
//生成module的build.gradle檔案
private static void createAppBuildGradle(String appPath) throws Exception {
Template template = getTemplate("build.gradle1.ftl");
File file = new File(appPath+"/build.gradle");
if(!file.exists()) {
file.createNewFile();
}
FileWriter fileWriter = new FileWriter(file);
Map<Object, Object> map = new HashMap<>();
map.put("packageName", PACKGE_NAME);
template.process(map, fileWriter);
fileWriter.close();
}
/*
* 生成專案的build.gradle
* */
public static void createBuildGradle() throws Exception{
// 在模板檔案目錄中找到名稱為name的檔案
Template template = getTemplate("build.gradle.ftl");
FileWriter fileWriter = new FileWriter(new File(PROJECT_ROOT+"/build.gradle"));
Map<Object, Object> map = new HashMap<>();
map.put("gradlePluginVersion", "3.2.1");
template.process(map, fileWriter);
fileWriter.close();
}
/*
* 生成AndroidManifest檔案
* */
public static void createAndroidManifest() throws Exception {
Template template = getTemplate("AndroidManifest.xml.ftl");
FileWriter fileWriter = new FileWriter(new File(PROJECT_ROOT+"/app/src/main/AndroidManifest.xml"));
Map<Object, Object> map = new HashMap<>();
map.put("packageName", PACKGE_NAME);
template.process(map, fileWriter);
fileWriter.close();
}
/*建立混淆檔案和忽略檔案*/
public static void createIgnoreAndProguardFiles() throws Exception{
//project下的忽略檔案
Template template = getTemplate(".gitignore.ftl");
File projectIgnoreFile = new File(PROJECT_ROOT+"/.gitignore");
if(!projectIgnoreFile.exists()) {
projectIgnoreFile.createNewFile();
}
FileWriter fileWriter = new FileWriter(projectIgnoreFile);
Map<Object, Object> map = new HashMap<>();
template.process(map, fileWriter);
//module下的忽略檔案
Template template1 = getTemplate(".gitignore1.ftl");
File moduleIgnoreFile = new File(PROJECT_ROOT+"/app/.gitignore");
if(!moduleIgnoreFile.exists()) {
moduleIgnoreFile.createNewFile();
}
FileWriter fileWriter1 = new FileWriter(moduleIgnoreFile);
Map<Object, Object> map1 = new HashMap<>();
template1.process(map1, fileWriter1);
fileWriter1.close();
//建立混淆檔案
Template template2 = getTemplate("proguard-rules.pro.ftl");
File proguardFile = new File(PROJECT_ROOT+"/app/proguard-rules.pro");
if(!proguardFile.exists()) {
proguardFile.createNewFile();
}
FileWriter fileWriter2 = new FileWriter(proguardFile);
Map<Object, Object> map2 = new HashMap<>();
template2.process(map2, fileWriter2);
//關流。
fileWriter.close();
fileWriter1.close();
fileWriter2.close();
}
/*
* 建立Test檔案
* */
public static void createTestFiles() throws Exception{
//建立ExampleUnitTest類
Template template = getTemplate("ExampleUnitTest.java.ftl");
String [] packages = PACKGE_NAME.split("\\.");
String testFilePath = PROJECT_ROOT+"/app/src/test/java";
for(int i=0;i<packages.length;i++) {
testFilePath = testFilePath+"/"+packages[i];
}
File testFile = new File(testFilePath+"/ExampleUnitTest.java");
if(!testFile.exists()) {
testFile.createNewFile();
}
FileWriter fileWriter = new FileWriter(testFile);
Map<Object, Object> map = new HashMap<>();
map.put("packageName", PACKGE_NAME);
template.process(map, fileWriter);
//建立ExampleInstrumentedTest類
Template template1 = getTemplate("ExampleInstrumentedTest.java.ftl");
String androidTestFilePath = PROJECT_ROOT+"/app/src/androidTest/java";
for(int i=0;i<packages.length;i++) {
androidTestFilePath = androidTestFilePath+"/"+packages[i];
}
File androidTestFile = new File(androidTestFilePath+"/ExampleInstrumentedTest.java");
if(!androidTestFile.exists()) {
androidTestFile.createNewFile();
}
FileWriter fileWriter1 = new FileWriter(androidTestFile);
Map<Object, Object> map1 = new HashMap<>();
map1.put("packageName", PACKGE_NAME);
template1.process(map1, fileWriter1);
fileWriter.close();
fileWriter1.close();
}
//建立MainActivity類。
public static void createMainActivity() throws Exception{
Template template = getTemplate("MainActivity.java.ftl");
String [] packages = PACKGE_NAME.split("\\.");
String mainActivityPath = PROJECT_ROOT+"/app/src/main/java";
for(int i=0;i<packages.length;i++) {
mainActivityPath = mainActivityPath+"/"+packages[i];
}
File mainActivityFile = new File(mainActivityPath+"/MainActivity.java");
if(!mainActivityFile.exists()) {
mainActivityFile.createNewFile();
}
FileWriter fileWriter = new FileWriter(mainActivityFile);
Map<Object, Object> map = new HashMap<>();
map.put("packageName", PACKGE_NAME);
template.process(map, fileWriter);
fileWriter.close();
}
public static Template getTemplate(String templateName) throws Exception{
Configuration configuration = new Configuration();
configuration.setDirectoryForTemplateLoading(new File(FTL_PATH));
Template template = configuration.getTemplate(templateName);
return template;
}
}
這個類是用來通過模板檔案來生成工程中所有的檔案的,是生成檔案的核心類,當然需要Freemarker jar包的支援。到此我們通過命令生成Android Studio目錄結構的Android工程就已經完成了。最終整個工具的目錄如下:
說到底就是一個指令碼,一個java類就可以了,主要是構建這樣一個工具的思路。這個指令碼可以在windows下執行,也可以在linux下執行。先看windows下,首先你需要一個shell 環境,但是windows下預設是沒有shell環境的,你可以下載Git for Windows,下載地址:Git for Windows,下載完成,一路預設安裝,安裝完成就可以使用了,在桌面或任何檔案目錄中,點選右鍵選單中會有Git Bash Here選項:
但是要使用這個工具,我們需要在tools資料夾下右擊,選擇Git Bash Here,如下:
然後執行如下命令:
./build Demo com.suning.demo
可以看到如下效果:
表示工程生成成功了。其中Demo是專案名,com.suning.demo是專案的包名。有兩點需要注意:
1. tools資料夾所在的目錄不要包含中文,可能會出現亂碼。
2. 執行命令的時候一定要切換到tools目錄下,無論是windows還是linux。
看下生成的工程:
這個專案是可以直接用Android Studio來執行的。當然你可以修改這個工具,改變某個檔案想要改變的地方都可以,感興趣的自己可以改一改,沒啥難度。哦,對了,要在linux下執行,build檔案要修改一處地方:
把其中的分號改成冒號就行:
java -cp ".:freemarker.jar" Generate "$1" "$2"
不然在linux下執行會報錯。所有的程式碼都上傳到Gihub上了,程式碼傳送門。
總結:其實這個工具也挺雞肋的,建立工程還是Android Studio來的方便,正常都不會用到,公司所開發的移動開發平臺,要求網頁端可以建立Android工程,並可以動態選擇專案的依賴庫(如Okhtp、Glide、Retrofit、RxJava等),然後生成工程的時候,遠端呼叫命令,把所選依賴庫的配置程式碼寫入到gradle檔案中,省去使用者自行配置的時間,要說倒也算是優點功能,不過有沒有人用還是個未知數,畢竟作為開發者,我還是更相信開發工具生成的專案,完-。-
相關文章
- 使用命令列建立Maven的專案或模組目錄命令列Maven
- Mac 和 Android Studio 命令列走 Shadowsocks 代理MacAndroid命令列
- Android Studio打包專案:APKAndroidAPK
- 使用Git命令列clone一個專案Git命令列
- android--Android Studio使用terminal終端(命令視窗)Android
- mysql命令列建立使用者MySql命令列
- Android studio 無法建立layout_land檔案解析Android
- Android Studio修改專案名和包名Android
- Mac 下在命令列下使用 PHPStorm 開啟專案Mac命令列PHPORM
- Android Studio在android Emulator中執行的專案黑屏Android
- 使用 composer 建立專案
- Git基本命令 -- 建立Git專案Git
- 【Android Studio】解決升級 macOS High Sierra 後 Android Studio 同步專案時 gradle 構AndroidMacGradle
- 做ftp專案中使用命令列引數及 ----python 命令列 解析模組 optparseFTP命令列Python
- Android Studio 專案匯入的正確姿勢Android
- maven使用mvn archetype:generate命令建立專案骨架遇到的問題Maven
- 使用 Docker 建立 Hyperf 專案Docker
- 使用 Docker 建立 Lumen 專案Docker
- 使用IDEA建立gradle專案IdeaGradle
- 使用IDEA建立springboot專案IdeaSpring Boot
- 使用 pnpm 建立 vue 專案NPMVue
- 使用SAP iRPA Studio建立的本地專案,如何部署到SAP雲平臺上?
- visual studio建立專案時需要注意的問題
- 第一次開啟Android Studio建立專案執行報錯Failed to open zip fileAndroidAI
- 如何建立依賴專案工程--android moduleAndroid
- Flutter開發第一個專案android studio 開發工具的使用說明FlutterAndroid
- Android Studio向專案新增C/C++原生程式碼AndroidC++
- Android Studio 已有專案新增NDK支援(mac/ndk-build)AndroidMacUI
- 命令啟動android studio 模擬器Android
- FFmpeg command line tool(Android中使用FFmpeg命令列)Android命令列
- Android studio使用小技巧Android
- 使用 Angular 8 建立前端專案Angular前端
- Android開發_在Android Studio中搜尋專案中出現過的字串Android字串
- Android studio(建立、監聽器intent選單)AndroidIntent
- Windows從命令列建立文字檔案的兩種方式Windows命令列
- Android Studio Run專案出現Failure [INSTALL_FAILED_TEST_ONLY]AndroidAI
- Android Studio目錄結構及工程專案結構解析Android
- android studio匯入專案--解決gradle-headache問題AndroidGradle