Loading [MathJax]/jax/input/TeX/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >IOC架构实现布局、事件注入

IOC架构实现布局、事件注入

作者头像
aruba
发布于 2021-12-06 09:37:35
发布于 2021-12-06 09:37:35
55400
代码可运行
举报
文章被收录于专栏:android技术android技术
运行总次数:0
代码可运行
IOC全称Inverse Of Control,中文释义为控制反转,常见的方式叫作依赖注入(Dependency Injection),IOC核心的思想和代理模式一样,使用者不必关心资源的具体获取,资源通过第三方来管理
之前有提到过注解是设计框架时常用的工具,利用注解可以在编译期(通过APT)或运行期生成代码,今天通过运行期使用注解来实现ButterKnife的布局和事件绑定功能
一、布局注入

我们希望在类上通过注解的方式,指定Activity的布局

1.新建注解

该注解需要一个布局id的参数

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * 布局注解
 * Created by aruba on 2021/10/27.
 */
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
annotation class ContentView(val value: Int)
2.定义注入工具类

通过反射获取ContentView注解,并最终调用Activity的setContentView方法

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * 注入工具
 * Created by aruba on 2021/10/27.
 */
object InjectUtils {

    fun inject(activity: BaseActivity) {
        injectContentView(activity)
    }

    /**
     * 注入布局文件
     */
    private fun injectContentView(activity: BaseActivity) {
        activity.javaClass.getAnnotation(ContentView::class.java)?.apply {
            activity.setContentView(value)
        }
    }

}
3.Activity创建时,调用注入工具

写一个基类,在onCreate中调用注入工具的方法

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * Created by aruba on 2021/10/27.
 */
open class BaseActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        //运行时注入
        InjectUtils.inject(this)
    }
}
4.Activity类上使用注解

我们继承BaseActivity基类,并使用ContentView注解指定布局id

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@ContentView(R.layout.activity_main)
class MainActivity : BaseActivity() {
    
}

布局就一个默认的TextView,内容如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/tv_hello"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

效果:

二、控件id绑定

有了上面的基础,控件id绑定也是依葫芦画瓢

1.控件绑定注解
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * 绑定id注解
 * Created by aruba on 2021/10/27.
 */
@Target(AnnotationTarget.FIELD)
@Retention(AnnotationRetention.RUNTIME)
annotation class BindID(val id: Int)
2.注入工具实现
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * 注入工具
 * Created by aruba on 2021/10/27.
 */
object InjectUtils {

    fun inject(activity: BaseActivity) {
        injectContentView(activity)
        injectViewId(activity)
    }

    /**
     * 注入布局文件
     */
    private fun injectContentView(activity: BaseActivity) {
        activity.javaClass.getAnnotation(ContentView::class.java)?.apply {
            activity.setContentView(value)
        }
    }

    /**
     * 注入控件id
     */
    private fun injectViewId(activity: BaseActivity) {
        activity.javaClass.declaredFields.filter { field ->
            field.getAnnotation(BindID::class.java) != null
        }.forEach{ field ->
            field.apply {
                isAccessible = true
                set(activity, activity.findViewById(getAnnotation(BindID::class.java)!!.id))
            }
        }
    }

}
3.Activity中使用注解
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@ContentView(R.layout.activity_main)
class MainActivity : BaseActivity() {
    @BindID(R.id.tv_hello)
    val tvHello: TextView? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        tvHello?.apply {
            text = "hello inject"
        }
    }
}

效果:

三、事件注入

事件注入需要使用动态代理,我们需要生成View对应的事件回调(点击、长按等)匿名类对象

1.定义事件元注解

为了方便扩展,我们定义一个元注解,来表示事件注解需要代理的设置监听方法、监听事件接口、接口方法,如:setOnClickListener,View.OnClickListener::class,onClick

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * 事件元注解
 * Created by aruba on 2021/10/27.
 */
@Target(AnnotationTarget.ANNOTATION_CLASS)
@Retention(AnnotationRetention.RUNTIME)
annotation class Event(
    val setter: String,
    val listenerClz: KClass<out Any>,
    val listenerCallbackMethodName: String
)
2.定义事件注解

