Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 寫一個 ButterKnife

寫一個 ButterKnife

編輯:關於Android編程

ButterKnife 很多人都用過,能節省很多代碼,最多的就是省去了很多 findViewById 語句。接下來自己寫一個,就叫 BBKnife 吧。

分析

在使用 ButterKnife 時,需要書寫下面的類似代碼,以一個 Activity 為例

ExampleActivity.java

class ExampleActivity extends Activity {
  @BindView(R.id.title) TextView title;
  @BindView(R.id.subtitle) TextView subtitle;
  @BindView(R.id.footer) TextView footer;

  @Override public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.simple_activity);
    ButterKnife.bind(this);
    // TODO Use fields...
  }
}

在編譯後,會自動生成一個和 Activity 同名的 ExampleActivity$ViewBinder 的輔助類文件,並且生成 findViewById 相關的代碼

ExampleActivity$ViewBinder.java


public void bind(ExampleActivity activity) {
  activity.subtitle = (android.widget.TextView) activity.findViewById(2130968578);
  activity.footer = (android.widget.TextView) activity.findViewById(2130968579);
  activity.title = (android.widget.TextView) activity.findViewById(2130968577);
}

那麼 BBKnife 庫要做的事情就很清楚了,生成輔助類。

動手開寫

步驟:

創建注解 編譯期間處理注解 生成輔助類 調用輔助類

0x00 創建工程

首先創建一個新的工程

\

然後創建一個 module , 選擇 java Library。

0x01 創建注解

這個注解作用於類的屬性上面,包含一個整型的參數,類似於 @BindView(R.id.title)

/**
 * Created by hanks on 2016/7/31.
 */
@Documented
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface BindView {
    int value();
}

0x02 編譯期間處理注解 && 生成輔助類

/**
 * 編譯期間處理注解
 * Created by hanks on 2016/7/31.
 */
@SupportedAnnotationTypes("xyz.hanks.BindView")
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class BindViewProcessor extends AbstractProcessor {
    private Messager messager;
    public static final String SUFFIX = "$ViewBinder";

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        messager = processingEnv.getMessager();
    }

    @Override
    public boolean process(Set annotations, RoundEnvironment roundEnv) {
        Map> map = new HashMap<>(); // key 是類名,value 是該類的注解元素
        // 遍歷 BindView 注解的所有元素
        for (Element element : roundEnv.getElementsAnnotatedWith(BindView.class)) {
            if (element == null || !(element instanceof VariableElement)) {
                continue;
            }
            // 給屬性添加的注解
            VariableElement variableElement = (VariableElement) element;
            // 獲取屬性所在的類名
            String className = element.getEnclosingElement().getSimpleName().toString();
            List variableElementList = map.get(className);
            if (variableElementList == null) {
                variableElementList = new ArrayList<>();
                map.put(className, variableElementList);
            }
            variableElementList.add(variableElement);
        }

        // 生成輔助類
        generate(map);
        return true;
    }

    private void generate(Map> map) {
        if (null == map || map.size() == 0) {
            return;
        }
        for (String className : map.keySet()) {
            List variableElementList = map.get(className);
            if (variableElementList == null || variableElementList.size() <= 0) {
                continue;
            }
            // 獲取包名
            String packageName = variableElementList.get(0).getEnclosingElement().getEnclosingElement().toString();
            StringBuilder builder = new StringBuilder()
                    .append("package ").append(packageName).append(";\n\n")
                    .append("public class ").append(className).append(SUFFIX).append("{\n") // open class
                    .append("    public void bind(Object target) {\n")
                    .append("        ").append(className).append(" activity = (").append(className).append(")target;\n");

            for (VariableElement variableElement : variableElementList) {
                BindView bindView = variableElement.getAnnotation(BindView.class);
                log(bindView.toString());
                builder.append("        activity.").append(variableElement.getSimpleName().toString()).append("=(").append(variableElement.asType()).append(")activity.findViewById(").append(bindView.value()).append(");\n");
            }
            builder.append("    }\n}\n");
            // write the file
            try {
                String bindViewClassName = packageName + "." + className + SUFFIX;
                JavaFileObject source = processingEnv.getFiler().createSourceFile(bindViewClassName);
                Writer writer = source.openWriter();
                writer.write(builder.toString());
                writer.flush();
                writer.close();
            } catch (IOException e) {
                log(e.getMessage());
            }
        }
    }

    private void log(String msg) {
        messager.printMessage(Diagnostic.Kind.WARNING, msg);
    }

}

注意點:
- 添加 @SupportedAnnotationTypes @SupportedSourceVersion 注解, 原因: AbstractProcessor 中做了相關校驗(看 AbstractProcessor 源碼)。
- 打印消息是由 processingEnv.getMessager().printMessage 或者輸出日志文件,原因:編譯期間做的處理,不能使用 System.out 或者 Log.i
- 處理注解的時候需要獲取類名或者包名,需要注意獲取的是全路徑還是簡單名稱。
- 依照需要生成輔助類文件。

在 main 下新建 resources > META_INF > services 目錄,創建 javax.annotation.processing.Processor 文件,javac 會自動檢查和讀取 javax.annotation.processing.Processor 中的內容,並且注冊 BindViewProcessor 作為注解處理器。
\

0x03 調用輔助類

當執行 BBKnife.bind(activity) 的時候調用我們生成的輔助類,輔助類內部進行 findViewById 從而進行注入。

public class BBKnife {
    // 調用我們生成的輔助類
    public static void bind(Object view){
        try {
            String cla = view.getClass().getName()+BindViewProcessor.SUFFIX;
            Class clazz = Class.forName(cla);
            Object instance = clazz.newInstance();
            Method bind = clazz.getMethod("bind",Object.class);
            bind.invoke(instance,view);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

導出 bbknife.jar

在 build/libs 目錄下有自動導出的 jar 文件,

\<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxoMiBpZD0="使用">使用

復制到 app 下的 libs 進行引用。

\

app 下的 build.gradle

apply plugin: 'com.android.application'
apply plugin: 'com.neenbedankt.android-apt' // 使用 apt

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' // 添加引用
    }
}

android {
    compileSdkVersion 24
    buildToolsVersion "24.0.1"

    defaultConfig {
        applicationId "xyz.hanks.bbknifeproject"
        minSdkVersion 15
        targetSdkVersion 24
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    // 設置java 版本
    compileOptions{
        sourceCompatibility JavaVersion.VERSION_1_7
        targetCompatibility JavaVersion.VERSION_1_7
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:24.1.1'
}

在 MainActivity 使用


public class MainActivity extends AppCompatActivity {

    @BindView(R.id.bb_button) Button mButton;
    @BindView(R.id.bb_image)  ImageView mImage;
    @BindView(R.id.bb_text)   TextView mText;

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

        BBKnife.bind(this);

        mButton.setText("hanks.xyz");
        mText.setText("hanks.xyz");
        mImage.setImageResource(R.mipmap.ic_launcher);
    }
}

看最後生成的輔助類 MainActivity$ViewBinder.java


public class MainActivity$ViewBinder{
    public void bind(Object target) {
        MainActivity activity = (MainActivity)target;
        activity.mButton=(android.widget.Button)activity.findViewById(2131427412);
        activity.mImage=(android.widget.ImageView)activity.findViewById(2131427414);
        activity.mText=(android.widget.TextView)activity.findViewById(2131427413);
    }
}

\

 

 
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved