打造一個簡易的編譯時注解框架(二)

上一節我們已經搭建好了環境,和一個簡易的demo,我們在此基礎上再寫一個簡單的注解demo
先看一看項目結構

Paste_Image.png

1,建一個注解類取名叫InjectView

@Retention(RetentionPolicy.CLASS)
public @interface InjectView {
    int value();
} 

2,在process Java moduel創建注解處理器ViewInjectorProcessor

在這里Google給我提供了一個依賴包,專門為了減少建立META-INF.services的步驟
在Java moduel的build中添加依賴:

compile 'com.google.auto.service:auto-service:1.0-rc2'

這個類就這樣寫:

@AutoService(Processor.class)
public class ViewInjectorProcessor extends AbstractProcessor {
  @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        return (Collections.singleton(InjectView.class.getCanonicalName()));
    }
   @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
      return  false;
}
}

3,建立注解處理接口類AnnotationHandler


public interface AnnotationHandler {
    /**
     * @param processingEnv
     */
    void attachProcessingEnv(ProcessingEnvironment processingEnv);

    /**
     * @param roundEnv
     * @return
     */
     Map<String, List<VariableElement>> handleAnnotation(RoundEnvironment roundEnv);
   // Map<String, InjectorInfo> handleAnnotation(RoundEnvironment roundEnv);
}

ViewInjectHandler實現里面的方法

public class ViewInjectHandler implements com.example.annote.handler.AnnotationHandler {
    ProcessingEnvironment mProcessingEnv;
    private Map<String, InjectorInfo> mInfoMap = new HashMap<>();
    @Override
    public void attachProcessingEnv(ProcessingEnvironment processingEnv) {
        mProcessingEnv = processingEnv;
    }


    @Override
    public Map<String, List<VariableElement>> handleAnnotation(RoundEnvironment roundEnv) {
  //  public   Map<String, InjectorInfo>  handleAnnotation(RoundEnvironment roundEnv) {
        Map<String, List<VariableElement>> annotationMap = new HashMap<String, List<VariableElement>>();
        // 獲取使用ViewInjector注解的所有元素
        Set<? extends Element> elementSet = roundEnv.getElementsAnnotatedWith(InjectView.class);
        for (Element element : elementSet) {
            // 注解的字段
            VariableElement varElement = (VariableElement) element;
            // 類型的完整路徑名,比如某個Activity的完整路徑
            String className = AnnotationUtil.getParentClassName(mProcessingEnv,varElement);
            // 獲取這個類型的所有注解,例如某個Activity中的所有View的注解對象
            List<VariableElement> cacheElements = annotationMap.get(className);
            if (cacheElements == null) {
                cacheElements = new LinkedList<VariableElement>();
            }
            // 將元素添加到該類型對應的字段列表中
            cacheElements.add(varElement);
            // 以類的路徑為key,字段列表為value,存入map.
            // 這里是將所在字段按所屬的類型進行分類
            annotationMap.put(className, cacheElements);
        }

        return annotationMap;
    }
}

4,建立接口AdapterWriter

public interface AdapterWriter {
    void generate(Map<String, List<VariableElement>> typeMap);
  //  void generate(Map<String, InjectorInfo> mInfoMap);
}

AbsWriter實現AdapterWriter

public abstract class AbsWriter implements AdapterWriter {
    ProcessingEnvironment mProcessingEnv;
    Filer mFiler;


    public AbsWriter(ProcessingEnvironment processingEnv) {
        mProcessingEnv = processingEnv;
        mFiler = processingEnv.getFiler();
    }

