Android Studio插件開發

前言

在開發插件前,咱們先提一個需求,就是使用java語言開發安卓,實現頁面不直接經過findViewById來獲取控件並設置點擊事件(不可使用kotlin開發或ButterKnife之類的插件。)。

一.需求實現

需求實現原理

在activity,fragment或其餘的類中先把要用到的類聲明好,經過反射的方式獲取聲明好的屬性名稱。經過屬性名稱來獲取R文件中的控件,而後再爲控件添加上點擊的監聽器便可。是否是很簡單咧。

代碼分析

public void bindView(Object obj,Activity activity, View.OnClickListener clickListener){
        bindView(obj,null,clickListener,activity);
    }

    public void bindView(Object obj,View v, View.OnClickListener clickListener,Activity activity){
        Field[] field = obj.getClass().getDeclaredFields();
        for (Field f:field){
            try {
                f.setAccessible(true);
                Class clz = f.getType();
                //若是是基本數據類型,不處理,直接跳過
                if (clz.isPrimitive()){
                    continue;
                }
                //判斷實例是否是view的子類,不是直接跳過,不處理
                if (!View.class.isAssignableFrom(f.getType())){
                    continue;
                }
                //獲取屬性名稱
                String name = f.toString().substring(f.toString().lastIndexOf(".")+1);
                //經過屬性名稱獲取view的id
                int id = activity.getResources().getIdentifier(name,"id",activity.getPackageName());
                //實例化view
                View view;
                if (v == null){
                    view = activity.findViewById(id);
                }else {
                    view = v.findViewById(id);
                }
                //設置點擊事件
                if (clickListener != null && view != null){
                    view.setOnClickListener(clickListener);
                }
                //設置view
                f.set(obj,view);
            } catch (IllegalAccessException e) {
                Log.e("FindViewUtilsError","reason:"+e.getLocalizedMessage());
                e.printStackTrace();
            }
        }
    }
複製代碼

上面的代碼其實已經可使用一句話代替findViewById了。固然,調用上面的代碼位置必須是頁面裝載完成後,不然view都是空的。上面雖然實現了不用頻繁的findViewById和略過了設置點擊監聽器的代碼,可是咱們仍然要手動聲明控件的屬性名稱,同時屬性名稱必須和layout中的id一致,這不只容易出錯,還有點麻煩,畢竟屬性名稱一寫錯,view就爲空了,這是不能忍的。那麼怎麼實現連同代碼聲明這個步驟一併去掉呢,這時候咱們就能夠開發一個插件來實現了。

java

二.插件開發

插件開發工具準備

Android Studio自己這個工具就是針對安卓開發的,因此Android Studio自己是沒法支持插件開發的,開發插件咱們能夠用到IntelliJ IDEA去進行開發。IntelliJ IDEA社區版是免費的,因此能夠選擇社區版進行開發。

創建項目工程


建完工程後切記在src目錄下創建包名,不然有可能出現插件能夠在intellij idea下容許,但在android studio下容許報錯的狀況。創建完成後咱們須要給插件建立動做,步驟以下圖:

通過上圖的幾個步驟後,工程基本搭建完成了。下面就開始編寫Action中的代碼了。

代碼編寫

public class Test extends AnAction {
    private HashMap<String,String> map;

