我所知道的偷懶的方式

前言

在現今的軟件開發過程當中,軟件開發人員將更多的精力投入在了重複的類似勞動中。特別是在現在特別流行的 MVC 架構模式中,軟件各個層次的功能更加獨立,同時代碼的類似度也更加高。因此咱們須要尋找一種來減小軟件開發人員重複勞動的方法,讓程序員將更多的精力放在業務邏輯以及其餘更加具備創造力的工做上。java

服務端的技術發展了不少年,有不少值得能夠借鑑的地方。有的時候,在跟後臺溝通的時候,發現他們在設計好數據庫表結構的時候,常常能夠一鍵生成常見的功能(增、刪、改、查)。後來知道,由於相識程度很是的高。因此他們常常以代碼來生成代碼。就這樣完成了一站式的功能。android

更多時候,我在考慮,爲什麼他們不優先考慮封裝呢?git

  1. 可能會常常發生變更。
  2. 人員因素吧

Android 的一種"偷懶"

Android 中有種模板編程的偷懶方式。但不是今天的主角。今天的主角是Annotation Processor(註解處理器)。 在最近查閱源碼的時候。發現不少框架都會使用到Annotation Processor好比說,ButterKnifeDaggerARouter等等。因此理解Annotation Processor(註解處理器)的原理,是一個Android程序員必須具有的技能。程序員

1. Annotation Processor

Annotation Processor直譯成中文就是(註解處理器),就是可以對註解進行處理。既然可以對註解進行處理,那麼先定義一個簡單的註解。github

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface BindView {
    int value();
}

複製代碼
2. 使用註解

定義完註解後,咱們會想着在何時使用它呢? 還記得上述的核心是爲了偷懶,那麼 在Android存在有重複性很是高並且難度極地的代碼,就是對控件的獲取和處理。數據庫

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        TextView textView1=(TextView) findViewById(R.id.tvHello);
        textView1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
            }
        });
    }
}
複製代碼

這樣的模板代碼不只很浪費時間,並且代碼的美觀程度也大大的下降了。那麼咱們如今嘗試着用本身定義的註解去處理。編程

public class MainActivity extends AppCompatActivity {

    @BindView(R.id.tvHello)
    TextView tvHello;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    @OnClick(R.id.tvHello)
    public void changeText2() {
        Toast.makeText(this, "2222", Toast.LENGTH_LONG).show();
    }
}

複製代碼

上述的代碼,就會比較清晰。不會有獲取控件的過程的代碼。聲明即便用了。那麼僅僅這樣打上一個註解,可以自動的獲取控件嗎?答案是不可能的。咱們還缺乏一步編寫註解處理器(其實就是編寫遇到這個註解改怎麼處理)。api

3. 編寫註解處理器
xx01 最終生成的類

在編寫註解處理器以前,先嚐試着去編寫一個類。這個類的職責就是獲取控件。bash

public final class MainActivity$ViewBinding {
  public MainActivity$ViewBinding(MainActivity target, View source) {
    if(target == null)  return;
    if(source == null)  return;
    target.tvHello = (android.widget.TextView)source.findViewById(2131165275);
  }

  public MainActivity$ViewBinding(MainActivity target) {
    this(target, target.getWindow().getDecorView());
  }
}
複製代碼

上面的這個類,就是須要註解處理器幫咱們自動生成的模板代碼。咱們必須知道本身須要什麼代碼,才能控制註解處理器生成咱們想要的代碼。那麼觀察一下最終生成的代碼,其中有幾個須要注意的:數據結構

  1. 須要遍歷全部須要處理的註解。在本文中咱們只關心BindView
  2. 當咱們遍歷出全部帶有BindView的註解的時候,咱們須要爲這個字段賦值。而賦值的操做,咱們是經過屬性直接賦值(默認的訪問權限是包訪問權限)

target.tvHello = (android.widget.TextView)source.findViewById(2131165275);

因此這個由註解處理器幫我生成的類,須要跟目標類在同級包下。 3. 這個類的名字是按照必定格式生成的,在本文中它是由目標類+$ViewBinding。

到這裏,須要注意的點都講完了,接下開始來編寫註解處理器的核心代碼。

xx02 項目目錄的劃分

在知道了咱們最終須要生成什麼代碼以後,就須要對整個目錄進行規劃一下,由於這個工具不只僅是當前這個項目會被使用。頗有不少項目將對其引用。你可能會將其發佈到JCenter上。

image.png

根據上面的圖,咱們分別創建3module。其中2個是java libaryandroid libary

image.png

最終的目錄結構是這樣的

image.png

而後咱們將剛纔編寫的BindView註解放入inject-annotation模塊中

xx03 編寫註解處理器

在劃分話目錄結構後,咱們能夠編寫註解處理器的核心代碼了,也就意味着,咱們須要把它的代碼放置在inject-compiler中。

須要在inject-compiler下的build.gradle導入幾個庫

