【Android】APT(컴 파일 시 코드 생 성)

소개 하 다.
APT(Annotation Processing Tool)는 주 해 를 처리 하 는 도구 입 니 다.정확히 말 하면 자바 c 의 도구 입 니 다.컴 파일 할 때 주 해 를 스 캔 하고 처리 하 는 데 사 용 됩 니 다.주해 처리 기 는 자바 코드(또는 컴 파일 된 바이트 코드)를 입력 하여**자바 파일 을 출력 으로 생 성 합 니 다.쉽게 말 하면 컴 파일 기간 에 주 해 를 통 해 자바**파일 을 생 성 하 는 것 입 니 다.
역할.
APT 를 사용 하 는 장점 은 편리 하고 간단 하 며 중복 되 는 코드 를 적 게 사용 할 수 있다 는 것 이다.
ButterKnife,Dagger,EventBus 등 주해 프레임 워 크 를 사용 한 학생 들 은 이 프레임 워 크 를 이용 하면 코드 가 적 고 주 해 를 조금 만 쓰 면 된다 는 것 을 느 낄 수 있다.사실 그들 은 주 해 를 통 해 코드 를 만 들 었 을 뿐이다.APT 에 대한 학습 을 통 해 그들 은 매우 강하 다 는 것 을 알 게 될 것 입 니 다~~
이루어지다
이렇게 많아
목 표 는 APT 를 통 해 하나의 기능 을 실현 하고View변수 에 대한 주 해 를 통 해View의 바 인 딩ButterKnife중의@BindView을 실현 한다.
(여기 참고)
프로젝트 생 성 Android Module 이름 app 생 성 Java library Module 이름 apt-annotation 생 성 Java library Module 이름 apt-processor 의존 apt-annotation 생 성 Android library Module 이름 apt-library 의존 apt-annotation,auto-service
구 조 는 다음 과 같다.
기능 은 주로 세 부분 으로 나 뉜 다.
  • apt-annotation:사용자 정의 주석,저장@BindView
  • apt-processor:주해 프로세서,apt-annotation의 주해 에 따라 컴 파일 기간 에 코드 생 성xxxActivity_ViewBinding.java
  • apt-library:도구 류,호출xxxActivity_ViewBinding.java중의 방법 으로View의 연결 을 실현 합 니 다.

  • 관 계 는 다음 과 같다.
    app?app 은 기능 코드 가 아니 라 기능 을 검증 하 는 데 사 용 됩 니 다~~
    1.apt-annotation(사용자 정의 주석)
    주석 클래스 만 들 기BindView
    @Retention(RetentionPolicy.CLASS)
    @Target(ElementType.FIELD)
    public @interface BindView {
        int value();
    }
    
    @Retention(RetentionPolicy.CLASS):실행 시 주해@Target(ElementType.FIELD)를 나타 낸다.주해 범 위 는 클래스 구성원(구조 방법,방법,구성원 변수)임 을 나타 낸다.
    @Retention:보 존 된 시간의 길 이 를 정의 합 니 다.RetentionPoicy.SOURCE,RetentionPoicy.CLASS,RetentionPoicy.RUNTIME@Target:수 정 된 대상 범 위 를 정의 합 니 다 TYPE,FIELD,METHOD,PARAMETER,CONSTRUCTOR,LOCALVARIABLE 등 상세 내용
    실행 시 주석BindView을 정의 합 니 다.그 중에서value()은 대응 하 는Viewid을 가 져 오 는 데 사 용 됩 니 다.
    2.apt-processor(주해 프로세서)
    (중점 부분)Module에 의존 도 를 추가 합 니 다.
    dependencies {
        implementation 'com.google.auto.service:auto-service:1.0-rc2' 
        implementation project(':apt-annotation')
    }
    

    Android Studio 가 3.0 으로 업그레이드 되면 Gradle 도 3.0 으로 업그레이드 된다.implementation이전compile을 대체 했다.
    창설BindViewProcessor
    @AutoService(Processor.class)
    public class BindViewProcessor extends AbstractProcessor {
    
        private Messager mMessager;
        private Elements mElementUtils;
        private Map mProxyMap = new HashMap<>();
    
        @Override
        public synchronized void init(ProcessingEnvironment processingEnv) {
            super.init(processingEnv);
            mMessager = processingEnv.getMessager();
            mElementUtils = processingEnv.getElementUtils();
        }
    
        @Override
        public Set getSupportedAnnotationTypes() {
            HashSet supportTypes = new LinkedHashSet<>();
            supportTypes.add(BindView.class.getCanonicalName());
            return supportTypes;
        }
    
        @Override
        public SourceVersion getSupportedSourceVersion() {
            return SourceVersion.latestSupported();
        }
    
        @Override
        public boolean process(Set extends TypeElement> set, RoundEnvironment roundEnv) {
            //      Java  
            return false;
        }
    }
    
  • init:초기 화.ProcessingEnviroment,ProcessingEnviroment유용 한 도구 류Elements,TypesFiler
  • 를 많이 제공 합 니 다.
  • getSupportedAnnotationTypes:이 주해 처리 기 를 어느 주해 에 등 록 했 는 지 지정 합 니 다.여기 설명 은 주해BindView
  • 입 니 다.
  • getSupportedSourceVersion:지정 한 자바 버 전 입 니 다.보통 여 기 를 되 돌려 줍 니 다SourceVersion.latestSupported()
  • process:여기 서 주 해 를 스 캔,평가,처리 하 는 코드 를 쓰 고 자바 파일(process 의 코드 아래 상세 한 설명)을 생 성 할 수 있 습 니 다
  • @AutoService(Processor.class)
    public class BindViewProcessor extends AbstractProcessor {
    
        private Messager mMessager;
        private Elements mElementUtils;
        private Map mProxyMap = new HashMap<>();
    
        @Override
        public boolean process(Set extends TypeElement> set, RoundEnvironment roundEnvironment) {
            mMessager.printMessage(Diagnostic.Kind.NOTE, "processing...");
            mProxyMap.clear();
            //       
            Set extends Element> elements = roundEnvironment.getElementsAnnotatedWith(BindView.class);
            for (Element element : elements) {
                VariableElement variableElement = (VariableElement) element;
                TypeElement classElement = (TypeElement) variableElement.getEnclosingElement();
                String fullClassName = classElement.getQualifiedName().toString();
                ClassCreatorProxy proxy = mProxyMap.get(fullClassName);
                if (proxy == null) {
                    proxy = new ClassCreatorProxy(mElementUtils, classElement);
                    mProxyMap.put(fullClassName, proxy);
                }
                BindView bindAnnotation = variableElement.getAnnotation(BindView.class);
                int id = bindAnnotation.value();
                proxy.putElement(id, variableElement);
            }
            //    mProxyMap,  java  
            for (String key : mProxyMap.keySet()) {
                ClassCreatorProxy proxyInfo = mProxyMap.get(key);
                try {
                    mMessager.printMessage(Diagnostic.Kind.NOTE, " --> create " + proxyInfo.getProxyClassFullName());
                    JavaFileObject jfo = processingEnv.getFiler().createSourceFile(proxyInfo.getProxyClassFullName(), proxyInfo.getTypeElement());
                    Writer writer = jfo.openWriter();
                    writer.write(proxyInfo.generateJavaCode());
                    writer.flush();
                    writer.close();
                } catch (IOException e) {
                    mMessager.printMessage(Diagnostic.Kind.NOTE, " --> create " + proxyInfo.getProxyClassFullName() + "error");
                }
            }
    
            mMessager.printMessage(Diagnostic.Kind.NOTE, "process finish ...");
            return true;
        }
    }
    
    roundEnvironment.getElementsAnnotatedWith(BindView.class)를 통 해 모든 주석elements을 얻 은 다음elements의 정 보 를mProxyMap에 저장 하고 마지막 으로mProxyMap을 통 해 대응 하 는 자바 파일 을 만 듭 니 다.그 중에서mProxyMapClassCreatorProxyMap집합 입 니 다.ClassCreatorProxy자바 코드 를 만 드 는 프 록 시 클래스 입 니 다.다음 과 같 습 니 다.
    public class ClassCreatorProxy {
        private String mBindingClassName;
        private String mPackageName;
        private TypeElement mTypeElement;
        private Map mVariableElementMap = new HashMap<>();
    
        public ClassCreatorProxy(Elements elementUtils, TypeElement classElement) {
            this.mTypeElement = classElement;
            PackageElement packageElement = elementUtils.getPackageOf(mTypeElement);
            String packageName = packageElement.getQualifiedName().toString();
            String className = mTypeElement.getSimpleName().toString();
            this.mPackageName = packageName;
            this.mBindingClassName = className + "_ViewBinding";
        }
    
        public void putElement(int id, VariableElement element) {
            mVariableElementMap.put(id, element);
        }
    
        /**
         *   Java  
         * @return
         */
        public String generateJavaCode() {
            StringBuilder builder = new StringBuilder();
            builder.append("package ").append(mPackageName).append(";

    "
    ); builder.append("import com.example.gavin.apt_library.*;
    "
    ); builder.append('
    '
    ); builder.append("public class ").append(mBindingClassName); builder.append(" {
    "
    ); generateMethods(builder); builder.append('
    '
    ); builder.append("}
    "
    ); return builder.toString(); } /** * Method * @param builder */ private void generateMethods(StringBuilder builder) { builder.append("public void bind(" + mTypeElement.getQualifiedName() + " host ) {
    "
    ); for (int id : mVariableElementMap.keySet()) { VariableElement element = mVariableElementMap.get(id); String name = element.getSimpleName().toString(); String type = element.asType().toString(); builder.append("host." + name).append(" = "); builder.append("(" + type + ")(((android.app.Activity)host).findViewById( " + id + "));
    "
    ); } builder.append(" }
    "
    ); } public String getProxyClassFullName() { return mPackageName + "." + mBindingClassName; } public TypeElement getTypeElement() { return mTypeElement; } }

    위의 코드 는 주로Elements,TypeElement에서 원 하 는 정 보 를 얻 는 것 이다.예 를 들 어 package name,Activity 명,변수 유형,id 등 을 통 해StringBuilder코드 를 조금씩 맞 추고 각 대상 은 각각 대응 하 는Java파일 을 대표 한다.
    생각 지도 못 했 지!자바 코드 는 이렇게 쓸 수 있 습 니 다~생 성 된 코드 를 미리 보 세 요(정렬 되 지 않 고 포맷 되 었 습 니 다)
    public class MainActivity_ViewBinding {
        public void bind(com.example.gavin.apttest.MainActivity host) {
            host.mButton = (android.widget.Button) (((android.app.Activity) host).findViewById(2131165218));
            host.mTextView = (android.widget.TextView) (((android.app.Activity) host).findViewById(2131165321));
        }
    }
    

    결함.java을 통 해 자바 코드 를 조금씩 맞 추 는 것 이 번 거 로 울 뿐만 아니 라 잘못 쓰기 도 쉽 습 니 다~~
    더 좋 은 방안 은StringBuilder을 통 해 이러한 자바 코드 를 더욱 간단하게 생 성 할 수 있다.(나중에 얘 기 할 게 요.
    라 이브 러 리 auto-service 에 의존 하여 주석 처리 기 를 사용 할 때 먼저 설명 해 야 합 니 다.절차:1.processors 라 이브 러 리 의 main 디 렉 터 리 에서 resources 자원 폴 더 를 새로 만들어 야 합 니 다.2.resources 폴 더 에 META-INF/services 디 렉 터 리 폴 더 를 만 듭 니 다.3.META-INF/services 디 렉 터 리 폴 더 에 javax.annotation.processing.Processor 파일 을 만 듭 니 다.4.javax.annotation.processing.Processor 파일 에 주석 프로세서 의 전 칭 을 기록 합 니 다.패키지 경 로 를 포함 합 니 다.)이렇게 성명 하 는 것 도 너무 번 거 로 운 데?이것 이 바로 auto-service 를 도입 하 는 이유 다.auto-service 의@AutoService 를 통 해 AutoService 주석 처리 장 치 를 자동 으로 생 성 할 수 있 습 니 다.Google 에서 개발 한 것 으로 META-INF/services/javax.annotation.processing.Processor 파일 을 생 성 하 는 데 사 용 됩 니 다.
    3.apt-library 도구 류
    Processor 부분 을 완성 하여 거의 큰 성 과 를 거 두 었 습 니 다.javapoet에서 대응 하 는BindViewProcessor을 만 들 었 습 니 다.우 리 는 어떻게 호출 합 니까?당연히 반사 지!!!
    ModulexxxActivity_ViewBinding.java에 의존 도 를 추가 합 니 다.
    dependencies {
        implementation project(':apt-annotation')
    }
    

    주석 도구 클래스 만 들 기build.gradle
    public class BindViewTools {
    
        public static void bind(Activity activity) {
    
            Class clazz = activity.getClass();
            try {
                Class bindViewClass = Class.forName(clazz.getName() + "_ViewBinding");
                Method method = bindViewClass.getMethod("bind", activity.getClass());
                method.invoke(bindViewClass.newInstance(), activity);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        }
    }
    
    BindViewTools의 부분 은 비교적 간단 하 다.반 사 를 통 해 대응 하 는apt-library종 류 를 찾 은 다음 에 그 중의ViewBinding방법 으로bind()의 연결 을 완성 한다.
    지금까지 모든 관련 코드 를 다 써 서 마침내 꺼 내 서 미 끄 러 질 수 있 게 되 었 다.
    4、app
    Module 의View에 의존(Gradle>=2.2)
    dependencies {
        implementation project(':apt-annotation')
        implementation project(':apt-library')
        annotationProcessor project(':apt-processor')
    }
    

    Android Gradle 플러그 인 2.2 버 전의 출시,Android Gradle 플러그 인 은build.gradle라 는 기능 을 제공 하여 완전히 대체 합 니 다annotationProcessor(Gradle<2.2)Projectandroid-apt에서:
    buildscript {
        dependencies {
            classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'  
        }
    }
    

    Module 의build.gradle에서:
    apply plugin: 'com.android.application'
    apply plugin: 'com.neenbedankt.android-apt'
    dependencies {
        apt project(':apt-processor')
    }
    
    buile.gradle에 사용 하고MainActivity의 앞 에View주 해 를 붙 여BindView를 전달 하면 된다.
    public class MainActivity extends AppCompatActivity {
    
        @BindView(R.id.tv)
        TextView mTextView;
        @BindView(R.id.btn)
        Button mButton;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            BindViewTools.bind(this);
            mTextView.setText("bind TextView success");
            mButton.setText("bind Button success");
        }
    }
    

    운행 결 과 는 모두 가 알 고 있 을 것 이다.이것id의 기능 이 완성 되 었 다 는 것 을 증명 하기 위해 서 나 는 그림 을 붙 였 다.
    생 성 된 코드
    위의 기능 은 자바 코드 를 만 드 는 것 입 니 다.그러면 생 성 된 코드 는 어디 에 있 습 니까?app/build/generated/source/apt 에서 생 성 된 자바 파일 에 대응 하 는 코드 를 찾 을 수 있 습 니 다.
    public class MainActivity_ViewBinding {
        public void bind(com.example.gavin.apttest.MainActivity host) {
            host.mButton = (android.widget.Button) (((android.app.Activity) host).findViewById(2131165218));
            host.mTextView = (android.widget.TextView) (((android.app.Activity) host).findViewById(2131165321));
        }
    }
    

    javapoet 을 통 해 코드 생 성
    위 는BindView에서ClassCreatorProxy를 통 해 대응 하 는 자바 코드 를 생 성 한다.이런 방법 은 비교적 번 거 롭 고 더 우아 한 방법 이 있 는데 그것 이 바로 자바 poet 이다.
    우선 의존 도 추가
    dependencies {
        implementation 'com.squareup:javapoet:1.10.0'
    }
    

    그리고StringBuilder에서
    public class ClassCreatorProxy {
        //      ...
    
        /**
         *   Java  
         * @return
         */
        public TypeSpec generateJavaCode2() {
            TypeSpec bindingClass = TypeSpec.classBuilder(mBindingClassName)
                    .addModifiers(Modifier.PUBLIC)
                    .addMethod(generateMethods2())
                    .build();
            return bindingClass;
    
        }
    
        /**
         *   Method
         */
        private MethodSpec generateMethods2() {
            ClassName host = ClassName.bestGuess(mTypeElement.getQualifiedName().toString());
            MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("bind")
                    .addModifiers(Modifier.PUBLIC)
                    .returns(void.class)
                    .addParameter(host, "host");
    
            for (int id : mVariableElementMap.keySet()) {
                VariableElement element = mVariableElementMap.get(id);
                String name = element.getSimpleName().toString();
                String type = element.asType().toString();
                methodBuilder.addCode("host." + name + " = " + "(" + type + ")(((android.app.Activity)host).findViewById( " + id + "));");
            }
            return methodBuilder.build();
        }
    
    
        public String getPackageName() {
            return mPackageName;
        }
    }
    
    

    마지막 으로ClassCreatorProxy에서
        @Override
        public boolean process(Set extends TypeElement> set, RoundEnvironment roundEnvironment) {
            //      ...
            //  javapoet  
            for (String key : mProxyMap.keySet()) {
                ClassCreatorProxy proxyInfo = mProxyMap.get(key);
                JavaFile javaFile = JavaFile.builder(proxyInfo.getPackageName(), proxyInfo.generateJavaCode2()).build();
                try {
                    //     
                    javaFile.writeTo(processingEnv.getFiler());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            mMessager.printMessage(Diagnostic.Kind.NOTE, "process finish ...");
            return true;
        }
    

    자바 코드 를BindViewProcessor맞 추 는 것 보다 훨씬 간결 하고 많다.마지막 으로 생 성 된 코드 는 이전 과 같 아서 붙 이지 않 습 니 다.
    javapoet 상세 용법
    소스 코드
    GitHub
    레 퍼 런 스
    컴 파 일 러 주해 의 APT 컴 파 일 러 주해 의 사용법 을 상세히 소개 합 니 다.Android 컴 파 일 러 주해-Android APT 향상 및 APT 기반 간단 한 응용  Android 컴 파일 시 주석 해석 프레임 워 크 를 만 드 는 것 은 처음에 알 아야 할 APT,annotationProcessor,android-apt,provide,사용자 정의 주석 일 뿐 입 니 다.
    이상 잘못된 점 이 있 습 니 다.지적 해 주 셔 서 감사합니다.

    좋은 웹페이지 즐겨찾기