編輯:關於Android編程
Android注解越來越引領潮流,比如 Dagger2, ButterKnife, EventBus3 等,他們都是注解類型,而且他們都有個共同點就是編譯時生成代碼,而不是運行時利用反射,這樣大大優化了性能;而這些框架都用到了同一個工具就是:APT(Annotation Processing Tool ),可以在代碼編譯期解析注解,並且生成新的 Java 文件,減少手動的代碼輸入。
今天我們要自己實現的就是類似ButterKnife的簡單的view初始化和點擊事件;
先看下整個項目的目錄結構:

先從最簡單入手,注解moudle:
1.創建名字為viewinject-annotation的java類型module
2.該module只有兩個類:
1.BindView用來對成員變量進行注解,並且接收一個 int 類型的參數
* Created by JokAr on 16/8/6.
*/
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface BindView {
int value();
}
2.OnClick對方法進行注解,接收一個或一組 int 類型參數,相當於給一組 View 指定點擊響應事件。
/**
* Created by JokAr on 16/8/6.
*/
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.METHOD)
public @interface OnClick {
int[] value();
}
注解module就完成了,下面看看API module
1.首先創建一個Android moudle 的inject,然後創建interface
/** * Created by JokAr on 16/8/6. */ public interface Inject{ void inject(T host, Object object, Provider provider); }
/**
* Created by JokAr on 16/8/6.
*/
public interface Provider {
Context getContext(Object object);
View findView(Object object, int id);
}
因為我們需要生成的文件是這麼寫的:
public class MainActivity$$ViewInject implements Inject{ @Override public void inject(final MainActivity host, Object source, Provider provider) { host.textView = (TextView)(provider.findView(source, 2131427412)); host.button1 = (Button)(provider.findView(source, 2131427413)); View.OnClickListener listener = new View.OnClickListener() { @Override public void onClick(View view) { host.click(); } } ; provider.findView(source, 2131427412).setOnClickListener(listener); } }
當然這個生成文件是根據自己需求生成,然後需要一個類來關聯自己的activity類與生成的類:
/**
* Created by JokAr on 16/8/6.
*/
public class ViewInject {
private static final ActivityProvider activityProvider = new ActivityProvider();
private static final ViewProvider viewProvider = new ViewProvider();
private static final ArrayMap injectMap = new ArrayMap<>();
public static void inject(Activity activity) {
inject(activity, activity, activityProvider);
}
public static void inject(View view) {
inject(view, view);
}
private static void inject(Object host, View view) {
inject(host, view, viewProvider);
}
private static void inject(Object host, Object object, Provider provider) {
String className = host.getClass().getName();
try {
Inject inject = injectMap.get(className);
if (inject == null) {
Class aClass = Class.forName(className + "$$ViewInject");
inject = (Inject) aClass.newInstance();
injectMap.put(className, inject);
}
inject.inject(host, object, provider);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
使用方法就是:
ViewInject.inject(this);host 表示注解 View 變量所在的類,也就是注解類 object 表示查找 View 的地方,Activity & View 自身就可以查找,Fragment 需要在自己的 itemView 中查找 provider 是一個接口,定義了不同對象(比如 Activity、View 等)如何去查找目標 View,項目中分別為 Activity、View 實現了 Provider 接口(具體實現參考項目代碼) 為了提高效率,避免每次注入的時候都去找 Inject 對象,用一個 Map 將第一次找到的對象緩存起來,後面用的時候直接從 Map 裡面取。
API module類就完成了
再看viewinject-compilermodule:
首先創建名為iewinject-compiler的Java module ,然後在該module的buile.gradle加上一些依賴:
compile project(':viewinject-annotation')
compile 'com.squareup:javapoet:1.7.0'
compile 'com.google.auto.service:auto-service:1.0-rc2'
Javapoet是square一個工具,提供了各種 API 讓你用各種姿勢去生成 Java 代碼文件,避免了徒手拼接字符串的尴尬。 auto-service 主要用於注解 Processor,對其生成 META-INF 配置信息。
首先創建ViewInjectProcesser類:
/**
* Created by JokAr on 16/8/8.
*/
@AutoService(Processor.class)
public class ViewInjectProcesser extends AbstractProcessor {
private Filer mFiler; //文件相關的輔助類
private Elements mElementUtils; //元素相關的輔助類
private Messager mMessager; //日志相關的輔助類
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
mFiler = processingEnv.getFiler();
mElementUtils = processingEnv.getElementUtils();
mMessager = processingEnv.getMessager();
mAnnotatedClassMap = new TreeMap<>();
}
@Override
public boolean process(Set annotations, RoundEnvironment roundEnv) {
return false;
}
/**
* 指定使用的 Java 版本。通常返回SourceVersion.latestSupported()。
* @return
*/
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
/**
* 指定哪些注解應該被注解處理器注冊
* @return
*/
@Override
public Set getSupportedAnnotationTypes() {
Set types = new LinkedHashSet<>();
types.add(BindView.class.getCanonicalName());
types.add(OnClick.class.getCanonicalName());
return types;
}
}
用 @AutoService 來注解這個處理器,可以自動生成配置信息。 在 init() 可以初始化拿到一些實用的工具類。
這個類的的基本內容就完成了,
現在創建BindViewField類,來解析BindView注解類來獲取用該注解的相關信息
/**
* Created by JokAr on 16/8/8.
*/
public class BindViewField {
private VariableElement mVariableElement;
private int mresId;
public BindViewField(Element element) throws IllegalArgumentException{
if (element.getKind() != ElementKind.FIELD) {
throw new IllegalArgumentException(String.format("Only fields can be annotated with @%s",
BindView.class.getSimpleName()));
}
mVariableElement = (VariableElement) element;
BindView bindView = mVariableElement.getAnnotation(BindView.class);
mresId = bindView.value();
if (mresId < 0) {
throw new IllegalArgumentException(
String.format("value() in %s for field %s is not valid !", BindView.class.getSimpleName(),
mVariableElement.getSimpleName()));
}
}
/**
* 獲取變量名稱
* @return
*/
public Name getFieldName() {
return mVariableElement.getSimpleName();
}
/**
* 獲取變量id
* @return
*/
public int getResId() {
return mresId;
}
/**
* 獲取變量類型
* @return
*/
public TypeMirror getFieldType() {
return mVariableElement.asType();
}
}
創建OnClickMethod類來解析使用OnClick注解的方法,獲取相關信息
public class OnClickMethod {
private ExecutableElement mExecutableElement;
private int[] resIds;
private Name mMethodName;
public OnClickMethod(Element element) throws IllegalArgumentException {
if (element.getKind() != ElementKind.METHOD) {
throw new IllegalArgumentException(
String.format("Only methods can be annotated with @%s",
OnClick.class.getSimpleName()));
}
mExecutableElement = (ExecutableElement) element;
resIds = mExecutableElement.getAnnotation(OnClick.class).value();
if (resIds == null) {
throw new IllegalArgumentException(String.format("Must set valid ids for @%s",
OnClick.class.getSimpleName()));
} else {
for (int id : resIds) {
if (id < 0) {
throw new IllegalArgumentException(String.format("Must set valid id for @%s",
OnClick.class.getSimpleName()));
}
}
}
mMethodName = mExecutableElement.getSimpleName();
List parameters = mExecutableElement.getParameters();
if (parameters.size() > 0) {
throw new IllegalArgumentException(
String.format("The method annotated with @%s must have no parameters",
OnClick.class.getSimpleName()));
}
}
/**
* 獲取方法名稱
* @return
*/
public Name getMethodName() {
return mMethodName;
}
/**
* 獲取id數組
* @return
*/
public int[] getResIds() {
return resIds;
}
}
然後重點就是生成Java代碼文件的類:
/**
* Created by JokAr on 16/8/8.
*/
public class AnnotatedClass {
private TypeElement mTypeElement;
private ArrayList mFields;
private ArrayList mMethods;
private Elements mElements;
public AnnotatedClass(TypeElement typeElement, Elements elements) {
mTypeElement = typeElement;
mElements = elements;
mFields = new ArrayList<>();
mMethods = new ArrayList<>();
}
public String getFullClassName() {
return mTypeElement.getQualifiedName().toString();
}
public void addField(BindViewField field) {
mFields.add(field);
}
public void addMethod(OnClickMethod method) {
mMethods.add(method);
}
public JavaFile generateFile() {
//generateMethod
MethodSpec.Builder injectMethod = MethodSpec.methodBuilder("inject")
.addModifiers(Modifier.PUBLIC)
.addAnnotation(Override.class)
.addParameter(TypeName.get(mTypeElement.asType()), "host", Modifier.FINAL)
.addParameter(TypeName.OBJECT, "source")
.addParameter(TypeUtil.PROVIDER,"provider");
for(BindViewField field : mFields){
// find views
injectMethod.addStatement("host.$N = ($T)(provider.findView(source, $L))",
field.getFieldName(),
ClassName.get(field.getFieldType()), field.getResId());
}
for(OnClickMethod method :mMethods){
TypeSpec listener = TypeSpec.anonymousClassBuilder("")
.addSuperinterface(TypeUtil.ANDROID_ON_CLICK_LISTENER)
.addMethod(MethodSpec.methodBuilder("onClick")
.addAnnotation(Override.class)
.addModifiers(Modifier.PUBLIC)
.returns(TypeName.VOID)
.addParameter(TypeUtil.ANDROID_VIEW, "view")
.addStatement("host.$N()", method.getMethodName())
.build())
.build();
injectMethod.addStatement("View.OnClickListener listener = $L ", listener);
for (int id : method.getResIds()) {
// set listeners
injectMethod.addStatement("provider.findView(source, $L).setOnClickListener(listener)", id);
}
}
//generaClass
TypeSpec injectClass = TypeSpec.classBuilder(mTypeElement.getSimpleName() + "$$ViewInject")
.addModifiers(Modifier.PUBLIC)
.addSuperinterface(ParameterizedTypeName.get(TypeUtil.INJET, TypeName.get(mTypeElement.asType())))
.addMethod(injectMethod.build())
.build();
String packgeName = mElements.getPackageOf(mTypeElement).getQualifiedName().toString();
return JavaFile.builder(packgeName, injectClass).build();
}
}
具體的可以看javapoet的API,然後我們需要完善ViewInjectProcesser類,增加:
private MapmAnnotatedClassMap; @Override public boolean process(Set annotations, RoundEnvironment roundEnv) { mAnnotatedClassMap.clear(); try { processBindView(roundEnv); processOnClick(roundEnv); } catch (IllegalArgumentException e) { e.printStackTrace(); error(e.getMessage()); } for (AnnotatedClass annotatedClass : mAnnotatedClassMap.values()) { try { annotatedClass.generateFile().writeTo(mFiler); } catch (IOException e) { error("Generate file failed, reason: %s", e.getMessage()); } } return true; } private void processBindView(RoundEnvironment roundEnv) throws IllegalArgumentException { for (Element element : roundEnv.getElementsAnnotatedWith(BindView.class)) { AnnotatedClass annotatedClass = getAnnotatedClass(element); BindViewField bindViewField = new BindViewField(element); annotatedClass.addField(bindViewField); } } private void processOnClick(RoundEnvironment roundEnv) throws IllegalArgumentException { for (Element element : roundEnv.getElementsAnnotatedWith(OnClick.class)) { AnnotatedClass annotatedClass = getAnnotatedClass(element); OnClickMethod onClickMethod = new OnClickMethod(element); annotatedClass.addMethod(onClickMethod); } } private void error(String msg, Object... args) { mMessager.printMessage(Diagnostic.Kind.ERROR, String.format(msg, args)); }
實際使用
在項目的根目錄的build.gradle添加:
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
在項目的主module的build.gradle添加:
apply plugin: 'com.neenbedankt.android-apt'
compile project(':viewinject-annotation')
compile project(':inject')
apt project(':viewinject-compiler')
在自己的activity類使用:
/**
* Created by JokAr on 16/8/8.
*/
public class MainActivity extends AppCompatActivity {
@BindView(R.id.textView)
TextView textView;
@BindView(R.id.button1)
Button button1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ViewInject.inject(this);
}
@OnClick(R.id.textView)
public void click() {
Toast.makeText(getApplicationContext(), "hello", Toast.LENGTH_SHORT).show();
}
}
點擊makeProject 就編譯完成後就可以在主項目module的/build/generated/source/apt/debug 目錄下看到生成的java類文件了
一個學習級的apt項目就完成了。
項目源碼
android sqlite綁定城市三級聯(動態) 包含mysql數據源的sql語句
package com.icq.spinnercity; import java.util.ArrayList; import java.util.List; imp
Embedded Android 協同翻譯
如果你有一定的Android的基礎和英語基礎, 有願意貢獻開源社區的心, 如果你對以下目錄感興趣, 歡迎加入我們協同翻譯《Embedded Android》 此次協同翻
適配器模式在android中使用
適配器模式(Adapter):適配器模式是一種行為模式,它可以把一個類的接口轉換成為另一個所期待的另一種接口,這樣可以使原本因接口無法在一起工作的兩個類能夠在一起工作了。
Android 屏幕旋轉適配全解析
這篇博文給大家介紹下,當手機屏幕旋轉時我們應當怎麼去處理,首先了解下默認情況下Android進行屏幕旋轉的原理,當手機進行旋轉時重力感應sensor起到作用,會將Acti