Android Studio自定義模板實現一鍵建立MVP結構
前言
之前有寫過關於如何使用 DataBinding 的兩篇文章,不僅僅是為了消滅掉一部分重複程式碼,更是為了提高開發效率。詳情可以點選下方的傳送門
ofollow,noindex">DataBinding入門進階指南(一) DataBinding入門進階指南(二)而這篇文章主要介紹的就是如何通過 Android+Studio/">Android Studio 提供的模版功能去自定義模版結構,從而實現類似於一鍵建立整個MVP程式碼的功能。可以說在提高效率的道路上,又向前走了一大步

下面可以來看一看具體效果:

介紹
在 Android Studio 中,建立一個 Activity 可以直接通過 File -> New -> Activity 來進行選擇建立

通過這種方式建立的 Activity 會自動在 AndroidManifest.xml 中完成註冊,建立其他元件也可以通過這種方式。
不過,如果你正在使用某種開發模式,譬如 MVP、MVVM 等,你每建立一個 Activity 就意味著需要同時建立一系列其他相關的類。
為了避免這種毫無意義的重複性勞動,我們可以編寫模板程式碼去實現一鍵建立重複程式碼。
開始
下面我們就來開始模版的編寫吧。
首先,找到你的 Android Studio 的安裝目錄,然後根據這個目錄找到 ...\templates 目錄:

然後進入 activityes 目錄,我們將要編寫的各種模版就在這個目錄內:

要說如何去編寫模版程式碼,一開始我也是一無所知的,不過好在 Android Studio 已經為我們提供了這些例子,我們直接參考例子去寫。
就拿最簡單的 Empty Activity 來開始吧
進入到 EmptyActivity 目錄

globals.xml.ftl
開啟 globals.xml.ftl 檔案,下面是它的內容:
<?xml version="1.0"?> <globals> <global id="hasNoActionBar" type="boolean" value="false" /> <global id="parentActivityClass" value="" /> <global id="simpleLayoutName" value="${layoutName}" /> <global id="excludeMenu" type="boolean" value="true" /> <global id="generateActivityTitle" type="boolean" value="false" /> <#include "../common/common_globals.xml.ftl" /> </globals>
根據檔名來看, globals.xml.ftl 的作用是用來控制一些全域性變數,比如是否顯示 ActionBar 等,暫且先不用管它
recipe.xml.ftl
recipe.xml.ftl檔案內容如下:
<?xml version="1.0"?> <#import "root://activities/common/kotlin_macros.ftl" as kt> <recipe> <#include "../common/recipe_manifest.xml.ftl" /> <@kt.addAllKotlinDependencies /> <#if generateLayout> <#include "../common/recipe_simple.xml.ftl" /> <open file="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" /> </#if> <instantiate from="root/src/app_package/SimpleActivity.${ktOrJavaExt}.ftl" to="${escapeXmlAttribute(srcOut)}/${activityClass}.${ktOrJavaExt}" /> <open file="${escapeXmlAttribute(srcOut)}/${activityClass}.${ktOrJavaExt}" /> </recipe>
第一段
<#import "root://activities/common/kotlin_macros.ftl" as kt>
就是用於匯入Kotlin的相關命令,同時它的別名為 kt
主要還是注意 instantiate 程式碼塊中的相關資訊, 其中 ${ktOrJavaExt} 表示當你建立模版的時候,建立的 .java 檔案還是 .kt 檔案,而相對應的,你需要在編寫模版例子的時候分別寫上對應的兩份 Java 與 Kotlin 程式碼
open程式碼塊就是建立模版後,預設開啟的檔案
template.xml
template.xml程式碼略長,這裡只是貼出了大致程式碼
<?xml version="1.0"?> <template format="5" revision="5" name="Empty Activity" minApi="9" minBuildApi="14" description="Creates a new empty activity"> <category value="Activity" /> <formfactor value="Mobile" /> <parameter id="activityClass" name="Activity Name" type="string" constraints="class|unique|nonempty" suggest="${layoutToActivity(layoutName)}" default="MainActivity" help="The name of the activity class to create" /> ... <!-- 128x128 thumbnails relative to template.xml --> <thumbs> <!-- default thumbnail is required --> <thumb>template_blank_activity.png</thumb> </thumbs> <globals file="globals.xml.ftl" /> <execute file="recipe.xml.ftl" /> </template>
我們挑出其中的重點來說
<category value="Activity" />
表示當前的這個模版的分類,當前的 Value 是 Activity ,就表示它會出現在 File -> New -> Activity 中,這個是可以自定義的.
<thumbs> <!-- default thumbnail is required --> <thumb>template_blank_activity.png</thumb> </thumbs>
thumbs用於指定建立模版時所展示出來的圖片
而最重要的,還是 parameter 程式碼塊的內容了,在這之中,我們只需要關注以下幾個,其他的顧名思義即可。
<parameter id="activityClass" name="Activity Name" type="string" constraints="class|unique|nonempty" suggest="${layoutToActivity(layoutName)}" default="MainActivity" help="The name of the activity class to create" />
activityClass表示所要建立的 Activity ,其中 default 為預設名。
<parameter id="generateLayout" name="Generate Layout File" type="boolean" default="true" help="If true, a layout file will be generated" />
上面的程式碼塊表示是否同時自動建立一個Activity對應的佈局
<parameter id="layoutName" name="Layout Name" type="string" constraints="layout|unique|nonempty" suggest="${activityToLayout(activityClass)}" default="activity_main" visibility="generateLayout" help="The name of the layout to create for the activity" />
layoutName則表示佈局的名字,這裡的 suggest 屬性所填寫的內容即為佈局名, ${activityToLayout(activityClass)} 則為跟隨Activity的名字,其中 activityClass 是Activity名字的引用
剩下的不用再作說明,基本上可以見名知意。
模版程式碼
接下來我們從 EmptyActivity 中的 root 目錄一直進入,直到看到下面兩個檔案