事件注解需要使用元注解,注明代理控件的设置监听方法、监听方法传入的参数类型、监听类的回调函数名。

还需要一个集合属性,用来获取需要绑定的控件id集合

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * 点击事件注解
 * Created by aruba on 2021/10/27.
 */
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
@Event(
    setter = "setOnClickListener",
    listenerClz = View.OnClickListener::class,
    listenerCallbackMethodName = "onClick"
)
annotation class OnClick(
    vararg val ids: Int
)
3.注入工具实现

我们需要获取Activity中使用OnClick注解的方法,并获取OnClick注解的元注解Event,通过元注解,获取控件的setOnClickListener方法,并通过动态代理生成View.OnClickListener的代理对象,最后通过反射调用setOnClickListener方法为view绑定代理对象

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * 注入工具
 * Created by aruba on 2021/10/27.
 */
object InjectUtils {

    fun inject(activity: BaseActivity) {
        injectContentView(activity)
        injectViewId(activity)
        injectClick(activity)
    }

    /**
     * 注入布局文件
     */
    private fun injectContentView(activity: BaseActivity) {
        activity.javaClass.getAnnotation(ContentView::class.java)?.apply {
            activity.setContentView(value)
        }
    }

    /**
     * 注入控件id
     */
    private fun injectViewId(activity: BaseActivity) {
        activity.javaClass.declaredFields.filter { field ->
            field.getAnnotation(BindID::class.java) != null
        }.forEach { field ->
            field.apply {
                isAccessible = true
                set(activity, activity.findViewById(getAnnotation(BindID::class.java)!!.id))
            }
        }
    }

    /**
     * 注入点击事件
     */
    private fun injectClick(activity: BaseActivity) {
        //获取方法
        activity.javaClass.declaredMethods.filter { method ->
            //过滤非OnClick注解
            method.getAnnotation(OnClick::class.java) != null
        }.forEach { method -> //method 为 Activity中的clickView方法
            //获取OnClick注解
            val onClick = method.getAnnotation(OnClick::class.java)
            //获取控件id数组
            val ids = onClick!!.ids

            //获取OnClick注解的 元注解:Event
            method.annotations.forEach {
                //强转成Java Annotation对象,因为kotlin无法获取元注解(注解的注解)
                (it as java.lang.annotation.Annotation).annotationType()
                    .getAnnotation(Event::class.java)
                    ?.apply {
                        //给每个View绑定事件
                        ids.forEach { id ->
                            val view: View = activity.findViewById(id)
                            //获取setOnClickListener方法,入参为View.OnClickListener
                            val setOnClickListenerMethod =
                                view.javaClass.getMethod(setter, listenerClz.java)

                            //绑定 点击事件回调函数名onClick 与 Activity中的clickView方法(被OnClick注解的方法) 的关系
                            val map = mapOf(listenerCallbackMethodName to method)

                            //动态代理
                            val handler = ClickInvocationHandler(WeakReference(activity), map)
                            //动态代理生成的对象:点击事件匿名内部类
                            val proxy = Proxy.newProxyInstance(
                                listenerClz.java.classLoader,
                                arrayOf(listenerClz.java),
                                handler
                            )

                            //为view设置setOnClickListener
                            setOnClickListenerMethod.invoke(view, proxy)
                        }
                    }
            }
        }
    }

}
4.动态代理

InvocationHandler中,我们需要代理View.OnClickListener的onClick方法,改为调用被OnClick注解的方法,通过外部传入的Map,可以通过方法名快速获取到被OnClick注解的方法

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * 动态代理事件
 * Created by aruba on 2021/10/27.
 */
class ClickInvocationHandler(
    private val activity: WeakReference<BaseActivity>,
    private val map: Map<String, Method>
) : InvocationHandler {

    override fun invoke(proxy: Any?, method: Method?, args: Array<out Any>?): Any? {
        //发现Method是onClick方法,执行被OnClick注解的clickView方法
        method?.apply {
            activity.get()?.run {
                //onClick回调函数的入参为view: View?,将它强转成View后再传给clickView方法
                return map[name]?.invoke(this, args!![0] as View)
            }
        }

        return method?.invoke(proxy, args)
    }

}
5.封装

