当前位置: 首页 > 编程日记 > 正文

开发自己的山寨Android注解框架

目录

  • 开发自己的山寨Android注解框架

开发自己的山寨Android注解框架

参考

Github黄油刀

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...}
}

优雅的代码扑面而来~

我们就是准备模仿这么一个东西,当然我们要做出来的东西,肯定远不及人家,可能就有人会说,既然已经有了这么好的框架,为什么我们还有做一些重复造轮子的工作呢?

对此我想说,我们不能做一个只会用别人的轮子的开发者,总归我们是要能够造出来自己的轮子的。

第一步,我们要实现的东西

我们在这里要实现两个功能

  1. 通过注解,来替换掉,以前的findViewByid的操作
  2. 通过注解,来完成绑定OnCLick事件的操作。
编程思路

实现该功能的思路如下:

  1. 在Activity/Fragment初始化的时候,进行绑定操作
  2. 绑定操作后,通过遍历所有的字段和方法
  3. 遍历字段,找出所有的被注解标记的字段
  4. 根据注解中的值,找到相应的View,然后赋值给字段
  5. 遍历所有的方法,找出被注解的方法
  6. 通过注解的值找到对应的View
  7. 为找到的View注册事件
  8. 回收占用的资源

第二步,做准备工作

建立项目

