Android原生開發 APT製做重複打點檢測工具

背景

作數據打點的時候,若是打點字段重複了怎麼辦?java

例如:程序員

申明統計打點字段.swift

/** * 頁面1打開次數 */
    public static final int COUNT_KEY_1 = 10007;
    /** * 頁面2打開次數 */
    public static final int COUNT_KEY_2 = 10008;
複製代碼

其餘團隊作廣告模塊的同窗不知道10007和10008字段被佔用了.繼續申明瞭markdown

/** * 廣告打開次數 */
    public static final int COUNT_KEY_AD = 10008;
複製代碼

這個時候字段10008就被污染了,廣告打開次數和頁面2打開次數就沒法準確統計到.app

實際工做中,2個場景容易出現上訴事故.ide

  1. 多個程序員並行開發打點需求,代碼合併的時候自動合併了打點文件.
  2. 不一樣模塊之間打點,不知道對方模塊已經使用了什麼字段.

指望有個工具能夠檢測重複定義的字段模塊化

解決思路

轉化爲語法錯誤

iOS同窗能夠利用enum的語法特性+協議來解決.實例示例代碼以下:函數

enum Model: Int: ModelProtocol {
    case home = 11
    case sounds = 12
    
    public func toCode() -> Int64 {
        self.rawValue
    }
}


enum SleepPlan: Int64, EventCodeProtocol {
    var model: Model {
        return .home
    }
    case click = 0001
    case show = 0002
    
    public func toCode() -> Int64 {
        self.rawValue
    }
    
    public func type() -> EventTypeCode {
        .count
    }
    public func immediately() -> Bool {
        true
    }
}

protocol ModelProtocol {
    func toCode() -> Int64
}

extension EventCodeProtocol {
    var model: ModelProtocol
}

func log(code: EventCodeProtocol) {
    code.model.toCode() * 1000 + code.toCode()
}
複製代碼

交流了下實現原理,就是讓定義重複打點的時候觸發一次編譯器語法錯誤,讓編譯失敗,從而讓開發人員知道有字段重複定義了.工具

那麼Android上怎麼開發,Java能夠利用switch...case...語法不能有重複字段申明的特性,把上訴問題轉化爲下面的代碼ui

那麼問題是: 怎麼來實現這個校驗函數?,若是本身手寫的話耗時,耗力,還容易寫錯.

APT實現

比起手動去維護這個函數,APT的優點是能夠自動生成從而不易出錯.

參考ButterKnife、EventBus的實現後,總結APT的實現步驟大體以下:

1.申明註解

@Retention(RetentionPolicy.CLASS)
@Target(ElementType.TYPE)
public @interface Statistics {
    Type type();

    public enum Type {
        String, Int
    }
}
複製代碼

而後在原項目引用

@Statistics(type = Statistics.Type.Int)
public class StaticDemo2 {
複製代碼

這一步的目的是讓APT知道須要處理哪些類.

2.構造處理函數

@AutoService(Processor.class)
public class StatisticsProcessor extends AbstractProcessor {
    private Filer mFilerUtils;       // 文件管理工具類
    private Types mTypesUtils;    // 類型處理工具類
    private Elements mElementsUtils;  // Element處理工具類
    static Set<String> typeSet = new HashSet<>();
    private Map<Statistics.Type, List<String>> dataMap = new HashMap<>();

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);

        mFilerUtils = processingEnv.getFiler();
        mTypesUtils = processingEnv.getTypeUtils();
        mElementsUtils = processingEnv.getElementUtils();

    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnv) {
        System.out.println("start process");
        if (set != null && set.size() != 0) {
            Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(Statistics.class);
            categories(elements);
            if (dataMap.size() > 0) {
                Element simpleElement = elements.iterator().next();
                String code = generateCode(simpleElement, dataMap.get(Statistics.Type.String), dataMap
                        .get(Statistics.Type.Int));
                String helperClassName = "StatisticsChecker"; // 構建要生成的幫助類的類名
                try {
                    JavaFileObject jfo = mFilerUtils.createSourceFile(helperClassName);
                    Writer writer = jfo.openWriter();
                    writer.write(code);
                    writer.flush();
                    writer.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                return true;
            }
        }
        return false;
    }


    private void categories(Set<? extends Element> elements) {
        if (typeSet.size() == 0) {
            Statistics.Type[] types = Statistics.Type.values();
            for (int i = 0; i < types.length; i++) {
                typeSet.add(types[i].name().toLowerCase());
            }
        }
        for (Element element : elements) {
            Statistics statistics = element.getAnnotation(Statistics.class);
            Symbol.ClassSymbol cE = (Symbol.ClassSymbol) element;
            String preName = cE.className();
            List eleSubList = element.getEnclosedElements();

            Statistics.Type type = statistics.type();
            List list = dataMap.get(type);
            if (list == null) {
                list = new ArrayList<String>();
                dataMap.put(type, list);
            }

            for (Object e : eleSubList) {
                if (e instanceof Symbol.VarSymbol) {
                    Symbol.VarSymbol el = (Symbol.VarSymbol) e;
                    if (typeSet.contains(el.type.tsym.name.toString().toLowerCase())) {
                        // 其實這裏就能夠知道有沒有重複字段了 可是爲了讓結果更加直觀 仍是把這個代碼生成出來看
                        list.add(preName + "." + (el).getSimpleName().toString());
                    }
                }
            }
        }
    }

    private String generateCode(Element typeElement, List<String> listStr, List<String> listInt) {
        String packageName = ((PackageElement) mElementsUtils.getPackageOf(typeElement))
                .getQualifiedName().toString(); // 獲取要綁定包名
        String helperClassName = "StatisticsChecker";   // 要生成的幫助類的名稱

        StringBuilder builder = new StringBuilder();
        builder.append("package ").append(packageName).append(";\n");
        builder.append("\n");
        builder.append("public class ").append(helperClassName);
        builder.append(" {\n");
        builder.append("\tvoid check() {\n");

        if (listStr != null && listStr.size() > 0) {
            builder.append("\t\tString countStr = null;\n");
            builder.append("\t\tswitch (countStr) {\n");

            for (String caseValue : listStr) {
                builder.append("\t\t\t");
                builder.append(String.format("case %s:\n", caseValue));
            }
            builder.append("\t\t}\n");
        }

        if (listInt != null && listInt.size() > 0) {
            builder.append("\t\tint countInt = 0;\n");
            builder.append("\t\tswitch (countInt) {\n");

            for (String caseValue : listInt) {
                builder.append("\t\t\t");
                builder.append(String.format("case %s:\n", caseValue));
            }
            builder.append("\t\t}\n");
        }


        builder.append("\t}\n");
        builder.append("}\n");

        return builder.toString();
    }
}
複製代碼

這裏是模板寫法, 繼承AbstractProcessor類實現他的process方法. 目的是在並編譯的時候自動生成代碼.

3.注入

看ButterKnife還有一個注入的過程,就是把自動生成的代碼放到原來的項目裏面調用,可是咱們這裏只是用來作靜態檢測,生成便可,不須要注入.

總結

到此APT自動檢測重複字段就實現了,可是還有幾個問題沒有解決:

  1. 那個AbstractProcessor的process函數代碼看起來很麻煩,怎麼實現的? 個人方式是一邊調試一邊寫,那麼APT代碼怎麼調試?
  2. 我寫好了這份代碼,怎麼複用到其餘項目? APT模塊化開發+生成jcenter依賴
  3. 這裏只是Java的實現,若是集成項目裏面是koltin寫的或者java&kotlin混合開發怎麼辦? APT支持Koltin與項目依賴.
相關文章
相關標籤/搜索