国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

自定義Android注解Part2:代碼自動生成

mist14 / 2390人閱讀

摘要:使用來綁定該,主要是用來實例化自動生成的類。該部分下篇文章將提及我們自己定義的綁定注解庫已經(jīng)完成了,接下來我們將實現(xiàn)它的代碼自動生成部分。也是注解庫代碼自動生成的核心部分。該方法的作用就是獲取到有我們自定義注解的。

上一期我們已經(jīng)把butterknife-annotations中的注解變量都已經(jīng)定義好了,分別為BindView、OnClick與Keep。

如果你是第一次進入本系列文章,強烈推薦跳到文章末尾查看上篇文章,要不然你可能會有點云里霧里。

如果在代碼中引用的話,它將與開源庫ButterKnife的操作類似。

class MainActivity : AppCompatActivity() {
 
    @BindView(R.id.public_service, R.string.public_service)
    lateinit var sName: TextView
 
    @BindView(R.id.personal_wx, R.string.personal_wx)
    lateinit var sPhone: TextView
 
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        Butterknife.bind(this)
    }
 
    @OnClick(R.id.public_service)
    fun nameClick(view: View) {
        Toast.makeText(this, getString(R.string.public_service_click_toast), Toast.LENGTH_LONG).show()
    }
 
    @OnClick(R.id.personal_wx)
    fun phoneClick(view: View) {
        Toast.makeText(this, getString(R.string.personal_wx_click_toast), Toast.LENGTH_LONG).show()
    }
}

使用@BindView來綁定我的View,使用@OnClick來綁定View的點擊事件。使用Butterknife.bind來綁定該Class,主要是用來實例化自動生成的類。(該部分下篇文章將提及)

我們自己定義的綁定注解庫已經(jīng)完成了1/3,接下來我們將實現(xiàn)它的代碼自動生成部分。這時就到了上期提到的第二個Module:butterknife-compiler。

NameUtils是一些常量的管理工具類。

final class NameUtils {
 
    static String getAutoGeneratorTypeName(String typeName) {
        return typeName + ConstantUtils.BINDING_BUTTERKNIFE_SUFFIX;
    }
 
    static class Package{
        static final String ANDROID_VIEW = "android.view";
    }
 
    static class Class {
        static final String CLASS_VIEW = "View";
        static final String CLASS_ON_CLICK_LISTENER = "OnClickListener";
    }
 
    static class Method{
        static final String BIND_VIEW = "bindView";
        static final String SET_ON_CLICK_LISTENER = "setOnClickListener";
        static final String ON_CLICK = "onClick";
    }
 
    static class Variable{
        static final String ANDROID_ACTIVITY = "activity";
    }
}

NameUitls包含了自動生成的類名稱,包名,方法名,變量名。總之就是為了代碼更健全,方便管理。

第二個類Processor是今天的重中之重。也是注解庫代碼自動生成的核心部分。由于注解的自動生成代碼都是在注解進程中進行,所以這里它繼承于AbstractProcessor,其中主要有三個方法需要實現(xiàn)。

init:初始化必要的數(shù)據(jù)

getSupportedAnnotationTypes:所支持的注解

process:解析注解,編寫自動生成代碼

init

從簡單到容易,先是init方法,我們直接看代碼

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        mFiler = processingEnv.getFiler();
        mMessager = processingEnv.getMessager();
        mElementUtils = processingEnv.getElementUtils();
    }

方法參數(shù)processingEnv為我們提供注解處理所需的環(huán)境狀態(tài)。我們通過getFiler()、getMessager()與getElementUthis()方法,分別獲取創(chuàng)建源代碼的Filer、消息發(fā)送器Messager(主要用于向外界發(fā)送錯誤信息)與解析注解元素所需的通用方法。

例如:當(dāng)我們已經(jīng)構(gòu)建好了需要自動生成的類,這時我們就可以使用Filter來將代碼寫入到j(luò)ava文件中,如遇錯誤使用Messager將錯誤信息發(fā)送出去。

