开发自己的山寨Android注解框架
开发自己的山寨Android注解框架
参考
Overview
在上一章我们学习了Java的注解(Annotation),但是我想大家可能感觉,虽然理解了也会学会,但是不知道干什么用,那么请继续忍受我这枯燥乏味的文风继续向下看吧。
在下面我们将会模仿(山寨)一把 黄油刀 。
第零步
我想许多 Android Coder 都非常讨厌findViewById
这种操作,既乏味无趣有没有代码的优雅感,实在是让人厌恶至极。 我们著名的黄油刀框架就是为了解决这一问题的。
优雅的黄油刀
class ExampleActivity extends Activity {
//通过注解找到我们想要的控件
@BindView(R.id.user) EditText username;
@BindView(R.id.pass) EditText password;
@BindString(R.string.login_error) String loginErrorMessage;
//绑定OnClick事件
@OnClick(R.id.submit) void submit() {
// TODO call server...
}
@Override public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.simple_activity);
ButterKnife.bind(this);
// TODO Use fields...
}
}
优雅的代码扑面而来~
我们就是准备模仿这么一个东西,当然我们要做出来的东西,肯定远不及人家,可能就有人会说,既然已经有了这么好的框架,为什么我们还有做一些重复造轮子的工作呢?
对此我想说,我们不能做一个只会用别人的轮子的开发者,总归我们是要能够造出来自己的轮子的。
第一步,我们要实现的东西
我们在这里要实现两个功能
- 通过注解,来替换掉,以前的findViewByid的操作
- 通过注解,来完成绑定OnCLick事件的操作。
编程思路
实现该功能的思路如下:
- 在Activity/Fragment初始化的时候,进行绑定操作
- 绑定操作后,通过遍历所有的字段和方法
- 遍历字段,找出所有的被注解标记的字段
- 根据注解中的值,找到相应的View,然后赋值给字段
- 遍历所有的方法,找出被注解的方法
- 通过注解的值找到对应的View
- 为找到的View注册事件
- 回收占用的资源
第二步,做准备工作
建立项目
首先建立一个项目,我叫他为Finder
,在项目中建立一个FinderLibrary
Module, 我们会在这个Model中写代码。
创建我们需要的注解
@BindView 注解,用于绑定视图
/**
* 用于绑定视图的注解
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface BindView {
int value();
}
@ClickEvent注解,用于注册OnClick事件
@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ClickEvent {
int[] value();
}
主体代码
Finder类的主要方法
建立一个Find类来写我们的核心代码。下面的代码中列出了finder类中的主要的方法,后面我们会一步步地实现他们。
public final class Finder {
/**
* 私有化构造方法,禁止从外部创建对象
*/
private Finder() throws Exception {
throw new Exception("Invalidate constructor");
}
/**
* 用于存储所有的已经绑定的对象的集合
*/
static Map<Unbinder, View> Pot = new HashMap<>();
/**
* 用于绑定活动
*/
public static Unbinder bind(@NonNull Activity target) {}
/**
* 用于绑定碎片
*/
public static Unbinder bind(Fragment target) {}
private static void apply(Unbinder unbinder) {}
/**
* 绑定View,为标记的字段
*/
private static void bindView(Object holder, View view) throws Exception {}
/**
* 绑定OnClick方法
*/
private static void bindMethod(Object holder, View view) throws Exception {}
/**
* 为View绑定ONClick事件
*/
private static void bindOnClickListener
(final Object holder, View view, final Method method) {}
/**
* 从集合释放掉占用的对象
*/
public static void unbind(Unbinder unbinder) {}
}
Unbinder类
这个类的作用很简单,当我们在Activity/Fragment中绑定了Finder以后,会返回一个此对象,那么当Activity/Fragment 生命周期结束的时候,通过此对象释放掉Finder所占用的资源。
package little_david.finderlibrary;
public class Unbinder {
public Unbinder(Object c) {
this.holder = c;
}
/**
* 当前Unbinde对象的持有者
*/
Object holder;
/**
* 解绑操作
*/
public void unbind() {
Finder.unbind(this);
}
}
bind方法
在此方法中我们需要获取Activity/Fragment的View,通过调用apply
方法,并将其存储起来。
/**
* 用于绑定活动
*/
public static Unbinder bind(@NonNull Activity target) {
Unbinder unbinder = new Unbinder(target);
//获取Activity的顶级布局,并存储
Pot.put(unbinder, target.getWindow().getDecorView());
apply(unbinder);
return unbinder;
}
/**
* 用于绑定碎片
*/
public static Unbinder bind(Fragment target) {
Unbinder unbinder = new Unbinder(target);
//获取Fragment的顶级布局,并存储
Pot.put(unbinder, target.getView());
apply(unbinder);
return unbinder;
}
apply方法
通过此方法来调用,最主要的两个核心方法。这个方法并没有什么特殊,只是起到了一个过渡的作用。
private static void apply(Unbinder unbinder) {
try {
//视图对象
View view = Pot.get(unbinder);
bindView(unbinder.holder, view);
bindMethod(unbinder.holder, view);
} catch (Exception e) {
e.printStackTrace();
}
}
bindView 方法
在此方法中做的事情是,遍历被注解了的字段,然后根据注解中的值,进行反射赋值。
/**
* 绑定View,为标记的字段
*/
private static void bindView(Object holder, View view) throws Exception {
//拿到持有者的Class
Class cls = holder.getClass();
//获取到所有的字段
Field[] fields = cls.getFields();
//遍历字段
for (Field field : fields) {
//过滤没有被@BindView标记的字段
if (!field.isAnnotationPresent(BindView.class))
continue;
//获取我们注解的详细的对象,并赋值
BindView bindView = field.getAnnotation(BindView.class);
int viewResId = bindView.value();
View targetView = view.findViewById(viewResId);
//为字段赋值
field.set(holder, targetView);
}
}
bindMethod
在此方法中遍历所有的方法,找出被注解标识的方法并且满足我们要求的方法,作为View的OnClick事件的处理方法。
/**
* 绑定OnClick方法
*/
private static void bindMethod(Object holder, View view) throws Exception {
Class cls = holder.getClass();
Method[] methods = cls.getMethods();
for (Method method : methods) {
//过滤没有被注解的方法
if (!method.isAnnotationPresent(ClickEvent.class))
continue;
//获取方法参数
Class[] parameterClsArray = method.getParameterTypes();
//根据方法参数过滤,过滤被注解了但是不合法的方法
if (parameterClsArray.length != 1 || parameterClsArray[0] != View.class)
continue;
//绑定点击事件
ClickEvent event = method.getAnnotation(ClickEvent.class);
/**
* 因为@ClickEvent注解是支持多选的,所以我们需要遍历所有的值来进行设置OnClick事件操作
* */
for (int resId : event.value()) {
View eventView = view.findViewById(resId);
bindOnClickListener(holder, eventView, method);
}
}
}
/**
* 为View绑定ONClick事件
*/
private static void bindOnClickListener(final Object holder, View view, final Method method) {
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
try {
//执行方法
method.invoke(holder, v);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
});
}
unbind
此方法的左右就是释放掉所占用的资源。
/**
* 从集合释放掉占用的对象
*/
public static void unbind(Unbinder unbinder) {
unbinder.holder = null;
Pot.remove(unbinder);
}
第三步, 实验我们自己框架
应用于Activity
public class MainActivity extends AppCompatActivity {
@BindView(R.id.btn1)
Button btn1;
@BindView(R.id.btn2)
Button btn2;
private Unbinder mUnbinder;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//进行绑定操作
mUnbinder = Finder.bind(this);
btn1.setText("Hello world!");
}
/**
* 可以绑定多个View
*/
@ClickEvent({R.id.btn1, R.id.btn2})
public void btn1Click(View view) {
Toast.makeText(this, "My id is: " + view.getId(), Toast.LENGTH_SHORT).show();
}
/**
* 进行解绑操作,释放资源
*/
@Override
protected void onDestroy() {
mUnbinder.unbind();
super.onDestroy();
}
}
应用于Fragment
public class TestFragment extends Fragment {
private Unbinder mUnbinder;
@BindView(R.id.btn3)
Button btn3;
@BindView(R.id.tvTest)
TextView tvTest;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_test, container, false);
}
/**
* 绑定
*/
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mUnbinder = Finder.bind(this);
}
/**
* 释放资源
*/
@Override
public void onDestroyView() {
mUnbinder.unbind();
super.onDestroyView();
}
@ClickEvent(R.id.btn3)
public void btn3Click(View view) {
Toast.makeText(getContext(), "Btn3.text=" + this.btn3.getText().toString(), Toast.LENGTH_SHORT).show();
}
}
源码下载
虽然,我们做的这个框架现在还漏洞百出,但是,我们已经跨出了我们造轮子的第一步,总有一天我们也会能够写出非常好的轮子让别人用。
好了,现在本节的内容已经结束了,我想还是比较容易理解的,毕竟没什么难度。源码已经上传至github,欢迎大家吐槽。
https://github.com/1258730808/Finder
开发自己的山寨Android注解框架的更多相关文章
- Android注解框架实战-ButterKnife
文章大纲 Android注解框架介绍 ButterKnife实战 项目源码下载 一.框架介绍 为什么要用注解框架? 在Android开发过程中,我们经常性地需要操作组件,操作方法有findVie ...
- Android 注解框架对比
Java的注解(Annotation)相当于一种标记,在程序中加入注解就等于为程序打上某种标记,标记可以加在包,类,属性,方法,本地变量上.然后你可以写一个注解处理器去解析处理这些注解(人称编译时注解 ...
- 淘宝(阿里百川)手机客户端开发日记第一篇 android 主框架搭建(一)
android 主框架搭建(一) 1.开发环境:Android Studio 相继点击下一步,直接项目建立完毕(如下图) 图片看的效果如果很小,请放大您的浏览器显示百分比 转载请注明http://w ...
- Android 开发快速导引:Android程序框架【草】
概述 学习一项新技术之前要先了解这个技术的整体框架,这里先简单说一下 Android 的程序结构. Android App 有四个顶层的类:Activity.Service.ContentProvid ...
- java注解框架
我们经常会在java代码里面看到:“@Override”,“@Target”等等样子的东西,这些是什么? 在java里面它们是“注解”. 下面是百度百科的解释:java.lang.annotation ...
- android注解使用详解(图文)
在使用Java的SSH框架的时候,一直在感叹注解真是方便啊,关于注解的原理,大家可以参考我的另一片文章Java注解详解.最近有时间研究了android注解的使用,今天与大家分享一下. android中 ...
- android注解使用具体解释(图文)
在使用Java的SSH框架的时候,一直在感叹注解真是方便啊,关于注解的原理,大家能够參考我的还有一片文章Java注解具体解释. 近期有时间研究了android注解的使用,今天与大家分享一下. andr ...
- Android注解使用之通过annotationProcessor注解生成代码实现自己的ButterKnife框架
前言: Annotation注解在Android的开发中的使用越来越普遍,例如EventBus.ButterKnife.Dagger2等,之前使用注解的时候需要利用反射机制势必影响到运行效率及性能,直 ...
- Android ButterKnife注解框架使用
这段时间学习了下ButterKnife注解框架,学习的不是特别深入,但是基础也差不多了,在此记录总结一下. ButterKnife是一个Android View注入的库,主要是注解的使用,可以减少很多 ...
随机推荐
- C#处理MySql多个返回集的方法
本文实例讲述了C#处理MySql多个返回集的方法.分享给大家供大家参考.具体方法如下: 关于Mysql返回多个集java和Php的较多,但是C#的完整代码好像没见过,研究了一下做个封装以后用 做一个M ...
- python 基础数据类型之str
1.字符串去除空格 # S.strip(self, chars=None) #去除字符串两端空格# S.lstrip(self, chars=None) #去除字符串左端空格# S.rstrip(se ...
- 风控模型-美国FICO标准
python信用评分卡(附代码,博主录制) https://study.163.com/course/introduction.htm?courseId=1005214003&utm_camp ...
- python爬虫 抓取一个网站的所有网址链接
sklearn实战-乳腺癌细胞数据挖掘 https://study.163.com/course/introduction.htm?courseId=1005269003&utm_campai ...
- oracle按照in的顺序进行排序
oracle按照in的顺序进行排序 ,,) order by case id end;
- element-ui 设置input的只读或禁用
只读:readonly 在data里定义:readonly: true, 然后在input框里加上readonly就可以了. 禁用:disabled 在data里定义:edit: true, 然后在i ...
- node.js+express,实现RESTful API
node代码如下(exptest.js): var express = require('express'); var bodyParser = require('body-parser'); var ...
- bzoj千题计划254:bzoj2286: [Sdoi2011]消耗战
http://www.lydsy.com/JudgeOnline/problem.php?id=2286 虚树上树形DP #include<cmath> #include<cstdi ...
- 一个中国地图的SVG,可以带参数
<script src="http://files.cnblogs.com/files/LoveOrHate/jquery.min.js"></script> ...
- [整]Android开发优化-布局优化
优化布局层次结构 一个普遍的误解就是,使用基本的布局结构会产生高效的布局性能.然而每一个添加到应用的控件和布局,都需要初始化,布局位置和绘制.比如,使用一个嵌套的LinearLayout会导致过深的布 ...