    @Override
      public void generate(Map<String, List<VariableElement>> typeMap) {
 //   public void generate(Map<String, InjectorInfo> infoMap) {
        Iterator<Map.Entry<String, List<VariableElement>>> iterator = typeMap.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<String, List<VariableElement>> entry = iterator.next();
            List<VariableElement> cacheElements = entry.getValue();
            if (cacheElements == null || cacheElements.size() == 0) {
                continue;
            }

            // 取第一個元素來構造注入信息
            InjectorInfo info = createInjectorInfo(cacheElements.get(0));
                Writer writer = null;
                JavaFileObject javaFileObject;
                try {
                    mProcessingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, info.getClassFullPath());
                    javaFileObject = this.mFiler.createSourceFile(info.getClassFullPath());

                    writer = javaFileObject.openWriter();
                    // 寫入package, import, class以及findViews函數等代碼段
                    generateImport(writer, info);
                    // 寫入該類中的所有字段到findViews方法中
                    for (VariableElement variableElement : entry.getValue()) {
                        writeField(writer, variableElement, info);
                    }
                    // 寫入findViews函數的大括號以及類的大括號
                    writeEnd(writer);
                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                    try {
                        if (writer != null) {
                            writer.flush();
                        }
                        IOUtil.closeQuitly(writer);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }

                }
            }
    //    writeInfo(infoMap);
    }

    /**
     * @param element
     * @return
     */
    protected InjectorInfo createInjectorInfo(VariableElement element) {
        TypeElement typeElement = (TypeElement) element.getEnclosingElement();
        String packageName = AnnotationUtil.getPackageName(mProcessingEnv, typeElement);
        String className = typeElement.getSimpleName().toString();
        return new InjectorInfo(packageName, className);
    }

    protected abstract void generateImport(Writer writer, InjectorInfo info)
            throws IOException;

    protected abstract void writeField(Writer writer, VariableElement element, InjectorInfo info)
            throws IOException;

    protected abstract void writeEnd(Writer writer) throws IOException;
}

DefaultJavaFileWriter繼承AbsWriter

public class DefaultJavaFileWriter extends AbsWriter {

    public DefaultJavaFileWriter(ProcessingEnvironment processingEnv) {
        super(processingEnv);
    }

    @Override
    protected void generateImport(Writer writer, InjectorInfo info) throws IOException {
        writer.write("package " + info.packageName + " ;");
        writer.write("\n\n");
        writer.write("import com.example.butterknife.InjectAdapter ;");
        writer.write("\n");
        writer.write("import com.example.butterknife.ViewFinder;");

        writer.write("\n\n\n");
        writer.write("/* This class is generated by Simple ViewInjector, please don't modify! */ ");
        writer.write("\n");
        writer.write("public class " + info.newClassName
                + " implements InjectAdapter<" + info.classlName + "> { ");
        writer.write("\n");
        writer.write("\n");
        // 查找方法
        writer.write("  public void injects(" + info.classlName
                + " target)  { ");
        writer.write("\n");


    }

      @Override
      protected void writeField(Writer writer, VariableElement element, InjectorInfo info) throws IOException {
          InjectView injector = element.getAnnotation(InjectView.class);
        //  writer.write(" ViewFinder.setContentView(target,"  + injector.value() + "  ) ; \n");
          String fieldName = element.getSimpleName().toString();
          writer.write("      target." + fieldName + " =  ViewFinder.findViewById(target, "
                  + injector.value() + "  ) ; ");
          writer.write("\n");
      }

    @Override
    protected void writeEnd(Writer writer) throws IOException {
        writer.write("  }");
        writer.write("\n\n");
        writer.write(" } ");
    }
    @Override
    public  String generateJavaCode(InjectorInfo info) {
        StringBuilder builder = new StringBuilder();
        builder.append("http:// Generated code from HyViewInjector. Do not modify!\n");
        builder.append("package ").append(info.packageName).append(";\n\n");

        builder.append("import android.view.View;\n");
        builder.append("import com.example.butterknife.ViewFinder;\n");
        builder.append("import com.example.butterknife.InjectAdapter ;\n");
        builder.append('\n');

        builder.append("public class ").append(info.newClassName);
        builder.append(" implements InjectAdapter<T>");
        builder.append(" {\n");
        generateInjectMethod(builder,info);
        builder.append("\n");
        builder.append("}\n");
        return builder.toString();
    }
}