//寫入java文
try {
    JavaFile.builder(packageName, typeBuilder.build()).build().writeTo(mFiler)
} catch (IOException e) {
    mMessager.printMessage(Diagnostic.Kind.ERROR, e.getMessage(), typeElement);
}

代碼中的JavaFile與typeBuilder都是JavaPoet中的類。JavaPote主要提供Java API來幫助生成.java資源文件。

getSupportedAnnotationTypes
    @Override
    public Set getSupportedAnnotationTypes() {
        return new TreeSet<>(Arrays.asList(
                BindView.class.getCanonicalName(),
                OnClick.class.getCanonicalName(),
                Keep.class.getCanonicalName())
        );
    }

看方法名就知道了,包含所支持的注解,將其通過set集合來返回。這里將我們上一期自定義的注解添加到set集合中即可。

process

到了本篇文章的核心,process用來生成與注解相匹配的方法代碼。通過解析Class中定義的注解,生成與注解相關(guān)聯(lián)的類。

    @Override
    public boolean process(Set annotations, RoundEnvironment roundEnv) {
        ....
        ....
        return true;
    }

提供了兩個參數(shù):annotations與roundEnv,分別代表需要處理的注解,這里就代表我們自定義的注解;注解處理器所需的環(huán)境,幫助進行解析注解。

在開始解析注解之前,我們應(yīng)該先過濾我們所不需要的注解。回頭看getSupportedAnnotationTypes方法,我們只支持BindView、OnClick與Keep這三個注解。為了解析出相匹配的注解,我們將這個邏輯多帶帶抽離出來,交由getTypeElementsByAnnotationType來管理。

    private Set getTypeElementsByAnnotationType(Set annotations, Set elements) {
        Set result = new HashSet<>();
        //遍歷包含的 package class method
        for (Element element : elements) {
            //匹配 class or interface
            if (element instanceof TypeElement) {
                boolean found = false;
                //遍歷class中包含的 filed method constructors
                for (Element subElement : element.getEnclosedElements()) {
                    //遍歷element中包含的注釋
                    for (AnnotationMirror annotationMirror : subElement.getAnnotationMirrors()) {
                        for (TypeElement annotation : annotations) {
                            //匹配注釋
                            if (annotationMirror.getAnnotationType().asElement().equals(annotation)) {
                                result.add((TypeElement) element);
                                found = true;
                                break;
                            }
                        }
                        if (found) break;
                    }
                    if (found) break;
                }
            }
        }
        return result;
    }

首先理解Element是什么?Element代表程序中的包名、類、方法,這也是注解所支持的作用類型。然后再回到代碼部分,已經(jīng)給出詳細代碼注釋。
該方法的作用就是獲取到有我們自定義注解的class。這里介紹兩個主要的方法

getEnclosedElements():獲元素中的閉包的注解元素,在我們的實例中元素為MainActivity(TypeElement,Type代表Class),而閉包的注解元素則為sName、sPhone、nameClick、phoneClick與onCreate。在這里簡單的理解就是獲取有注解的字段名、方法名

getAnnotationMirrors():獲取上述閉包元素的所有注解。這里分別為sName與sPhone上的@BindeView、nameClick與phoneClick上的@OnClick、onCreate上的@Override。

所以通過該方法最終返回的就是MainActivity,它將被轉(zhuǎn)化為TypeElement類型返回,然后將由processing來處理。

我們再回到process方法中。通過getTypeElementsByAnnotationType()方法我們已經(jīng)獲取到了我們使用了自定義注解的TypeElement(MainActivity)。

//獲取與annotation相匹配的TypeElement,即有注釋聲明的class
Set elements = getTypeElementsByAnnotationType(annotations, roundEnv.getRootElements());

下面我們再獲取構(gòu)建類所需的相關(guān)信息。