首先建立一个项目,我叫他为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 {//拿到持有者的ClassClass 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() {@Overridepublic 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;@Overrideprotected 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();}/*** 进行解绑操作,释放资源*/@Overrideprotected 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@Overridepublic View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {return inflater.inflate(R.layout.fragment_test, container, false);}/*** 绑定*/@Overridepublic void onActivityCreated(@Nullable Bundle savedInstanceState) {super.onActivityCreated(savedInstanceState);mUnbinder = Finder.bind(this);}/*** 释放资源*/@Overridepublic 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

转载于:https://www.cnblogs.com/slyfox/p/8677380.html

相关文章:

特斯拉起诉小鹏汽车员工窃取商业机密,何小鹏回应

参加 2019 Python开发者日&#xff0c;请扫码咨询 ↑↑↑整理 | 琥珀出品 | AI科技大本营&#xff08;ID:rgznai100&#xff09;3 月 21 日&#xff0c;外媒 TheVerge 报道称&#xff0c;特斯拉正针对多名前雇员和自动驾驶汽车公司 Zoox 提起诉讼&#xff0c;涉嫌盗用公司商业机…

【Qt】一个使用QEventLoop时,遇到的教训

1、问题描述 伪代码如下&#xff1a; QEventLoop eventLoop; QObject::connect(this, &Class::signal, [](){doSomething();eventLoop.exit(0); }); emit signal(); eventLoop.exec();在执行eventLoop.exec()时&#xff0c;造成永不退出。 2、原因分析 本打算在&#x…

基于kryo序列化方案的memcached-session-manager多memcached...

为什么80%的码农都做不了架构师&#xff1f;>>> 上次基于Java IO的序列化方案配置了memcached-session-manager&#xff0c;但是性能不好&#xff0c;现在先简单配置成基于kryo的 Xml代码 <Context path"/mobilemail" docBase"D:\webapp\WebR…

Python持续点火,跟进还是观望?

参加 2019 Python开发者日&#xff0c;请扫码咨询 ↑↑↑Python 这把火&#xff0c;到底烧了多久了&#xff1f;近日&#xff0c;李笑来带着他的 Python 编程书&#xff0c;一路狂收 Star、Fork&#xff0c;火速登顶 GitHub Trending 榜&#xff0c;直接 C 位出道。币圈大佬也玩…

win2d 图片水印

win2d 图片水印 本文告诉大家如何使用 win2d 给图片加上水印。 安装 首先需要使用 Nuget 安装 win2d &#xff0c;安装参见win10 uwp win2d 如果没有更新 dot net core 那么在运行可能会出现下面异常System.TypeLoadException: Requested Windows Runtime type Microsoft.Graph…

【Qt】QtCreator中的单例编程(以ActionManager为例)

1、ActionManager简介 ActionManager类负责注册菜单和菜单项以及键盘快捷键。 ActionManager的公共函数都是静态的,只有使用信号时,才用到单例。 2、ActionManager单例伪代码 头文件中将构造函数和析构函数设置成私有(private),使用instance()函数获取单例 class Acti…

37、C++ Primer 4th笔记,特殊工具与技术,类成员指针

1、成员指针&#xff08;pointer to member&#xff09;包含类的类型以及成员的类型。成员指针只应用于类的非static成员。static类成员不是任何对象的组成部分&#xff0c;所以不需要特殊语法来指向static成员&#xff0c;static成员指针是普通指针。通过指定函数的返回类型&a…

聊一聊javascript执行上下文

跟大家聊聊js的执行上下文 一&#xff0c;相关概念 EC : 执行上下文 ECS : 执行环境栈 VO &#xff1a; 变量对象 AO &#xff1a; 活动对象 scope chain &#xff1a;作用域链 二&#xff0c;执行上下文 javascript运行的代码环境有三种&#xff1a; 全局代码&#xff1a;代码…

【Qt】QtCreator中使用ActionManager类管理标题栏(MunuBar)、菜单(Menu)和菜单中的项目(Action)

1、简介 QtCreator中使用ActionManager类管理标题栏(MunuBar)、菜单(Menu)和菜单中的项目(Action),下面以创建“New”动作为例,介绍ActionManager的使用方法,后续会详细分析ActionManager。 2、创建标题栏(MunuBar) const char MENU_BAR[] = "QtCreator.MenuBar&quo…

和达摩院深度绑定,阿里云下一个十年,成为“云上的阿里巴巴”

参加 2019 Python开发者日&#xff0c;请扫码咨询 ↑↑↑整理 | 非主流出品 | AI科技大本营&#xff08;ID:rgznai100&#xff09;2009 &#xff5e; 2019&#xff0c;阿里云已经走过了 10 年时光。在此次的阿里云峰会北京站上&#xff0c;为阿里云的站台的是张建锋。去年 11 月…

如何用TF Object Detection API训练交通信号灯检测神经网络?

参加 2019 Python开发者日&#xff0c;请扫码咨询 ↑↑↑作者简介&#xff1a;申泽邦&#xff08;Adam Shan&#xff09;&#xff0c;谷歌认证机器学习专家&#xff08;Google Developer Expert&#xff09;&#xff0c;兰州大学智能驾驶团队技术负责人&#xff0c;硕士在读。文…

iOS progressive Web App (PWA) 技术

随着 iOS 11.3 的发布&#xff0c;iOS PWA 的时代终于来了&#xff01;本文对 iOS 中 PWA 的能力进行了分析&#xff0c;并将其与 iOS 上的 Native App、Android 上的 PWA 进行了深度对比&#xff0c;是值得收藏的一篇好文。 随着 iOS 11.3 的发布&#xff0c;苹果悄悄的支持了…

VS新建类自动添加版本注释

我们开发的时候习惯在代码顶部加上自己的版权说明。 每次拷贝挺麻烦的&#xff0c;上网查了一下&#xff0c;2003的介绍但不少。但08的模板生成方式好像改变了 后来2005的一篇介绍给了我一点提示。原来模板文件放在common7目录下。 主要分为ProjectTemplates和ItemTemplates&am…

【linux】shell中整数运算的加、减、乘、除

1、使用双括号“(( ))” shell中变量实现加法的方法 d $(($d 2)) 例子如下 #!/bin/bash d0 echo "d$d"for((i1;i<10;i)); do d$(($d 2)) echo "d$d" done unset d((i$j$k)) 等价于 iexpr $j $k ((i$j-$k)) 等价于 iexpr $j -$k ((i$…

简单使用PDO

2019独角兽企业重金招聘Python工程师标准>>> 首先基本配置&#xff1a; 连接MYSQL <?php $dsn mysql:hostlocalhost;dbnameworld;; $user user; $password secret; try { $dbh new PDO($dsn, $user, $password); } catch (PDOException $e) { echo Connecti…

一文看懂模糊搜索1.0到3.0的算法迭代历程

参加 2019 Python开发者日&#xff0c;请扫码咨询 ↑↑↑作者 | 宋广泽责编 | 郭芮来源 | CSDN&#xff08;ID&#xff1a;CSDNnews&#xff09;前一段时间在Linux上用C语言做了一个信息管理系统&#xff0c;初始版本的搜索就是直接使用了C语言库文件<string.h>里的库函数…

【linux】shell中浮点数运算的加、减、乘、除

bash 不支持浮点运算&#xff0c;如果需要进行浮点运算&#xff0c;需要借助bc,awk 处理。 1、bc #!/bin/bash#加 f$(echo "4.32.5"|bc) echo "4.32.5$f"#减 f$(echo "4.3-2.5"|bc) echo "4.3-2.5$f"#乘 f$(echo "4.30*2.50&qu…

页面加载和解析流程

输入url,浏览器向服务器发出请求&#xff0c;服务器返回html文件&#xff0c;浏览器开始载入html代码&#xff0c;发现head标签有link标签引入外部的css文件&#xff0c;浏览器发出css文件的请求&#xff0c;服务器返回这个css文件&#xff0c;浏览器继续载入body中的代码&…

作为程序员应有10项权利

Scott认为&#xff0c;作为开发人员&#xff0c;应该有权享有以下列表所示的待遇&#xff1b;不过在国内&#xff0c;这个却有点异想天开&#xff0c;能有几个老板愿意给员工如此舒适的环境呢&#xff1f; 1.每位程序员应该拥有一个安静的工作环境 2.每位程序员应该拥有听音乐…

【Qt】QtCreator中自动补全注释

1、简述 在QtCreator中编辑代码,可以自动补全函数注释,供doxygen使用并生成文档。doxygen的使用方法,后续会写一个详细的博文。 2、使用方法 在函数前分别输入“/**”、“/*!”、“//!”、“///”,然后敲击回车键,会自动补全下方函数的注释。 注意:输入的注释一定要紧…

Java内存模型与线程

一、一致性高速缓存的存储交互很好的解决了处理器与内存的速度矛盾&#xff0c;但也存在缓存一致性&#xff08;cache coherence&#xff09;问题二、java内存模型内存模型&#xff1a;对特定的内存或高速缓存进行读写访问的过程抽象。 java内存模型&#xff08;java memory mo…

Google用更少标签生成图像,还提出一个用于训练评估GAN的库

参加 2019 Python开发者日&#xff0c;请扫码咨询 ↑↑↑译者 | 刘畅责编 | 琥珀出品 | AI科技大本营&#xff08;ID:rgznai100&#xff09;生成对抗网络&#xff08;GAN&#xff09;是属于一种强有力的深度生成模型。GAN 的主要思想是训练两个神经网络&#xff1a;一个是学习如…

视频用户行为及推荐系统评价KPI-部分

问题 KPI 使用推荐区的用户数量和比率是否显著提升 使用推荐区用户量及其占比与之前进行对比 新老用户使用推荐差异是否明显 新老用户推荐区使用比率占各自类别比&#xff0c;新老用户推荐区产生的VV占各自类别比 推荐区产生的VV占总VV是否显著提升 推荐区VV占总VV占比与…

【linux】用户和组的管理:添加、修改、删除(useradd usermod userdel groupadd groupdel)

一、用户 1、添加 $ useradd -h Usage: useradd [options] LOGINuseradd -Duseradd -D [options]Options:-b, --base-dir BASE_DIR base directory for the home directory of the new account-c, --comment COMMENT 加上备注文字&#xff0c;备注文字保存在pa…

ping命令工具:同时ping多个IP

检测多个ip在同一时间点的响应状态&#xff0c;通过对比来判断哪个ip异常。 下载地址&#xff1a;https://share.weiyun.com/5XCkypG 转载于:https://www.cnblogs.com/leavind/p/8743149.html

顶会论文9篇,又斩获百度奖学金!哈工大NLP“新生代”正崭露头角

参加 2019 Python开发者日&#xff0c;请扫码咨询 ↑↑↑作者 | 琥珀出品 | AI科技大本营&#xff08;ID:rgznai100&#xff09;“Static OnePlus”&#xff1f;首次看到这个网名时&#xff0c;激起了笔者不小的兴趣。正如每个网名背后都有一段不一样的故事&#xff0c;Static …

医院数据中心机房建设资料汇总(31篇)

医疗数据中心包括病人基本数据、入出转数据、电子病历、诊疗数据、医学影像数据、医学管理、经济数据&#xff0c;它们围绕着病人这个中心&#xff0c;成为了医 疗信息的主要来源。医疗数据质量的影响表现在医疗数据的实时、近期和远期应用&#xff0c;首先影响医疗信息系统的日…

【linux】CentOS启动后网络自动配置过程

1、启动后如何调用的网络配置脚本 网络配置脚本路径&#xff1a;/etc/init.d/network 根据不同启动级别对network脚本的调用情况&#xff1a; 进入/etc目录后&#xff0c;执行 $ find -name “*network”&#xff0c;结果如下&#xff1a; $ find -name "*network"…

web存储中cookie、session区别

http协议是一种无状态的协议&#xff0c;浏览器对服务器的每一次请求都是独立的。为了使得web能够产生一些动态信息&#xff0c;就需要保存”状态”&#xff0c;而cookie和session机制就是为了解决http协议无状态而产生。cookie是一种在客户端保存状态的方案&#xff0c;sessio…

李沐团队新作Gluon,复现CV经典模型到BERT,简单好用 | 强烈推荐

参加 2019 Python开发者日&#xff0c;请扫码咨询 ↑↑↑责编 | Jane出品 | AI科技大本营&#xff08;公众号id&#xff1a;rgznai100&#xff09;【导语】上周&#xff0c;李沐老师公布 GluonNLP0.6 版本&#xff0c;借助 Apache MXNet&#xff0c;大家可以尝试在 Gluon 中复现…