5,ViewInjectorProcessor完整的代碼如下:

@AutoService(Processor.class)
public class ViewInjectorProcessor extends AbstractProcessor {

    /**
     * 所有注解處理器的列表
     */
    List<AnnotationHandler> mHandlers = new LinkedList<AnnotationHandler>();
    /**
     * 類型與字段的關聯表,用于在寫入Java文件時按類型來寫不同的文件和字段
     */
      final Map<String, List<VariableElement>> map = new HashMap<String, List<VariableElement>>();
   // final Map<String, InjectorInfo> mInfoMap = new HashMap<>();
    /**
     *
     */
    AdapterWriter mWriter;

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        return (Collections.singleton(InjectView.class.getCanonicalName()));
    }

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        registerHandlers();
        this.mWriter = new DefaultJavaFileWriter(processingEnv);
    }

    private void registerHandlers() {
        this.mHandlers.add(new ViewInjectHandler());
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        Iterator var4 = this.mHandlers.iterator();
        while (var4.hasNext()) {
            AnnotationHandler handler = (AnnotationHandler) var4.next();
            // 關聯ProcessingEnvironment
            handler.attachProcessingEnv(this.processingEnv);
            // 解析注解相關的信息
            map.putAll(handler.handleAnnotation(roundEnv));
        }
        // 將解析到的數據寫入到具體的類型中
        this.mWriter.generate(map);
        return true;
    }
}

6,建立Android moduel 名字butterknife,如圖

Paste_Image.png
創建注冊activity或者fragment的視圖接口注冊器

public interface InjectAdapter<T> {
void injects(T target);
}

創建轉化控件的靜態方法類
public class ViewFinder {


    public static <T extends View> T findViewById(Activity act, int id) {
        return (T) act.findViewById(id);
    }

   /* public static void setContentView(Activity act, int layoutId) {
        act.setContentView(layoutId);
    }
*/
    public static <T extends View> T findViewById(View rootView, int id) {
        return (T) rootView.findViewById(id);
    }

    public static <T extends View> T findViewById(Fragment fragment, int id) {
        return findViewById(fragment.getView(), id);
    }

}
綁定視圖的實現類

public final class ButterView {
    public static final String SUFFIX = "$ViewAdapter";
    static Map<Class<?>, InjectAdapter<?>> sInjectCache = new HashMap();

    public ButterView() {
    }

    public static void inject(Activity activity) {
        InjectAdapter adapter = getViewAdapter(activity.getClass());
        adapter.injects(activity);
    }

    public static void inject(Fragment fragment, View rootView) {
        InjectAdapter adapter = getViewAdapter(fragment.getClass());
        if(fuckTheFragment(fragment, rootView)) {
            adapter.injects(fragment);
        }
    }

    private static boolean fuckTheFragment(Fragment fragment, View rootView) {
        try {
            Class e;
            for(e = fragment.getClass(); e != Object.class && !e.equals(Fragment.class); e = e.getSuperclass()) {
                ;
            }

            Field rootViewField = e.getDeclaredField("mView");
            rootViewField.setAccessible(true);
            rootViewField.set(fragment, rootView);
            Log.e("", "### getView " + fragment.getView());
            return true;
        } catch (SecurityException var4) {
            var4.printStackTrace();
        } catch (NoSuchFieldException var5) {
            var5.printStackTrace();
        } catch (IllegalArgumentException var6) {
            var6.printStackTrace();
        } catch (IllegalAccessException var7) {
            var7.printStackTrace();
        }

        return false;
    }

    public static void inject(View view) {
    }