//包名
String packageName = mElementUtils.getPackageOf(typeElement).getQualifiedName().toString();
//類名
String typeName = typeElement.getSimpleName().toString();
//全稱類名
ClassName className = ClassName.get(packageName, typeName);
//自動生成類全稱名
ClassName autoGenerationClassName = ClassName.get(packageName,
        NameUtils.getAutoGeneratorTypeName(typeName));
 
//構(gòu)建自動生成的類
TypeSpec.Builder typeBuilder = TypeSpec.classBuilder(autoGenerationClassName)
        .addModifiers(Modifier.PUBLIC)
        .addAnnotation(Keep.class);

注釋已經(jīng)劃分清楚了,可以分為四個步驟

獲取對應(yīng)typeElement的包名(這里獲取的是com.idisfkj.androidapianalysis)

獲取typeElement的SimpleName(這里為MainActivity字符串)

根據(jù)上述獲取的包名與SimpleName來構(gòu)建一個ClassName,為了后續(xù)聲明方法的參數(shù)類型(這里為MainActivity類,注意是MainActivity類型)

構(gòu)建需要自動生成的ClassName,這里使用NameUtils.getAutoGeneratorTypeName進行了統(tǒng)一命名(這里自動生成的類名為MainActivity$Binding,都以原始類名后面加$Binding)

所有信息準(zhǔn)備完畢后,然后開始定義自動生成的類。這里通過使用TypeSpec.Builder來構(gòu)建。它是JavaPoet中的類。

JavaPoet

由于直接使用JavaFileObject生成.java資源文件是非常麻煩的,所以推薦使用JavaPoet。JavaPoet是一個開源庫,主要用來幫助方便快捷的生成.java的資源文件。想要全面了解的可以查看Github鏈接。為了幫助快速讀懂該文章,這里對其中幾個主要方法進行介紹。當(dāng)然在使用前還需在butterknife-compiler中的builder.gradle添加依賴:

dependencies {
    implementation fileTree(dir: "libs", include: ["*.jar"])
    implementation project(":butterknife-annotations")
    implementation "com.squareup:javapoet:1.11.1"
}

同時也將上一期我們自定義的注解Module引入。

TypeSpec.Builder: 定義一個類

addModifiers: 定義private、public與protected類型

addAnnotation: 對Element元素添加注解。例如:@Keep

TypeSpec.Builder -> addMethod: 添加方法

MethodSpec -> addParameter: 為方法添加參數(shù)類型與參數(shù)名

MethodSpec -> addStatement: 在方法中添加代碼塊。而其中的一些動態(tài)類型會使用占位符替代。例如:addStatement("$N($N)", "bindView", "activity"),它將會生成bindView(activity)。占位符:$N -> name, $T -> type(ClassName), $L -> literals

有了上面的理解我們再來看下面的生成代碼:

//構(gòu)建自動生成的類
TypeSpec.Builder typeBuilder = TypeSpec.classBuilder(autoGenerationClassName)
        .addModifiers(Modifier.PUBLIC)
        .addAnnotation(Keep.class);
 
//添加構(gòu)造方法
typeBuilder.addMethod(MethodSpec.constructorBuilder()
        .addModifiers(Modifier.PUBLIC)
        .addParameter(className, NameUtils.Variable.ANDROID_ACTIVITY)
        .addStatement("$N($N)",
                NameUtils.Method.BIND_VIEW,
                NameUtils.Variable.ANDROID_ACTIVITY)
        .addStatement("$N($N)",
                NameUtils.Method.SET_ON_CLICK_LISTENER,
                NameUtils.Variable.ANDROID_ACTIVITY)
        .build());

首先通過TypeSpec.Builder構(gòu)建一個類,類名為autoGenerationClassName(MainActivity$Binding),類的訪問級別為public,由于為了防止混淆使用了我們自定義的@Keep注解。

然后再來添加類的構(gòu)造方法,使用addMethod、addModifiers、addParameter與addStatement分別構(gòu)建構(gòu)造方法名、方法訪問級別、方法參數(shù)與方法中執(zhí)行的代碼塊。所以上面的代碼最終將會自動生成如下代碼:

