項目的升級-給RemoveButterKnife插件增長新功能

前言

通過項目的初步編寫和進一步改造,RemoveButterKnife插件終於也有模有樣了,可是,功能上僅僅支持Activity/Fragment的BindView註解。android

關於編寫和優化的過程能夠看下面兩篇文章 項目構造RemoveButterKnifegit

項目改進-重構RemoveButterKnifegithub

固然,這裏也附上這個項目的github地址正則表達式

爲了讓插件支持更加完全,咱們還要支持組合自定義view以及viewholder中使用butterknife的狀況,固然,咱們也要支持OnClick註解以及一些其餘的使用場景。bash

要增長哪些功能?

首先要肯定須要增長哪些功能點,功能點的更新以下app

  1. 增長對多moudle時的R2.id.xxx的支持
  2. 增長對OnClick註解的支持
  3. 增長對viewholder和自定義組合view的支持

肯定增長功能的前後順序

審閱咱們的功能,發現1號功能是比較容易的,因此咱們把1號定爲優先 再次思考發現,2,3號功能之間存在關聯性,即,3號功能說起的支持種類中也要支持Onclick註解的形式。 因此肯定開發順序爲1->3->2ide

功能拆分

在這裏咱們使用github提供的project功能,具體分解以下 能夠看到,咱們分爲了todo,doing,done三個部分,並且把每一個任務都細分爲了幾個步驟,這樣咱們就能夠在開發某一功能時保持專一,而不須要東寫一點西寫一點了函數

具體功能開發

1.對R2.id.xxxx的支持

因爲咱們原來的代碼中尋找匹配使用的是正則表達式,以下優化

String pattern = "^@(BindView|InjectView|Bind)\\((R.id.*)|(R2.id.*)\\)$";
   Pattern r = Pattern.compile(pattern);
複製代碼

能夠看到,咱們的代碼已經添加了對R2.id.xxx的支持,只須要給正則表達式增長一個條件。 最後,咱們給這個功能添加上unit test,就能夠完成對功能1的開發ui

2.對自定義view和viewholder的支持

首先,咱們要判別一個類究竟是自定義view仍是viewholder,對於activity和fragment很簡單,由於初始函數是不一樣的,一個是oncreate,一個是oncreateview,可是因爲增長了支持種類,老辦法就行不通了,這時咱們須要使用idea的sdk來進行判斷,代碼以下

GenCodeContext codeContext = new GenCodeContext(mClass, mFactory);
        String type = mClass.getSuperClassType().toString();
            if (type.contains("Activity")){
                codeContext.setStrategy(new ActivityStrategy(code,clickMap));
            }else if (type.contains("Fragment")) {
                codeContext.setStrategy(new FragmentStrategy(code,clickMap));
            }else if (type.contains("ViewHolder")||type.contains("Adapter<ViewHolder>")) {
                codeContext.setStrategy(new AdapterStrategy(code,clickMap));
            }else {
                codeContext.setStrategy(new CustomViewStrategy(code,clickMap));
            }
            codeContext.executeStrategy();
複製代碼

對於原來的代碼,咱們已經可以找到activity/fragment的特定位置插入代碼,可是對於自定義view和viewholder,又該用什麼特徵來定位該在哪裏插入呢? 對於這個問題咱們分狀況討論

  1. 自定義view 咱們這裏討論的自定義view僅僅針對組合view,自繪和擴展方式不作討論,由於這兩種方式通常不會使用ButterKnife. 組合自定義view的特徵 對於這種自定view,最大的特徵就是在構造的時候會使用inflate方法將xml文件進行壓入,那麼,找到inflate或者R.layout.xxx的語句,這裏就是咱們插入生成後代碼的位置 代碼
private PsiStatement findInflateStatement(PsiClass mClass){
        PsiStatement result = null;
        PsiMethod[] methods = mClass.getAllMethods();
        for (PsiMethod method:methods) {
            for (PsiStatement statement : method.getBody().getStatements()) {
                String returnValue = statement.getText();
                if (returnValue.contains("R.layout") || returnValue.contains("LayoutInflater.from(context).inflate")) {
                    result = statement;
                    break;
                }
            }
        }
        return result;
    }