    private static <T> InjectAdapter<T> getViewAdapter(Class<?> clazz) {
        InjectAdapter adapter = (InjectAdapter)sInjectCache.get(clazz);
        if(adapter == null) {
            String adapterClassName = clazz.getName() + "$ViewAdapter";

            try {
                Class e = Class.forName(adapterClassName);
                adapter = (InjectAdapter)e.newInstance();
                sInjectCache.put(e, adapter);
            } catch (ClassNotFoundException var4) {
                var4.printStackTrace();
            } catch (InstantiationException var5) {
                var5.printStackTrace();
            } catch (IllegalAccessException var6) {
                var6.printStackTrace();
            }
        }

        Log.e("", "### find adapter : " + adapter);
        return (InjectAdapter)adapter;
    }

}

同樣的需要clean Project ,rebuild,生成Java文件
在MainActivity中驗證:

public class MainActivity extends AppCompatActivity {
    @InjectView(R.id.textView)
    TextView mTextView;
    @InjectView(R.id.textView1)
    TextView mTextView1;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
          setContentView(R.layout.activity_main);
        //    mTextView = (TextView) findViewById(R.id.textView);
        ButterView.inject(this);
        mTextView.setText("我是編譯時的注解");
        mTextView1.setText("我是第二個注解");
   //  showMessage();

    }

貼出utils和模型類里面的代碼

public final class AnnotationUtil {
    /**
     * 獲取包名
     * @param processingEnv
     * @param element
     * @return
     */
    public static String getPackageName(ProcessingEnvironment processingEnv, Element element) {
        return processingEnv.getElementUtils().getPackageOf(element).getQualifiedName().toString();
    }
    /**
     * 獲取某個字段的完整的路徑
     *
     * @param varElement
     * @return
     */
    public static String getParentClassName(ProcessingEnvironment processingEnv,VariableElement varElement) {
        // 獲取該元素所在的類型,例如某個View是某個Activity的字段,這里就是獲取這個Activity的類型
        TypeElement typeElement = (TypeElement) varElement.getEnclosingElement();
        // 獲取typeElement的包名
        String packageName = AnnotationUtil.getPackageName(processingEnv, typeElement);
        // 類型的完整路徑名,比如某個Activity的完整路徑
        return packageName + "." + typeElement.getSimpleName().toString();
    }
}

public class InjectorInfo {
    public static final String SUFFIX = "$ViewAdapter";
    /**
     * 被注解的類的包名
     */
    public String packageName;
    /**
     * 被注解的類的類名
     */
    public String classlName;
    /**
     * 要創建的InjectAdapter類的完整路徑,新類的名字為被注解的類名 + "$ViewAdapter", 與被注解的類在同一個包下
     */
    public String newClassName;


    private TypeElement typeElement;
    private int layoutId;

    private Map<Integer, ViewInfo> idViewMap = new HashMap<>();

    public InjectorInfo(String packageName, String classlName) {
        this.packageName = packageName;
        newClassName=classlName+SUFFIX;
        this.classlName = classlName;
    }

    public TypeElement getTypeElement() {
        return typeElement;
    }

    public void setTypeElement(TypeElement typeElement) {
        this.typeElement = typeElement;
    }

    public int getLayoutId() {
        return layoutId;
    }

    public void setLayoutId(int layoutId) {
        this.layoutId = layoutId;
    }


    public void putViewInfo(int id, ViewInfo viewInfo) {
        idViewMap.put(id, viewInfo);
    }

    public Map<Integer, ViewInfo> getIdViewMap() {
        return idViewMap;
    }

    public String getClassFullPath(){

        return packageName+ "."+newClassName;
    }
}

public class ViewInfo {
    private int id;
    private String type;
    private String name;

    public ViewInfo(int id, String type, String name) {
        this.id = id;
        this.type = type;
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

運行后如圖:

Paste_Image.png

這樣一個簡單的編譯時注解框架就打造好了。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,461評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,538評論 3 417
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,423評論 0 375
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,991評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,761評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,207評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,268評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,419評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,959評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,782評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,983評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,528評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,222評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,653評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,901評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,678評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,978評論 2 374

推薦閱讀更多精彩內容