@Keep
public class MainActivity$Binding {
  public MainActivity$Binding(MainActivity activity) {
    bindView(activity);
    setOnClickListener(activity);
  }
}  

在自動生成類的構(gòu)造方法中調(diào)用了我們想要的bindView與setOnClickListener方法。所以接下來我們要實現(xiàn)的就是這兩個方法的構(gòu)建。

bindView
//添加bindView成員方法
MethodSpec.Builder bindViewBuilder = MethodSpec.methodBuilder(NameUtils.Method.BIND_VIEW)
        .addModifiers(Modifier.PRIVATE)
        .returns(TypeName.VOID)
        .addParameter(className, NameUtils.Variable.ANDROID_ACTIVITY);
 
//添加方法內(nèi)容
for (VariableElement variableElement : ElementFilter.fieldsIn(typeElement.getEnclosedElements())) {
    BindView bindView = variableElement.getAnnotation(BindView.class);
    if (bindView != null) {
        bindViewBuilder.addStatement("$N.$N=($T)$N.findViewById($L)",
                NameUtils.Variable.ANDROID_ACTIVITY,
                variableElement.getSimpleName(),
                variableElement,
                NameUtils.Variable.ANDROID_ACTIVITY,
                bindView.value()[0]
        ).addStatement("$N.$N.setText($N.getString($L))",
                NameUtils.Variable.ANDROID_ACTIVITY,
                variableElement.getSimpleName(),
                NameUtils.Variable.ANDROID_ACTIVITY,
                bindView.value()[1]);
    }
}
 
typeBuilder.addMethod(bindViewBuilder.build());

使用MethodSpec.Builder來創(chuàng)建bindView方法,其它的都與構(gòu)造方法類似。使用returns為方法返回void類型。然后再遍歷MainActivity中的注解,找到與我們定義的BindView相匹配的字段。最后分別向bindView方法中添加findViewById與setText代碼塊,同時將定義的方法添加到typeBuilder中。所以執(zhí)行完上面代碼后在MainActivity$Binding中展示如下:

  private void bindView(MainActivity activity) {
    activity.sName=(TextView)activity.findViewById(2131165265);
    activity.sName.setText(activity.getString(2131427362));
    activity.sPhone=(TextView)activity.findViewById(2131165262);
    activity.sPhone.setText(activity.getString(2131427360));
  }

實現(xiàn)了我們最初的View的綁定與TextView的默認值設(shè)置。

setOnClickListener
//添加setOnClickListener成員方法
MethodSpec.Builder setOnClickListenerBuilder = MethodSpec.methodBuilder(NameUtils.Method.SET_ON_CLICK_LISTENER)
        .addModifiers(Modifier.PRIVATE)
        .returns(TypeName.VOID)
        .addParameter(className, NameUtils.Variable.ANDROID_ACTIVITY, Modifier.FINAL);
 
//添加方法內(nèi)容
ClassName viewClassName = ClassName.get(NameUtils.Package.ANDROID_VIEW, NameUtils.Class.CLASS_VIEW);
ClassName onClickListenerClassName = ClassName.get(NameUtils.Package.ANDROID_VIEW, NameUtils.Class.CLASS_VIEW, NameUtils.Class.CLASS_ON_CLICK_LISTENER);
 
for (ExecutableElement executableElement : ElementFilter.methodsIn(typeElement.getEnclosedElements())) {
    OnClick onClick = executableElement.getAnnotation(OnClick.class);
    if (onClick != null) {
        //構(gòu)建匿名class
        TypeSpec typeSpec = TypeSpec.anonymousClassBuilder("")
                .addSuperinterface(onClickListenerClassName)
                .addMethod(MethodSpec.methodBuilder(NameUtils.Method.ON_CLICK)
                        .addModifiers(Modifier.PUBLIC)
                        .addParameter(viewClassName, NameUtils.Class.CLASS_VIEW)
                        .returns(TypeName.VOID)
                        .addStatement("$N.$N($N)",
                                NameUtils.Variable.ANDROID_ACTIVITY,
                                executableElement.getSimpleName(),
                                NameUtils.Class.CLASS_VIEW)
                        .build())
                .build();

        setOnClickListenerBuilder.addStatement("$N.findViewById($L).setOnClickListener($L)",
                NameUtils.Variable.ANDROID_ACTIVITY,
                onClick.value(),
                typeSpec);
    }
}
 