複製代碼
  1. viewholder 這裏說的viewholder特指recyclerview.viewholder. 這種viewholder都有一個構造函數,參數爲(View xxx)第一句是super(xxx); 咱們能夠基於這兩個特徵進行定位。
private PsiStatement findSuperStatement(PsiMethod method,String viewName){
        PsiStatement result = null;
        for (PsiStatement statement : method.getBody().getStatements()) {
            String returnValue = statement.getText();
            if (returnValue.contains("super(" + viewName + ")")) {
                result = statement;
                break;
            }
        }
        return result;
    }
複製代碼

那麼,既然可以識別和找到哪裏插入代碼了,咱們的類型支持也就水到渠成了。 在類型支持的時候,咱們使用了策略模式,這樣根據類型不一樣,設置不一樣的策略就能夠方便的進行處理。 目錄結構以下

3.對onclick註解的支持

咱們對onclick的處理分如下幾步

  1. 尋找註解
  2. 分析註解信息並保持
  3. 根據保存信息生成代碼並插入

1.尋找註解

使用正則表達式很容易找到,這裏再也不重複貼代碼

2.分析註解信息並保存

onclick註解有幾種狀況

  1. 單id/多id的綁定
  2. 點擊函數是否有參數的狀況 咱們要獲取的信息有如下幾個
  3. 綁定的id列表
  4. 點擊函數的名稱,是否有參數,參數的類型 針對第二點,咱們使用一個對象將其封裝起來 保存獲取信息咱們使用一個Map<>來進行 代碼:
@Override
    public void process() {
        String pattern = "^@OnClick\\(\\{*(R.id.*,|R.id.*|R2.id.*|R2.id.*,)+\\}*\\)$";
        Pattern r = Pattern.compile(pattern);
        for (int i = 0;i < currentDoc.length;i++){
            Matcher m = r.matcher(currentDoc[i].trim());
            currentDoc[i] = currentDoc[i].trim();
            if (m.find()) {
                method = detectMethod(currentDoc[i+1]);
                ids = detectID(currentDoc[i], method);
                methodAndIDMap.put(method,ids);
                deleteLineNumbers.add(i);
            }
        }
    }
複製代碼

3.根據保存信息生成代碼並插入

這步咱們須要根據保存的信息進行代碼生成和插入,咱們主要討論生成,插入部分和findviewbyid代碼大同小異 咱們已經知道了註解的id和點擊對應的方法,那麼咱們復原的結果就應該是 findViewById(R.id.xxx).setOnclickListener(new OnclickListener(.... 咱們須要注意的地方就是點擊函數是否有參數,這會影響到咱們生成的代碼 看具體代碼:

protected StringBuilder getMethodInvokeString(ClickMehtod method) {
        StringBuilder methodString = new StringBuilder();
        if (method.isHaveArg()){
            methodString.append(method.getName()+"(("+method.getArgType()+")"+"v);");
        }else{
            methodString.append(method.getName()+"();");
        }
        return methodString;
    }
 protected String getOnClickCode(StringBuilder methodString, String id) {
        return "findViewById("+id+").setOnClickListener(new View.OnClickListener() {\n" +
                " @Override\n" +
                " public void onClick(View v){\n"+
                methodString.toString()+
                "}"+
                "});";
    }
複製代碼

到了這裏,咱們的Onclick註解支持也完成了。

總結

經過對這個小小的插件的開發和重構以及功能添加,雖然項目很小,可是工程和麪向對象的思想的重要性已經體現了出來,在一個擁有良好項目結構的工程下增長新功能是很是簡答而明快的,若是像最第一版本那樣把全部的代碼寫在一個文件中而沒有進行邏輯拆分的話,新增功能基本等於重寫項目,這確定是痛苦的。

還有一點值得一提,在作項目的時候第一步永遠是整體構思,第二部是具體拆分,寫代碼這件事的優先級並無那麼高,容易犯的一個問題就是一提到某個功能立刻就開始寫具體代碼,這樣的結果每每費力不討好,有一個明確的功能拆分和行進步驟會極大的加強開發體驗。

至此,RemoveButterKnife系列文章就告一段落了,這幾篇文章的目的不只僅是記錄開發RemoveButterKnife插件中的思路和遇到的問題,更重要的是總結了做者我開發軟件項目的一個歷程,而把這些寫下來的過程,也是鞏固這段歷程的重要步驟。

相關文章
相關標籤/搜索