我们日常使用的很多开源库都有注解的实现,主要有运行时注解和编译时注解两种。
运行时注解:主要作用就是得到注解的信息
Retrofit:
调用
@GET("/users/{username}")
User getUser(@Path("username") String username)
定义
@Documented
@Target(METHOD)
@Retention(RUNTIME)
@RestMethod("GET")
public @interface GET {
String value()
}
编译时注解:主要作用动态生成代码
Butter Knife
调用
@InjectView(R.id.user)
EditText username
定义
@Retention(CLASS)
@Target(FIELD)
public @interface InjectView {
int value()
}
2、编译时注解
要实现编译时注解需要3步:
1、定义注解(关于注解的定义可以参考下面引用的博客)
import java.lang.annotation.Retention
import java.lang.annotation.Target
import java.lang.annotation.RetentionPolicy
import java.lang.annotation.ElementType
@Target({ ElementType.FIELD, ElementType.TYPE })
@Retention(RetentionPolicy.CLASS)
public @interface Seriable
{
}
2、编写注解解析器
@SupportedAnnotationTypes("annotation.Seriable")
@SupportedSourceVersion(SourceVersion.RELEASE_6)
public class ViewInjectProcessor extends AbstractProcessor {
@Override
public boolean process(Set annotations,
RoundEnvironment roundEnv) {
for (Element ele : roundEnv.getElementsAnnotatedWith(InjectView.class)) {
if(ele.getKind() == ElementKind.FIELD){
//todo
}
return true
}
@SupportedAnnotationTypes,定义要燃并支持注解的完整路径,也可以通过getSupportedAnnotationTypes方法来定义
@Override
public Set getSupportedAnnotationTypes() {
Set types = new LinkedHashSet<>()
types.add(InjectView.class.getCanonicalName())
return types
}
@SupportedSourceVersion(SourceVersion.RELEASE_6)表示支持的jdk的版本
3、创建制定文猜段液件resources/META-INF/services/javax.annotation.processing.Processor,并填写注解解析器的类路径,这样在编译的时穗物候就能自动找到解析器
看上去实现编译时注解还是很容易的,但是真要完整的实现一个类似Butter Knife的框架,这还只是开始。
Butter Knife是专注View的注入,在使用注解的类编译后,查看编译后的class文件,会发现多出了文件,如:
SimpleActivity$$ViewInjector.java
SimpleActivity$$ViewInjector.java,就是通过编译时注解动态创建出来的,查看SimpleActivity$$ViewInjector.java的内容
// Generated code from Butter Knife. Do not modify!
package com.example.butterknife
import android.view.View
import butterknife.ButterKnife.Finder
public class SimpleActivity$$ViewInjector {
public static void inject(Finder finder, final com.example.butterknife.SimpleActivity target, Object source) {
View view
view = finder.findRequiredView(source, 2131230759, "field 'title'")
target.title = (android.widget.TextView) view
view = finder.findRequiredView(source, 2131230783, "field 'subtitle'")
target.subtitle = (android.widget.TextView) view
view = finder.findRequiredView(source, 2131230784, "field 'hello', method 'sayHello', and method 'sayGetOffMe'")
target.hello = (android.widget.Button) view
view.setOnClickListener(
new butterknife.internal.DebouncingOnClickListener() {
@Override
public void doClick(
android.view.View p0
) {
target.sayHello()
}
})
view.setOnLongClickListener(
new android.view.View.OnLongClickListener() {
@Override
public boolean onLongClick(
android.view.View p0
) {
return target.sayGetOffMe()
}
})
view = finder.findRequiredView(source, 2131230785, "field 'listOfThings' and method 'onItemClick'")
target.listOfThings = (android.widget.ListView) view
((android.widget.AdapterView) view).setOnItemClickListener(
new android.widget.AdapterView.OnItemClickListener() {
@Override
public void onItemClick(
android.widget.AdapterView p0,
android.view.View p1,
int p2,
long p3
) {
target.onItemClick(p2)
}
})
view = finder.findRequiredView(source, 2131230786, "field 'footer'")
target.footer = (android.widget.TextView) view
}
public static void reset(com.example.butterknife.SimpleActivity target) {
target.title = null
target.subtitle = null
target.hello = null
target.listOfThings = null
target.footer = null
}
}
inject方法进行初始化,reset进行释放。inject都是调用Finder的方法与android系统的findViewById等方法很像,再来看Finder类,只截取部分。
public enum Finder {
public T findRequiredView(Object source, int id, String who) {
T view = findOptionalView(source, id, who)
if (view == null) {
String name = getResourceEntryName(source, id)
throw new IllegalStateException("Required view '"
+ name
+ "' with ID "
+ id
+ " for "
+ who
+ " was not found. If this view is optional add '@Nullable' (fields) or '@Optional'"
+ " (methods) annotation.")
}
return view
}
public T findOptionalView(Object source, int id, String who) {
View view = findView(source, id)
return castView(view, id, who)
}
@Override protected View findView(Object source, int id) {
return ((View) source).findViewById(id)
}
@SuppressWarnings("unchecked") // That's the point.
public T castView(View view, int id, String who) {
try {
return (T) view
} catch (ClassCastException e) {
if (who == null) {
throw new AssertionError()
}
String name = getResourceEntryName(view, id)
throw new IllegalStateException("View '"
+ name
+ "' with ID "
+ id
+ " for "
+ who
+ " was of the wrong type. See cause for more info.", e)
}
}
findRequiredView方法实际上就是我们常用findViewById的实现,其动态帮我们添加了这些实现。view注入的原理我们就清楚了。
虽然编译时创建了这个类,运行的时候如何使用这个类呢,这里我用自己实现的一个类来描述
public class XlViewInjector {
static final Map, AbstractInjector>INJECTORS = new LinkedHashMap, AbstractInjector>()
public static void inject(Activity activity){
AbstractInjector injector = findInjector(activity)
injector.inject(Finder.ACTIVITY, activity, activity)
}
public static void inject(Object target, View view){
AbstractInjector injector = findInjector(target)
injector.inject(Finder.VIEW, target, view)
}
private static AbstractInjector findInjector(Object target){
Class clazz = target.getClass()
AbstractInjector injector = INJECTORS.get(clazz)
if(injector == null){
try{
Class injectorClazz = Class.forName(clazz.getName()+"$$"+ProxyInfo.PROXY)
injector = (AbstractInjector) injectorClazz.newInstance()
INJECTORS.put(clazz, injector)
}catch(Exception e){
e.printStackTrace()
}
}
return injector
}
}
XlViewInjector与ButterKnife,比如调用时我们都会执行XlViewInjector.inject方法,通过传入目标类的名称获得封装后的类实例就是SimpleActivity$$ViewInjector.java,再调用它的inject,来初始化各个view。
总结一下整个实现的流程:
1、通过编译时注解动态创建了一个包装类,在这个类中已解析了注解,实现了获取view、设置监听等代码。
2、执行时调用XlViewInjector.inject(object)方法,实例化object类对应的包装类,并执行他的初始化方法inject;
因此我们也能明白为什么XlViewInjector.inject(object)方法一定要在setContentView之后执行。
3、实现注解框架时的坑
解析有用到android api,因此需要创建Android工程,但是android library并没有javax的一些功能,
在eclipse环境下,右键build-path add library把jdk加进来
在android studio下,需要先创建java Library功能,实现与view无关的解析,再创建一个android library功能引用这个工程并实现余下的解析。
返回上一个activity只需要调用 finish() 方法(前歼闷提是你跳转的时候没有关闭上个activity)
当然,这洞唤也必须要通过点击事件来实现
1.写个BaseActivity实现 OnClickListenerpublic abstract class BaseActivity extends AppCompatActivity implements OnClickListener{} 这样每个继承它的activity都会自动重写
@Override
public void onClick(View view) {
switch(view.getid){
}
}
给每一个需要点击事件的view 氏颤弯写个监听 view.setOnClickListener(this)
2.使用框架 比如xUtil等 不推荐
3.使用butterknife
4.使用rxjava实现的rxbus
5.使用EventBus 不推荐
ADB Idea ADB *** 作快捷菜单,快速清除数据没旁,重启应用,卸载应用等 *** 作Android
Annotations 配合Annotation框架快速生成控件绑定代码,同Android ButterKnife Zeleny
,本来都是用ButterKnife,生成快速绑定代码,后面开发拍察禅的都是Lib库没办法使用ButterKnife只好转Annotations库
Android Drawable Importer 做些小应用时妈妈再也不用担心我找不到图标了~需要配合google/material-design-icons · GitHub、Android Icons使用,安装后在设置菜单多出来的选项中设置
Android Parcelable Code Generator 如标题。快速生成Parcelable代码
Android
Accessors
按照Google官方的开发规范,类的成员变量以m开头,此插件可快速生成成员变量的set/get方法但是不带m,评论中有人指出可以通过再
Settings-CodeStyle-Java-CodeGeneration中修改前缀,经查验却是更加方便。
SelectorChapek
for Android
插件库好多生成Selector的插件,还是这个用的最顺,按照不同状态(normal、pressed)的标准命名后,右键文件树Generate
Android Selectors见inmite/android-selector-chapek · GitHub
IntelliJ IDea通用
AceJump 快速跳转到屏幕某个位置(Ctrl+)
FindBugs-IDEA、CheckStyle-IDEA 用自动化代替双眼
JavaDoc 添加注释,可自定义模板
Eclipse Code Formmater 项目之前袭尘未全部转向Studio,该插件兼容Eclipse的代码格式化风格
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)