implementation 'com.google.auto.service:auto-service:1.0-rc4'
    annotationProcessor 'com.google.auto.service:auto-service:1.0-rc4'
    api 'com.squareup:javapoet:1.10.0'
    implementation project(':inject-annotation')
複製代碼

介紹一下這幾個庫,

  1. auto-service 是幫助生成META-INF/services/javax.annotation.processing文件中的內容
  2. javapoet 更加面向對象的輸出代碼
  3. inject-annotation 須要處理的註解。

在添加完這些東西以後,咱們就須要建立一個註解處理器了

@Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        HashMap<TypeElement, List<Element>> datas = new HashMap<>();
        for (Element element : roundEnv.getElementsAnnotatedWith(BindView.class)) {
            TypeElement originalType = (TypeElement) element.getEnclosingElement();
            if (datas.get(originalType) == null) {
                List<Element> elements = new ArrayList<>();
                datas.put(originalType, elements);
            }
            List<Element> dd = datas.get(originalType);
            dd.add(element);
        }

        for (Map.Entry<TypeElement, List<Element>> entry : datas.entrySet()) {
            TypeElement key = entry.getKey();
            List<Element> value = entry.getValue();
            createFile(key, value);
        }
        return false;
    }

複製代碼

核心代碼大概是上面的,就是遍歷類中的註解元素,包裝成一個Map數據的數據結構。

image.png

而後將構建後的數據結構,進行處理,按照之間MainActivity$ViewBinding的所預想的規則進行輸出。

private MethodSpec createCustomerConstructor2View(TypeElement originalType, List<Element> elements) {
        ParameterSpec targetParamSpec = ParameterSpec.builder(TypeName.get(originalType.asType()), "target").build();
        MethodSpec.Builder constructor1 = MethodSpec.constructorBuilder()
                .addModifiers(Modifier.PUBLIC)
                .addParameter(targetParamSpec)
                .addParameter(mViewParameterSpec)
                .addStatement("if(target == null) return")
                .addStatement("if(source == null) return");

        for (Element element : elements) {
            String variateName = element.getSimpleName().toString();
            String variateType = element.asType().toString();
            int resId = element.getAnnotation(BindView.class).value();
            constructor1.addStatement("target.$L = ($N)source.findViewById($L)", variateName, variateType, resId);
        }
        return constructor1.build();
    }
複製代碼

這樣咱們就完成了註解處理器的編寫,當你編寫後從新Rebuild Project後,若是不出意外的話,你能夠找到下面這個文件。

image.png

xx03 調試Annotation Processor(註解處理器)

若是你在編寫註解處理器可能不是你預想,那麼斷點調試就變得很是的重要了。那麼接下來介紹如何調試註解處理器.

  1. 打開Edit Configurations新建一個Remote Configurations

image.png

  1. Terminal中運行下面命令

./gradlew --no-daemon -Dorg.gradle.debug=true :app:clean :app:compileDebugJavaWithJavac

image.png

  1. 須要斷點的地方,下斷點。

  2. Debug 剛纔添加的Configurations

image.png

5.它就能夠正常的斷點了

image.png

xx04 調用生成的代碼

當咱們完成了註解處理器的編寫,也正常的生成了MainActivity$ViewBinding文件後,咱們就須要調用所生成的類,讓它來幫咱們完成控件的注入。由於其中會涉及到對Activity的引用,因此這個包是android libary。也就意味着,接下來的代碼須要在inject-core模塊中編寫。

咱們在進入一個Activity或者Fragment的時候,可以調用inject方法,而inject的實現以下:

public static void inject(Activity activity) {
        String name = activity.getClass().getSimpleName();
        String packName = activity.getPackageName();
        String fullName = name + "$ViewBinding";
        try {
            Class targetClass = activity.getClassLoader().loadClass(packName + "." + fullName);
            Constructor constructor = targetClass.getConstructor(activity.getClass());
            constructor.newInstance(activity);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

複製代碼

就是經過反射,而後實例化剛纔的對象。這樣的話,咱們就完成了控件的注入了。

4. 發佈到JCenter

由於咱們項目中存在有jaraar格式的包,因此須要分別上傳。在編寫上傳任務以前,須要先在最外層的build.gradle添加plugin,請注意插件的版本。否則會出現一些奇奇怪怪的問題。

buildscript {
    repositories {
        google()
        jcenter()
        
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.5.0'
        classpath 'com.github.dcendents:android-maven-gradle-plugin:2.0'
        classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.6'

    }
}

allprojects {
    repositories {
        google()
        jcenter()
        
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

複製代碼
  1. 編寫jar格式的gradle,在最外層新建jar_publish.gradle文件,而後在:inject-compilerbuild.gradle中 apply。

image.png

  1. 編寫aar格式的gradle,在最外層新建aar_publish.gradle文件,而後在:inject-corebuild.gradle中 apply。

image.png

而後依次上傳便可。

本文中的代碼

  1. github.com/BelongsH/AP…

相關連接

  1. github.com/google/auto…
相關文章
相關標籤/搜索