typeBuilder.addMethod(setOnClickListenerBuilder.build());

與bindView方法不同的是,由于使用到了匿名類OnClickListener與類View,所以我們這里也要定義他們的ClassName,然后使用TypeSpec來生成匿名類。生成之后再添加到setOnClickListener方法中。最后再將setOnClickListener方法添加到MainActivity$Binding中。所以最終展示如下:

  private void setOnClickListener(final MainActivity activity) {
    activity.findViewById(2131165265).setOnClickListener(new View.OnClickListener() {
      public void onClick(View View) {
        activity.nameClick(View);
      }
    });
    activity.findViewById(2131165262).setOnClickListener(new View.OnClickListener() {
      public void onClick(View View) {
        activity.phoneClick(View);
      }
    });
  }

我們的MainActivity$Binding類就已經(jīng)定義完成,最后再寫入到j(luò)ava文件中

//寫入java文件
try {
    JavaFile.builder(packageName, typeBuilder.build()).build().writeTo(mFiler);
} catch (IOException e) {
    mMessager.printMessage(Diagnostic.Kind.ERROR, e.getMessage(), typeElement);
}
services

在butterknife-compiler中,我們還需創(chuàng)建一個特定的目錄:
butterknife-compiler/src/main/resources/META-INF/services

在services目錄中,我們還需創(chuàng)建一個文件:javax.annotation.processing.Processor,該文件是用來告訴編譯器,當(dāng)它在編譯代碼的過程中正處于注解處理中時,會告訴注解處理器來自動生成哪些類。

所以我們在文件中將添加我們自定義的Processor路徑

com.idisfkj.butterknife.compiler.Processor

這樣注解器就會調(diào)用該指定的Processor。到這里整個butterknife-compiler就完成了,現(xiàn)在我們可以Make Project一下工程,完成之后就可以全局搜索到MainActivity$Binding文件了。或者在如下路徑中查看:

/app/build/generated/source/kapt/debug/com/idisfkj/androidapianalysis/MainActivity$Binding.java

文章中的代碼都可以在Github中獲取到。使用時請將分支切換到feat_annotation_processing

相關(guān)文章

自定義Android注解Part1:注解變量

自定義Android注解Part3:綁定

關(guān)注

公眾號:怪談時間到了

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/71549.html

相關(guān)文章

  • 定義Android注解Part3:綁定

    摘要:上一節(jié)我們已經(jīng)將自動生成注解代碼部分介紹完畢,今天這篇文章是自定義注解系列的最后一篇文章。該部分是對我們前面定義的注解變量與自動生成的代碼進行綁定,即調(diào)用我們自動生成的代碼。 showImg(https://segmentfault.com/img/bVbdEsh?w=1200&h=720); 上一節(jié)我們已經(jīng)將自動生成注解代碼部分介紹完畢,今天這篇文章是自定義Android注解系列的最...

    王晗 評論0 收藏0
  • 定義Android注解Part1:注解變量

    摘要:下面我們會自己實現(xiàn)與注解,實現(xiàn)中的對應(yīng)注解功能。帶大家一起來聲明注解變量。知道了它的作用范圍之后,我們在自定義注解時就要盡量較小注解的作用范圍,提高項目的編譯與運行速度。它們代表自定義的注解能夠作用的對象。總結(jié)庫中的自定義注解就完成了。 showImg(https://segmentfault.com/img/bVbc08a?w=740&h=416); 對于Android注解,或多或少...

    KavenFan 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<