可以看到,一個字尾是 java.ftl 另外一個字尾是 kt.ftl ,他們分別用於建立 Java模版與Kotlin模版,如果你暫時不使用Kotlin的話,可以不用去關心 Kotlin模版,當你完成了Java模版的編寫,也可以使用 Android Studio自帶的轉換功能,還是蠻方便的。
下面來看一下Java的模版程式碼:
package ${packageName}; import ${superClassFqcn}; import android.os.Bundle; <#if (includeCppSupport!false) && generateLayout> import android.widget.TextView; </#if> public class ${activityClass} extends ${superClass} { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); <#if generateLayout> setContentView(R.layout.${layoutName}); <#include "../../../../common/jni_code_usage.java.ftl"> <#elseif includeCppSupport!false> // Example of a call to a native method android.util.Log.d("${activityClass}", stringFromJNI()); </#if> } <#include "../../../../common/jni_code_snippet.java.ftl"> }
- ${packageName}:表示當前包名
- ${activityClass}:表示當前的Activity名字
-
{superClassFqcn}
- ${layoutName}:當前Activity所對應的佈局名
目前我們只需要關注上面這部分,接下來可以看一下我們實際想要建立的MVP結構:

編寫模版程式碼前,最好的方式是先寫一遍例子,然後對照例子去替換關鍵名部分,這樣做是最輕鬆的。
下面就來看一看具體的實現吧:
樣例程式碼
介面部分:TestActivityContact
package com.example.testcustomtemplates.contact; public interface TestActivityContact { interface Presenter<T> { void succeed(T t); void failed(T t); void error(Throwable e); void subscribe(); void unSubscribe(); } interface View<T> { void setPresenter(Presenter presenter); void succeed(T t); void failed(T t); void error(Throwable e); } interface Model { void setPresenter(Presenter presenter); } }
為了方便測試,這裡並沒有另外建立一些基類介面,可以看到上面程式碼中分別對應 MVP 結構中三個模組的介面,寫的是最基本的需求方法,不過 MVP 也不都是完全一樣的,這裡你可以定義自己想寫的方法。
Model層:TestActivityModel
package com.example.testcustomtemplates.model; import android.content.Context; import com.example.testcustomtemplates.contact.TestActivityContact; public class TestActivityModel implements TestActivityContact.Model { private Context context; private TestActivityContact.Presenter mPresenter; public TestActivityModel(Context context) { this.context = context; } @Override public void setPresenter(TestActivityContact.Presenter presenter) { this.mPresenter = presenter; } }
Model層主要就是做一些網路請求,儲存之類的資料相關操作,不可以持有對View的引用,他是通過Presenter去和View進行互動的。
Presenter層:TestActivityPresenter
package com.example.testcustomtemplates.presenter; import android.content.Context; import com.example.testcustomtemplates.contact.TestActivityContact; import com.example.testcustomtemplates.model.TestActivityModel; public class TestActivityPresenter<T> implements TestActivityContact.Presenter<T> { private TestActivityContact.View mView; private TestActivityModel mModel; private Context context; public TestActivityPresenter(TestActivityContact.View mView, Context context) { this.mView = mView; this.context = context; mModel = new TestActivityModel(context); } @Override public void succeed(T t) { } @Override public void failed(T t) { } @Override public void error(Throwable e) { } @Override public void subscribe() { } @Override public void unSubscribe() { } }
Presenter層自然不必多說,他最好是不要持有View控制元件的引用,大部分的邏輯操作需要他來完成,不過不可避免的,如果業務邏輯複雜了,Presenter層也會變得臃腫,這也是MVP結構的一個短處。
View層:TestActivity
package com.example.testcustomtemplates.activity; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import com.example.testcustomtemplates.R; import com.example.testcustomtemplates.contact.TestActivityContact; import com.example.testcustomtemplates.presenter.TestActivityPresenter; public class TestActivity<T> extends AppCompatActivity implements TestActivityContact.View<T> { private TestActivityContact.Presenter mPresenter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_test); new TestActivityPresenter<T>(this, this); } @Override public void setPresenter(TestActivityContact.Presenter presenter) { this.mPresenter = presenter; } @Override public void succeed(T t) { } @Override public void failed(T t) { } @Override public void error(Throwable e) { } }
Activity或者Fragment都可以用作View層,這層主要是對一些檢視控制元件的狀態進行切換,不做複雜的邏輯操作。
看完上面的這些程式碼後,其實就可以開始直接編寫我們的模版程式碼了。
模版編寫
首先,可以Copy一份 EmptyActivity 整個模版的檔案,然後改一下名字,隨便什麼都可以,這裡我將其改成 MvpDemoActivity