    @Override
    public void actionPerformed(AnActionEvent e) {
        //獲取編輯器
        Editor data = e.getData(PlatformDataKeys.EDITOR);
        //獲取鼠標選中區域
        SelectionModel selectionModel = data.getSelectionModel();
        if (selectionModel == null){
            return;
        }
        // TODO: insert action logic here
        //這個map用於保存要寫入的聲明對象的類和類名。
        map = new HashMap<>();
        //讀取xml文件。selectionModel.getSelectedText()這句話爲獲取鼠標點擊高亮區部分的文字內容。下面整句話是在工程中尋找名字爲xxx.xml的文件。xxx值就是鼠標選中的高亮區的文字。
        PsiFileSystemItem[] timeUtils = FilenameIndex.getFilesByName(e.getProject(), selectionModel.getSelectedText()+".xml", GlobalSearchScope.allScope(e.getProject()), false);
        //若是沒找到直接結束掉操做。
        if (timeUtils != null && timeUtils.length != 0){
            //理論上來講,xml文件大多數狀況下名字是惟一的,因此這裏直接取了第一個xml文件來解析
            VirtualFile virtualFile = timeUtils[0].getVirtualFile();
            //獲取對應的文件
            PsiFile manifestFile = PsiManager.getInstance(e.getProject()).findFile( virtualFile);
            // XmlDocument xml = (XmlDocument) manifestFile.getOriginalFile();
            //將文件解析爲xml文件
            XmlFile xmlFile = (XmlFile) PsiManager.getInstance(e.getProject()).findFile(virtualFile);
            XmlDocument document = xmlFile.getDocument();
            if (document != null) {
                //獲取頂層的xml內容
                XmlTag rootTag = document.getRootTag();
                if (rootTag != null) {
                    //獲取頂層後的下一層內容
                    XmlTag[] subTags = rootTag.getSubTags();
                    for (XmlTag tag : subTags) {
                        //獲取個各標籤的內容,裏面的方法使用了遞歸。
                        getTag(tag);
                    }
                }
            }
        }else {
            return;
        }
        //======================下面的代碼是將聲明和引用寫入到java文件中去==================================
        Document document = data.getDocument();
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                //這個stringBuffer爲最終須要寫入的內容
                StringBuffer stringBuffer = new StringBuffer();
                //這個map用於裝載要引入的包,之因此使用hashmap,由於hashmap鍵不可重複,避免重複引入包名。
                HashMap<String,String> importPackage = new HashMap<>();
                for (String key:map.keySet()){
                    if (!map.get(key).contains(".")){
                        //應爲沒有包含「.」的通常是一些基本控件,基本控件基本都是出自android.widget裏面,因此這裏就固定引入android.widget.+控件類名
                        importPackage.put(map.get(key),"import android.widget."+map.get(key)+";\n");
                        stringBuffer.append("\tprivate "+map.get(key)+" "+key+";\n");
                    }else {
                        //若是包含「.」的,多是自定義佈局或谷歌後面加入的佈局,這些佈局通常前面都帶有一串包名加上控件類名,這種咱們直接引入便可。
                        importPackage.put(map.get(key),"import "+map.get(key)+";\n");
                        stringBuffer.append("\tprivate "+map.get(key).substring(map.get(key).lastIndexOf(".")+1)+" "+key+";\n");
                    }
                    //System.out.println(key+","+map.get(key));
                }

                StringBuffer packageResult = new StringBuffer();
                for (String key:importPackage.keySet()){
                    packageResult.append(importPackage.get(key));
                }
                System.out.println(stringBuffer.toString());
                //在類中寫入屬性。寫入位置爲類文件中第一次出現「{」的地點的後一行
                document.insertString(document.getText().indexOf("{")+1,"\n"+stringBuffer.toString());
                //寫入導包的代碼。寫入位置爲類文件中第一次出現「;」的位置,即package xxx;的後一行
                document.insertString(document.getText().indexOf(";")+1,"\n"+packageResult.toString());

            }
        };
        WriteCommandAction.runWriteCommandAction(e.getProject(),runnable);
    }

    private void getTag(XmlTag tag) {
        //獲取標籤下的全部屬性
        XmlAttribute[] attributes = tag.getAttributes();
        for (XmlAttribute attribute:attributes){
            //咱們只須要獲取到id的內容和標籤的內容便可。這裏的id內容是指安卓xml裏面的id值,標籤內容是指id該id指代的對象的名稱,好比LinearLayout等。
            if ("android:id".equals(attribute.getName())){
                //將id和該id指代的對象以鍵值對的形式保存到hashmap中去。
                map.put(attribute.getValue().split("/")[1],tag.getName());
                //System.out.println(attribute.getValue().split("/")[1]+","+tag.getName());
            }
        }

        //判斷該標籤下是否存在子標籤,若是存在則進行遞歸調用
        XmlTag[] subTags = tag.getSubTags();
        for (XmlTag t : subTags) {
            getTag(t);
        }
    }
}
複製代碼

寫完上面的代碼後就能夠實現代碼注入的功能了。上面的註釋也寫得不少了,固然,有些接口是intellij idea裏面獨有的,可能以前沒見過,不過沒關係,記住就行。


android

調試插件

插件寫完後天然須要調試一下

不過要注意的是,調試插件時intellij idea會多開一個進程來安裝該插件,由於打開的進程是全新的,因此在打開後隨便打開一個項目便可。選中一個xml文件佈局,使用快捷鍵或在頂部導航條處觸發插件,便可驚奇的發現不到一秒,全部聲明一下就添加完了,要引入的包也都引入了。再配合文中最初的那段代碼,就可實現前面的需求了。 (混淆後的代碼沒法使用,由於混淆後聲明的變量名會變,致使view沒法正常找到。我能夠想到的就是在聲明的屬性前添加註解,在反射時讀取註解而不是變量名稱來實現)

三.總結

上面的插件沒有涉及到任何的圖形界面,這多少有點low,若是有須要瞭解圖形界面插件的搭建,能夠私聊我,或在後面開一篇內容來說解圖形界面插件的搭建及功能實現。
相關文章
相關標籤/搜索