Android Studio Plugin 插件開發教程(一) —— 開發你的第一個插件android
Android Studio Plugin 插件開發教程(二) —— 插件SDK中的經常使用對象介紹git
Android Studio Plugin 插件開發教程(三) —— 製做一個自動生成數據庫代碼的插件github
Android Studio Plugin 插件開發教程(四) —— 爲自動生成數據庫代碼的插件添加UIsql
本篇實戰擼個自動生成安卓Sqlite數據庫代碼的插件,先演示下最終效果
db文件夾下的都是插件自動生成的,而MainActivity裏面的代碼是我提早寫好的,用於實驗插件生成的代碼效果數據庫
簡單解釋下插件功能
給定一個數據類,好比User。但願插件能根據數據類自動生成對應的表結構,存在一個Column類裏。而後再生成對應的Dao類其中包含CRUD方法。app
和網上常見的一些數據庫框架相似,只不過這裏是用插件直接生成Android Sqlite原生代碼框架
優勢:ide
缺點:工具
開擼~
須要處理這麼幾個模塊
SqliteOpenHelper類,其中包含create table的sql語句;
Columns字段類,統一存在一個DataContract類中;
數據Dao類,包含CRUD的sql語句
幾個模塊的處理步驟和邏輯都相似,這裏拿Columns類生成舉例。
其餘能夠下載源碼參考 ,源碼地址:
github.com/boredream/A…
歡迎star和follow~
下載源碼後參考教程一先搭建環境,而後導入項目
處理步驟以下:
這裏但願把生成的類都存在包名目錄下的db包中(com.packagename.db)
首先要獲取到包名目錄路徑...app/src/main/java/包名,而後才能在它下面獲取或新建db文件夾。而獲取包名目錄又要先獲取Android項目的包名,想獲取這個又得先找到AndroidManifest文件~
由於AndroidManifest文件路徑是固定的,因此能夠用上一篇教程中的LocalFileSystem.getInstance().findFileByPath(path);方法獲取文件
public static PsiFile getManifestFile(Project project) {
String path = project.getBasePath() + File.separator +
"app" + File.separator +
"src" + File.separator +
"main" + File.separator +
"AndroidManifest.xml";
VirtualFile virtualFile = LocalFileSystem.getInstance().findFileByPath(path);
if(virtualFile == null) return null;
return PsiManager.getInstance(project).findFile(virtualFile);
}複製代碼
project能夠在Action中經過event.getData()獲取,參考上一篇教程
獲取到VirtualFile後再轉換成PsiFile,大部分操做都是針對Psi體系的
而後解析AndroidManifest文件,獲取package屬性裏的包名
由於是Xml文件,因此和Dom啥的解析差很少,獲取代碼以下
public static String getAppPackageName(Project project) {
PsiFile manifestFile = getManifestFile(project);
XmlDocument xml = (XmlDocument) manifestFile.getFirstChild();
return xml.getRootTag().getAttribute("package").getValue();
}複製代碼
而後就能夠根據包名獲取到包名目錄了
public static VirtualFile getAppPackageBaseDir(Project project) {
String path = project.getBasePath() + File.separator +
"app" + File.separator +
"src" + File.separator +
"main" + File.separator +
"java" + File.separator +
getAppPackageName(project).replace(".", File.separator);
return LocalFileSystem.getInstance().findFileByPath(path);
}複製代碼
project.getBasePath()是項目的根目錄,在其基礎上拼接後續路徑
而後,包名目錄下斷有沒有db文件夾,沒有就建立一個
// app包名根目錄 ...\app\src\main\java\PACKAGE_NAME\
VirtualFile baseDir = AndroidUtils.getAppPackageBaseDir(project);
// 判斷根目錄下是否有db文件夾
VirtualFile dbDir = baseDir.findChild("db");
if(dbDir == null) {
// 沒有就建立一個
try {
dbDir = baseDir.createChildDirectory(null, "db");
} catch (IOException e) {
e.printStackTrace();
}
}複製代碼
此次咱們用了 VirtualFile.FindChild(filename) 方法,獲取文件子一級路徑中尋找文件或文件夾
(LocalFileSystem.getInstance().findFileByPath(path); 只能獲取文件不能獲取目錄,因此不用)
沒有db文件夾的話,就 VirtualFile.createChildDirectory(requestor, name) 建立一個
這個方法第一個參數是指定誰調用了它,通常傳null不作特殊處理
db目錄定位到了,而後就是在裏面建立DataContract類了,再在其中存放Columns類。
DataContact.java文件其實也能夠經過相似上面的 VirtualFile.createChildData 直接建立文件,
但建立的是空的文件,而咱們須要的是有代碼內容的java文件,因此下面咱們介紹另外一個方法~
按照咱們的插件需求,要建立一個DataContract類,而後把Columns類都存進去。
首先就是要生成這個做爲殼子的類~ 咱們先拼接出來類文件的字符串,代碼以下
public static String genDataContractInitCode(VirtualFile dir) {
return StringUtils.formatSingleLine(0, "package " + AndroidUtils.getFilePackagePath(dir) + ";") +
"\n" +
StringUtils.formatSingleLine(0, "import android.provider.BaseColumns;") +
"\n" +
StringUtils.formatSingleLine(0, "public final class DataContract {") +
"\n" +
StringUtils.formatSingleLine(1, "private DataContract() {") +
StringUtils.formatSingleLine(2, "// private") +
StringUtils.formatSingleLine(1, "}") +
"\n" +
"}";
}複製代碼
其中getFilePackagePath是獲取當前文件/文件夾對應包名 com.xxx.xxx 的,
邏輯是把當前文件路徑的 / 替換成 . 而後截取com.xxx.xxx之後的部分便可
public static String getFilePackageName(VirtualFile dir) {
if(!dir.isDirectory()) {
// 非目錄的取所在文件夾路徑
dir = dir.getParent();
}
String path = dir.getPath().replace("/", ".");
String preText = "src.main.java";
int preIndex = path.indexOf(preText) + preText.length() + 1;
path = path.substring(preIndex);
return path;
}複製代碼
獲取到代碼字符串之後,能夠用createFileFromText建立有內容的文件,以下
String name = "DataContract.java";
VirtualFile virtualFile = dbDir.findChild(name);
if(virtualFile == null) {
// 沒有就建立一個,第一次使用代碼字符串建立個類
PsiFile initFile = PsiFileFactory.getInstance(project).createFileFromText(
name, JavaFileType.INSTANCE, CodeFactory.genDataContractInitCode(dbDir));
// 加到db目錄下
PsiManager.getInstance(project).findDirectory(dbDir).add(initFile);
virtualFile = dbDir.findChild(name);
}複製代碼
dbDir是步驟一中獲取到的db文件夾
genDataContractInitCode是上面拼接代碼的方法,返回代碼字符串
注意,createFileFromText建立的文件是一個無目錄的文件,須要手動add到須要位置
這個add操做就會把文件加到指定目錄下,新建一個文件~
上一篇介紹過,咱們能夠用action中的event獲取當前正在編輯的文件,而後在file中獲取到PsiClass元素,最後遍歷Class獲取所有成員變量Field。PsiClass和Java中的Class類似,有一點反射姿式的能夠很快上手
下面就是根據數據類信息,拼接代碼字符串的方法
public static String genBeanColumnsCode(PsiClass clazz) {
StringBuilder sb = new StringBuilder();
sb.append(StringUtils.formatSingleLine(0, "public interface " + clazz.getName() + " extends BaseColumns {"));
sb.append(StringUtils.formatSingleLine(1, "String TABLE_NAME = \"" + StringUtils.camel2underline(clazz.getName()) + "\";"));
for (PsiField field : clazz.getFields()) {
String name = StringUtils.camel2underline(field.getName()).toUpperCase();
String value = name.toLowerCase();
sb.append(StringUtils.formatSingleLine(1, "String " + name + " = \"" + value + "\";"));
}
sb.append("}");
return sb.toString().trim();
}複製代碼
clazz.getField獲取到類的全部成員變量,而後拼接成須要的代碼
其中的StringUtils是本身封裝的工具類
camel2underline是將駝峯命名轉換成下劃線風格的字符串
formatStringLine是在前面加縮進符,後面加換行符
拼好代碼後,就能夠用它去生成類、文件、方法等等
和以前生成文件相似,也是 createXXXFromText一類的方法,
能夠用代碼生成類、方法、語句、變量等等。
這裏咱們就要根據代碼去生成Columns類,也就是PsiClass對象
上一步咱們已經獲取到了DataContract類了,新建的Columns要保存在它裏面
PsiFile psiFile = PsiManager.getInstance(project).findFile(virtualFile);
// 用拼接的代碼生成Columns Class
String beanColumnsCode = CodeFactory.genBeanColumnsCode(clazz);
PsiElementFactory factory = JavaPsiFacade.getInstance(project).getElementFactory();
PsiClass beanColumnsClass = factory.createClassFromText(beanColumnsCode, psiFile);
// 將建立的class添加到DataContract Class中
PsiClass fileClass = PluginUtils.getFileClass(psiFile);
fileClass.add(beanColumnsClass.getInnerClasses()[0]);複製代碼
此次用到了 PsiElementFactory 類,而後用它去 createClassFromText 建立類
方法的第二個參數是Context,傳入所在File或所在Class均可以
而後將這個生成的類添加到DataContract文件類中
注意不能是添加到DataContract文件上,而是添加到文件裏的類上
獲取方法以下(應該有更好的方法吧,暫時沒找到)
public static PsiClass getFileClass(PsiFile file) {
for (PsiElement psiElement : file.getChildren()) {
if (psiElement instanceof PsiClass) {
return (PsiClass) psiElement;
}
}
return null;
}複製代碼
createFileFromText的時候咱們拼接的字符串是完整的代碼,
可是在createClassFromText的時候比較特殊,codeText是做爲類主體部分的
String classCode = "public class MyClass {\n" +
"\tprivate String a;\n" +
"}";
PsiClass newClass = factory.createClassFromText(classCode, null);複製代碼
若是你這樣去生成,那麼最終代碼會是
class _Dummy_ {
public class MyClass {
private String a;
}
}複製代碼
這不是咱們想要的!!!
因此通常作法是隻用類 { } 裏面的代碼去生成,好比"private String a;"
而類的public等信息須要額外設置,以下
newClass.setName("User"); // 設置類名,默認名爲_Dummy_
newClass.getModifierList().add(factory.createKeyword(PsiKeyword.PUBLIC)); // 定義列表裏添加關鍵字public
newClass.getImplementsList().add(factory.createReferenceElementByFQClassName(
"android.provider.BaseColumns", clazz.getResolveScope())); // 實現接口列表裏添加BaseColumns類複製代碼
這就比較麻煩了,因此介紹個良心小技巧!
仍是用所有代碼生成,而後再獲取這個類的innerClasses內部類裏面的第一個就好了!
因此纔有了上面的 class.getInnerClasses()[0] 的處理
將以前的代碼封裝到DatabaseGenerator類中的genCode方法中,而後在action裏調用
action的相關介紹參考教程一
public class DatabaseGenerateAction extends AnAction {
@Override
public void actionPerformed(AnActionEvent e) {
Project project = e.getData(PlatformDataKeys.PROJECT);
PsiFile file = e.getData(PlatformDataKeys.PSI_FILE);
PsiClass clazz = PluginUtils.getFileClass(file);
WriteCommandAction.runWriteCommandAction(project, () -> {
DatabaseGenerator.genCode(file, clazz);
});
}
}複製代碼
注意,這裏有個特殊的處理 WriteCommandAction.runWriteCommandAction
在插件中,若是是新建File等操做是能夠直接進行的。
但在DataContract文件類中添加個內部類,這種寫入文件內容的操做是須要特殊處理的,須要放在 WriteCommandAction.runWriteCommandAction 第二個參數的runnable中運行
搞定,效果圖見文章開始動態圖。
源碼部分見
github.com/boredream/A…歡迎star和follow~下載源碼後參考教程一先搭建環境,而後導入項目