我们将注入事件的方法优化,使它更具扩展性,将注解类型作为参数传入,并将控件id集合通过lambda获取

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * 注入工具
 * Created by aruba on 2021/10/27.
 */
object InjectUtils {

    fun inject(activity: BaseActivity) {
        injectContentView(activity)
        injectViewId(activity)
        injectClick(activity, OnClick::class.java) {
            it.ids
        }
    }

    /**
     * 注入布局文件
     */
    private fun injectContentView(activity: BaseActivity) {
        activity.javaClass.getAnnotation(ContentView::class.java)?.apply {
            activity.setContentView(value)
        }
    }

    /**
     * 注入控件id
     */
    private fun injectViewId(activity: BaseActivity) {
        activity.javaClass.declaredFields.filter { field ->
            field.getAnnotation(BindID::class.java) != null
        }.forEach { field ->
            field.apply {
                isAccessible = true
                set(activity, activity.findViewById(getAnnotation(BindID::class.java)!!.id))
            }
        }
    }

    /**
     * 注入点击事件
     */
    private inline fun <T : Annotation> injectClick(
        activity: BaseActivity,
        clz: Class<out T>,
        getIds: (annotation: T) -> IntArray
    ) {
        //获取方法
        activity.javaClass.declaredMethods.filter { method ->
            //过滤非OnClick注解
            method.getAnnotation(clz) != null
        }.forEach { method -> //method 为 Activity中的clickView方法
            //获取OnClick注解
            val onClick = method.getAnnotation(clz)
            //获取控件id数组
            val ids = getIds(onClick)

            //获取OnClick注解的 元注解:Event
            method.annotations.forEach {
                //强转成Java Annotation对象,因为kotlin无法获取元注解(注解的注解)
                (it as java.lang.annotation.Annotation).annotationType()
                    .getAnnotation(Event::class.java)
                    ?.apply {
                        //给每个View绑定事件
                        ids.forEach { id ->
                            val view: View = activity.findViewById(id)
                            //获取setOnClickListener方法,入参为View.OnClickListener
                            val setOnClickListenerMethod =
                                view.javaClass.getMethod(setter, listenerClz.java)

                            //绑定 点击事件回调函数名onClick 与 Activity中的clickView方法(被OnClick注解的方法) 的关系
                            val map = mapOf(listenerCallbackMethodName to method)

                            //动态代理
                            val handler = ClickInvocationHandler(WeakReference(activity), map)
                            //动态代理生成的对象:点击事件匿名内部类
                            val proxy = Proxy.newProxyInstance(
                                listenerClz.java.classLoader,
                                arrayOf(listenerClz.java),
                                handler
                            )

                            //为view设置setOnClickListener
                            setOnClickListenerMethod.invoke(view, proxy)
                        }
                    }
            }
        }
    }

}

这样如果我们想要新增长按事件功能,只需要新增长按事件的注解,并再次调用注入事件方法即可

6.Activity中使用注解
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@ContentView(R.layout.activity_main)
class MainActivity : BaseActivity() {
    @BindID(R.id.tv_hello)
    val tvHello: TextView? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        tvHello?.apply {
            text = "hello inject"
        }
    }

    @OnClick(R.id.tv_hello, R.id.tv_hello2)
    fun clickView(view: View) {
        Toast.makeText(this, (view as TextView).text, Toast.LENGTH_SHORT).show()
    }
}

效果:

项目地址:https://gitee.com/aruba/iocapplication.git
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021/10/28 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
【IOC 控制反转】Android 事件依赖注入 ( 事件依赖注入代码示例 )
Android 依赖注入的核心就是通过反射获取 类 / 方法 / 字段 上的注解 , 以及注解属性 ; 在 Activity 基类中 , 获取该注解 以及 注解属性 , 进行相关操作 ;
韩曙亮
2023/03/29
3550
【IOC 控制反转】Android 事件依赖注入 ( 事件依赖注入代码示例 )
移动架构-IOC架构设计
控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injection,简称DI),还有一种方式叫“依赖查找”(Dependency Lookup)。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体,将其所依赖的对象的引用传递给它。也可以说,依赖被注入到对象中
Android技术干货分享
2019/05/10
8120
移动架构-IOC架构设计
【IOC 控制反转】Android 视图依赖注入 ( 视图依赖注入步骤 | 视图依赖注入代码示例 )
Android 依赖注入的核心就是通过反射获取 类 / 方法 / 字段 上的注解 , 以及注解属性 ; 在 Activity 基类中 , 获取该注解 以及 注解属性 , 进行相关操作 ;
韩曙亮
2023/03/29
7660
【IOC 控制反转】Android 视图依赖注入 ( 视图依赖注入步骤 | 视图依赖注入代码示例 )
自定义Android IOC框架
所谓IOC,即控制反转(Inversion of Control,英文缩写为IoC)
没关系再继续努力
2021/11/29
3800
Java | 静态代理与动态代理真的超简单
静态代理: 由我们开发者自己手动创建或者在程序运行前就已经存在的代理类,静态代理通常只代理一个类,动态代理是代理一个接口下的多个实现类。
Petterp
2022/02/09
4680
编译时注解apt / kapt
一个注解允许你把额外的元数据关联到一个声明上。然后元数据就可以被相关的源代码工具访问,通过编译好的类文件或是在运行时,取决于这个注解是如何配置的。 --《Kotlin in Action》
蜻蜓队长
2019/04/25
1.8K0
编译时注解apt / kapt
《Kotin 极简教程》第13章 使用 Kotlin 和 Anko 的Android 开发
Anko (https://github.com/Kotlin/anko) 是一个用 Kotlin 写的Android DSL (Domain-Specific Language)。长久以来,Android视图都是用 XML 来完成布局的。这些 XML可重用性比较差。同时在运行的时候,XML 要转换成 Java 表述,这在一定程度上占用了 CPU 和耗费了电量。
一个会写诗的程序员
2018/08/17
3.7K0
【IOC 控制反转】Android 布局依赖注入 ( 布局依赖注入步骤 | 布局依赖注入代码示例 )
Android 依赖注入的核心就是通过反射获取 类 / 方法 / 字段 上的注解 , 以及注解属性 ; 在 Activity 基类中 , 获取该注解 以及 注解属性 , 进行相关操作 ;
韩曙亮
2023/03/29
9730
【IOC 控制反转】Android 布局依赖注入 ( 布局依赖注入步骤 | 布局依赖注入代码示例 )
【IOC 控制反转】Android 事件依赖注入 ( 事件三要素 | 修饰注解的注解 | 事件依赖注入步骤 )
Android 依赖注入的核心就是通过反射获取 类 / 方法 / 字段 上的注解 , 以及注解属性 ; 在 Activity 基类中 , 获取该注解 以及 注解属性 , 进行相关操作 ;
韩曙亮
2023/03/29
9230
Android之注解的使用介绍
Java 注解(Annotation)又称 Java 标注,是 JDK5.0 引入的一种注释机制。
李小白是一只喵
2021/07/08
6610
用kotlin打造简化版本的ButterKnife
大名鼎鼎的 ButterKnife 库相信很多 android 开发者都听过,在 Github 上star的数目已经快15k了,而且很多知名的app都在使用。
fengzhizi715
2018/08/24
9140
用kotlin打造简化版本的ButterKnife
Android开源框架源码解析系列(3)——ButterKnife源码解析
ButterKnife是一个专注于Android系统的View注入框架,有了ButterKnife可以很轻松的省去findViewById,ButterKnife用到的注解并不是在运行时反射的,而是在编译的时候生成新的class,对运行时性能没有影响,本篇我们来详细学习一下它的源码。
老马的编程之旅
2022/06/22
1K0
【Android】只给个泛型,如何自动初始化ViewModel与ViewBinding?这几种方案值得了解
链接:https://juejin.cn/post/7357546247849197606 本文由作者授权发布
Rouse
2024/06/11
4470
【Android】只给个泛型,如何自动初始化ViewModel与ViewBinding?这几种方案值得了解
【IOC 控制反转】Android 事件依赖注入 ( 事件依赖注入具体的操作细节 | 获取 Activity 中的所有方法 | 获取方法上的注解 | 获取注解上的注解 | 通过注解属性获取事件信息 )
Android 依赖注入的核心就是通过反射获取 类 / 方法 / 字段 上的注解 , 以及注解属性 ; 在 Activity 基类中 , 获取该注解 以及 注解属性 , 进行相关操作 ;
韩曙亮
2023/03/29
3.1K0
Java高级特性——注解,这也许是最简单易懂的文章了
博主在初学注解的时候看到网上的介绍大部分都是直接介绍用法或者功能,没有实际的应用场景,篇幅又很长导致学习的时候难以理解其意图,而且学完就忘QAQ。本篇文章中我将结合实际的应用场景尽可能由浅入深,平缓的介绍java注解。
lyb-geek
2018/09/27
5440
Java高级特性——注解,这也许是最简单易懂的文章了
Android--利用APT+kotlinpoet实现组件化开发Router机制
在实现Router机制之前,我们还可以对项目的组织架构进行优化,将gradle中公用部分抽出来 有了上一篇的基础,我们初步实现了架构分层,目前有三个module:
aruba
2021/12/06
9990
Android--利用APT+kotlinpoet实现组件化开发Router机制
【IOC 控制反转】Android 事件依赖注入 ( 事件依赖注入具体的操作细节 | 获取要注入事件的 View 对象 | 通过反射获取 View 组件的事件设置方法 )
Android 依赖注入的核心就是通过反射获取 类 / 方法 / 字段 上的注解 , 以及注解属性 ; 在 Activity 基类中 , 获取该注解 以及 注解属性 , 进行相关操作 ;
韩曙亮
2023/03/29
1.8K0
Butterknife全方位解析
Butterknife是供职于Square公司的JakeWharton大神开发的开源库,使用这个库,在AS中搭配Android ButterKnife Zelezny插件,可以大大提高开发的效率,从此摆脱繁琐的findViewById(int id),也不用自己手动@bind(int id) , 直接用插件生成即可。本篇博客将对Butterknife进行深入解析。
老马的编程之旅
2022/06/22
7550
Butterknife全方位解析
Android之IOC框架介绍
就是一个类里面需要用到很多个成员变量,传统的写法,你要用这些成员变量,那么你就new 出来用呗!
李小白是一只喵
2021/06/29
6440
Android--Hilt入门
和Dagger相同,Hilt也分两种注入方式,以上篇Dagger中的代码为例子,来对比两个框架的使用区别
aruba
2021/12/16
1.6K0
Android--Hilt入门
推荐阅读
【IOC 控制反转】Android 事件依赖注入 ( 事件依赖注入代码示例 )
3550
移动架构-IOC架构设计
8120
【IOC 控制反转】Android 视图依赖注入 ( 视图依赖注入步骤 | 视图依赖注入代码示例 )
7660
自定义Android IOC框架
3800
Java | 静态代理与动态代理真的超简单
4680
编译时注解apt / kapt
1.8K0
《Kotin 极简教程》第13章 使用 Kotlin 和 Anko 的Android 开发
3.7K0
【IOC 控制反转】Android 布局依赖注入 ( 布局依赖注入步骤 | 布局依赖注入代码示例 )
9730
【IOC 控制反转】Android 事件依赖注入 ( 事件三要素 | 修饰注解的注解 | 事件依赖注入步骤 )
9230
Android之注解的使用介绍
6610
用kotlin打造简化版本的ButterKnife
9140
Android开源框架源码解析系列(3)——ButterKnife源码解析
1K0
【Android】只给个泛型,如何自动初始化ViewModel与ViewBinding?这几种方案值得了解
4470
【IOC 控制反转】Android 事件依赖注入 ( 事件依赖注入具体的操作细节 | 获取 Activity 中的所有方法 | 获取方法上的注解 | 获取注解上的注解 | 通过注解属性获取事件信息 )
3.1K0
Java高级特性——注解,这也许是最简单易懂的文章了
5440
Android--利用APT+kotlinpoet实现组件化开发Router机制
9990
【IOC 控制反转】Android 事件依赖注入 ( 事件依赖注入具体的操作细节 | 获取要注入事件的 View 对象 | 通过反射获取 View 组件的事件设置方法 )
1.8K0
Butterknife全方位解析
7550
Android之IOC框架介绍
6440
Android--Hilt入门
1.6K0
相关推荐
【IOC 控制反转】Android 事件依赖注入 ( 事件依赖注入代码示例 )
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验