然後我們首先對 template.xml 檔案進行修改,主要修改下面這個部分:
<category value="Activity" />
然後是對 recipe.xml.ftl 檔案進行修改,修改後如下:
<?xml version="1.0"?> <#import "root://activities/common/kotlin_macros.ftl" as kt> <recipe> <#include "../common/recipe_manifest.xml.ftl" /> <@kt.addAllKotlinDependencies /> <#if generateLayout> <#include "../common/recipe_simple.xml.ftl" /> <open file="${escapeXmlAttribute(resOut)}/layout/${layoutName}.xml" /> </#if> <!--View-activity--> <instantiate from="root/src/app_package/MvpActivity.java.ftl" to="${escapeXmlAttribute(srcOut)}/activity/${activityClass}.java" /> <!--Model--> <instantiate from="root/src/app_package/MvpModel.java.ftl" to="${escapeXmlAttribute(srcOut)}/model/${activityClass}Model.java" /> <!--Contact--> <instantiate from="root/src/app_package/MvpContact.java.ftl" to="${escapeXmlAttribute(srcOut)}/contact/${activityClass}Contact.java" /> <!--Presenter--> <instantiate from="root/src/app_package/MvpPresenter.java.ftl" to="${escapeXmlAttribute(srcOut)}/presenter/${activityClass}Presenter.java" /> <open file="${escapeXmlAttribute(srcOut)}/activity/${activityClass}.java" /> </recipe>
上面的程式碼表示只編寫了Java版,當然你在修改這個檔案之前還是需要建立相對應的幾個類的模版程式碼的。這裡出於篇幅考慮暫時就不貼出實際的模版程式碼了,下面會給出github地址,編寫了Java版和Kotlin版的,大家可以拿去參考
Github專案連結
當然,有好的模版也可以一起分享一下
