Android组件化开发实战

Android组件化开发实战,第1张

概述前言本文只是我在开发过程中一步一步总结的实战经验,若有疑问,欢迎私信,留言讨论。你的支持是对我最大的鼓励。文章目录前言组件化概述项目地址前置知识组件通信补充说明引入kapt插件引入AutoService服务搭建组件化框架01Activity中调用Fragment02自定义注解ITabPage03使 前言

本文只是我在开发过程中一步一步总结的实战经验,若有疑问,欢迎私信,留言讨论。你的支持是对我最大的鼓励。

文章目录前言组件化概述项目地址前置知识组件通信补充说明引入kapt插件引入AutoService服务搭建组件化框架01Activity中调用Fragment02 自定义注解ITabPage03使用ITabPage注解04获取对象及注解05Fragment切换①添加扩展函数witchContent()②重写底部BottomNavigationView Click事件06组件传递数据①新建IMainAppService接口②实现IMainAppService接口③使用AutoService完成跳转④取值总结

组件化概述

组件化不同于模块化,是可独立运行的module,module可以独立打包测试运行,业务解耦,解决65536问题,可拆卸,便于协同开发而不受其它业务模块影响。如果还不了解组件化是什么,我为你贴心准备了以下连接,请前往下方连接观看了解
https://www.zhihu.com/question/29735633/answer/90873592

项目地址

https://gitee.com/zhuhuitao/componentization

前置知识

这篇文章的内容会涉及以下前置 / 相关知识,贴心的我都帮你准备好了,请享用~
自定义plugin+including构建工程
注解(Annotation)详细讲解
AutoService官方示例及API文档
组件化之AutoService使用与源码解析

组件通信

当我们在实现组件化工程的首要问题是如何解决组件通信问题,在业界比较熟悉的如阿里的ARouter,不得不说使用ARouter还是稍微繁琐。在后来的项目开发过程中我更多的采用了谷歌为我们提供的autoservice,来完成组件通信,如果不了解autoService的使用及原理,可以前往我为你准备的前置知识查看了解。

补充说明

由于采用自定义plugin+including方式构建项目,所以与以往的传统方式在编写gradle有稍许差别,这里我还是想说明工程是在什么地方引入kapt插件以及autoService服务,避免不了解的小伙伴,不知如何下手,以免浪费小伙伴的宝贵时间。
在version module中有VersionConfigPlugin文件,这里对工程做了许多配置项,包括引入kapt插件和autoService服务,如下所示:

引入kapt插件
   ///kotlin插件    private fun Project.configCommonPlugin() {        //添加androID  或者 kotlin插件        plugins.apply("kotlin-androID")        //已启用 使用vIEwBinding        //plugins.apply("kotlin-android-extensions")        plugins.apply("kotlin-kapt")    }
引入autoService服务
    ///library module 公共依赖    private fun Project.configlibraryDependencIEs() {        dependencIEs.apply {            add(API, filetree(mapOf("dir" to "libs", "include" to listof("*.jar"))))            add(implementation, GradlePlugins.kotlinStdlib)            //如果module 的 name 不是common则引入common模块,不加次判断,活报错,提示我们common            //module自己引入自己,相当于递归了            if (name != "common") {                add(API, project(":common"))            }            //引入autoService服务,主要为后续的组件化开发做准备,Kotlin中要使用kapt方式            add(kapt, ThirdParty.autoService)            add(compileOnly, ThirdParty.autoService)            configTestDependencIEs()        }    }
搭建组件化框架

在开发中,我们应用主页通常都为一个Activity+多个Fragment完成,这时我们的MainActivity和多个Fragment并不在一个module中,我们该怎样在MainActivity中使用其他module中的Fragment呢。又或者我们的module A 怎么 把数据传递给module B呢,在接下来的篇幅中,我将详细介绍如何使用autoService结合自定义注解完成这些功能。

01Activity中调用Fragment

正如我们前面提到,module A 如果不和module B做任何关联依赖,A中是无法使用B中的任何方法或者资源的,我们通过使用autoService,然后通过ServiceLoader获取具体的对象,最后通过获取的对象获取我们自定义的注解,完成其它module Fragment调用。

02 自定义注解ITabPage

自定义注解ITabPage,在前面我们提到可以使用autoService+ServiceLoader获取具体的对象,有了具体的对象,我们就可以获取类的任何属性信息,于是我们通过自定义注解,以注解的方式传入我们的Tabname,以及Iconname,然后在Activity中获取注解信息,然后运用,自定义注解详细代码如下:

/** *author  : huitao *e-mail  : [email protected] *date    : 2021/5/25 11:26 *desc    : tabname 为底部菜单Fragment对应的名字,Iconname为相对应的图标 *version : */@Retention(AnnotationRetention.RUNTIME)@Target(AnnotationTarget.CLASS)annotation class ITabPage(val tabname: String, val iconname: String)
03使用ITabPage注解

假如我们现在有一个首页module,在module中有HomeFragment,代码如下:

/** *author  : huitao *e-mail  : [email protected] *date    : 2021/5/25 11:26 *desc    : 首页 *version : */@autoService(Fragment::class)@ITabPage(tabname = "首页", iconname = "tab_home")class HomeFragment : BaseFragment<FragmentHomeBinding>() {    overrIDe fun getDataBindingConfig(): DataBindingConfig {        return DataBindingConfig(R.layout.fragment_home)    }}

关于其他module:project,square等当中的Fragment与HomeFragment差不多,就不一一贴出详细代码,这里为了后续工作,我将Fragment做了抽取,建了一个BaseFragmeng,其中我们使用了VIEwDataBinding,详细代码如下:

/** *author  : huitao *e-mail  : [email protected] *date    : 2021/5/24 15:42 *desc    : Fragment基类 *version : */abstract class BaseFragment<T : VIEwDataBinding> : Fragment() {    private lateinit var mBinding: T    overrIDe fun onCreateVIEw(        inflater: LayoutInflater,        container: VIEwGroup?,        savedInstanceState: Bundle?    ): VIEw? {        val datingConfig = getDataBindingConfig()        val array = datingConfig.bindingParams        val bind: T =            DataBindingUtil.inflate(inflater, datingConfig.layout, container, false)        bind.lifecycleOwner = this        for (i in 0 until array.size()) {            bind.setvariable(array.keyAt(i), array.valueAt(i))        }        mBinding = bind        return mBinding.root    }    fun getBind(): VIEwDataBinding {        return mBinding    }    abstract fun getDataBindingConfig(): DataBindingConfig}
04获取对象及注解

当我们的自定义注解ITabPage等一切基础设施完成之后,我们通过ServiceLoader获取我们具体的Fragment对象,代码如下:

     private fun loadFragments() {        //获取所有使用@autoservice注解对象 (也就是Fragment)        val iterator = ServiceLoader.load(Fragment::class.java)        //获取布局中的BottomNavigationVIEw        val menu = getBinding().bottomMenu.menu        //遍历被我们使用@autoservice注解的Fragment        iterator.forEach { fragment ->            //获取我们自定义的注解            val property = fragment.javaClass.getAnnotation(ITabPage::class.java) ?: return@forEach            //加入tab名称            val menuItem = menu.add(property.tabname)            //设置图标            menuItem.setIcon(resources.getIDentifIEr(property.iconname, "mipmap", packagename))            //将fragment加入集合中,便于后续 *** 作fragment            mFragmentList.add(fragment)            //这里为了给提交事务时加上tag标签            mFragmentTags.add(property.tabname)            //将指针移动下一个位置            iterator.iterator()        }        //如果有fragment对象,则将当前的第一个fragment加入到布局中fragment container中并提交        if (mFragmentList.isNotEmpty()) {            mCurrentFragment = mFragmentList.first()            supportFragmentManager.beginTransaction()                .add(R.ID.fragment_container, mCurrentFragment, mFragmentTags.first()).commit()        }    }   

以上的几步 *** 作完成了获取Fragmeng对象,以及fragemnt中我们自定义的注解属性,并提交了我们当前的第一个fragment。

05Fragment切换①添加扩展函数witchContent()

为了便于做首页Fragmen之间的来回t的切换,我们为AppCompatActivity添加扩展函数AppCompatActivity.witchContent,这样我们可以在项目的很多地方使用此扩展函数,详细代码如下:

fun AppCompatActivity.witchContent(from: Fragment, to: Fragment, tag: String, ID: Int) {    val ft = supportFragmentManager.beginTransaction()    when (to.isAdded) {        true -> {            ft.hIDe(from).show(to).commit()        }        else -> {            ft.hIDe(from).add(ID, to, tag).commit()        }    }}
②重写底部BottomNavigationVIEw Click事件

这一部分相对简单,直接贴代码:

    private fun initBottomEvent() {        val menu = getBinding().bottomMenu        menu.setonNavigationItemSelectedListener { item ->            val size: Int = menu.menu.size()            var index = 0            //找到我们选中的position            for (i in 0 until size) {                if (menu.menu.getItem(i) === item) {                    index = i                    break                }            }            witchContent(                mCurrentFragment,                mFragmentList[index],                mFragmentTags[index],                R.ID.fragment_container            )            mCurrentFragment = mFragmentList[index]            //注意这里一定要返回true            true        }    }

以上基本上对Activity中获取Fragment做了详细描述,也详细讲解了如何自定义注解,使用对象获取注解属性。

06组件传递数据

在前面的Fragment使用中我们通过自定义注解,然后通过具体对象获取注解属性的值,如果设计少量的数据传递,无疑是完全没有问题,如果数据大量,或者组件间要传递的数据涉及到非常多的地方,这种方式无疑是痛苦的,接下来我们使用autoService完成另外一种组件通信。

①新建IMainAppService接口

由于我们的组件模块不止一个,这里我通过一个组件来举例说明,其它组件原理一样,我们在Common层新建接口IMainAppService,代码也非常简单如下:

/** *author  : huitao *e-mail  : [email protected] *date    : 2021/5/26 13:48 *desc    : *version : */interface IMainAppService {    //跳转到首页,并将电话号码传递到首页    fun <T : BaseActivity<*>> startActivity(c: T, mobile: String)}
②实现IMainAppService接口

在组件module app中实现IMainAppService接口,在module中新建MainAppService,代码如下:

/** *author  : huitao *e-mail  : [email protected] *date    : 2021/5/26 13:49 *desc    : *version : */@autoService(IMainAppService::class)class MainAppService : IMainAppService {    overrIDe fun <T : BaseActivity<*>> startActivity(c: T, mobile: String) {        val bundle = Bundle()        bundle.putString("mobile", mobile)        c.startActivity(MainActivity::class.java, bundle)    }}
③使用autoService完成跳转
    inner class ClickProxy {        fun login() {            when {                mviewmodel.mobile.get().isNullOrEmpty() -> showToast(                    getString(R.string.enter_mobile),                    this@LoginActivity                )                mviewmodel.password.get().isNullOrEmpty() -> showToast(                    getString(R.string.enter_password),                    this@LoginActivity                )                else -> {                    loadService(IMainAppService::class.java)?.startActivity(                        this@LoginActivity,                        "${mviewmodel.mobile.get()}\n${mviewmodel.password.get()}"                    )                }            }        }    }}
④取值
  overrIDe fun initData() {        intent.extras.let {            val mobile = it?.getString("mobile")            showToast(mobile!!,this)       }    }

以下为MainActivity 和 LoginActivity详细代码:

class MainActivity : BaseActivity<ActivityMainBinding>() {    private val mFragmentList = ArrayList<Fragment>()    private val mFragmentTags = ArrayList<String>()    private lateinit var mCurrentFragment: Fragment    overrIDe fun initVIEws() {        loadFragments()        initBottomEvent()    }    private fun initBottomEvent() {        val menu = getBinding().bottomMenu        menu.setonNavigationItemSelectedListener { item ->            val size: Int = menu.menu.size()            var index = 0            //找到我们选中的position            for (i in 0 until size) {                if (menu.menu.getItem(i) === item) {                    index = i                    break                }            }            witchContent(                mCurrentFragment,                mFragmentList[index],                mFragmentTags[index],                R.ID.fragment_container            )            mCurrentFragment = mFragmentList[index]            //注意这里一定要返回true            true        }    }    private fun loadFragments() {        //获取所有使用@autoservice注解对象 (也就是Fragment)        val iterator = ServiceLoader.load(Fragment::class.java)        //获取布局中的BottomNavigationVIEw        val menu = getBinding().bottomMenu.menu        //遍历被我们使用@autoservice注解的Fragment        iterator.forEach { fragment ->            //获取我们自定义的注解            val property = fragment.javaClass.getAnnotation(ITabPage::class.java) ?: return@forEach            //加入tab名称            val menuItem = menu.add(property.tabname)            //设置图标            menuItem.setIcon(resources.getIDentifIEr(property.iconname, "mipmap", packagename))            //将fragment加入集合中,便于后续 *** 作fragment            mFragmentList.add(fragment)            //这里为了给提交事务时加上tag标签            mFragmentTags.add(property.tabname)            //将指针移动下一个位置            iterator.iterator()        }        //如果有fragment对象,则将当前的第一个fragment加入到布局中fragment container中并提交        if (mFragmentList.isNotEmpty()) {            mCurrentFragment = mFragmentList.first()            supportFragmentManager.beginTransaction()                .add(R.ID.fragment_container, mCurrentFragment, mFragmentTags.first()).commit()        }    }    overrIDe fun initData() {        intent.extras.let {            val mobile = it?.getString("mobile")            showToast(mobile!!,this)       }    }    overrIDe fun getDataBindingConfig(): DataBindingConfig {        return DataBindingConfig(R.layout.activity_main)    }}class LoginActivity : BaseActivity<@R_301_3078@inBinding>() {    private val mviewmodel = Loginviewmodel()    overrIDe fun initVIEws() {    }    overrIDe fun initData() {    }    overrIDe fun getDataBindingConfig(): DataBindingConfig {        return DataBindingConfig(R.layout.activity_login).addBindParams(BR.clickProxy, ClickProxy())            .addBindParams(BR.vm, mviewmodel)    }    inner class ClickProxy {        fun login() {            when {                mviewmodel.mobile.get().isNullOrEmpty() -> showToast(                    getString(R.string.enter_mobile),                    this@LoginActivity                )                mviewmodel.password.get().isNullOrEmpty() -> showToast(                    getString(R.string.enter_password),                    this@LoginActivity                )                else -> {                    loadService(IMainAppService::class.java)?.startActivity(                        this@LoginActivity,                        "${mviewmodel.mobile.get()}\n${mviewmodel.password.get()}"                    )                }            }        }    }}
总结

使用谷歌为我们提供的autoService无疑是轻量级的,易于理解的,并不需要我们做过多复杂配置以及些过多代码,就可以轻而易举完成我们组件开发框架的搭建,在整个架构的搭建中,我们最能感受到的是业务被具体划分,也不在是一个module单兵作战。如果你觉得这篇文章对你有帮助,欢迎点赞关注,你的支持便是对我最大的鼓励。

总结

以上是内存溢出为你收集整理的Android组件化开发实战全部内容,希望文章能够帮你解决Android组件化开发实战所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/web/1003750.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-22
下一篇 2022-05-22

发表评论

登录后才能评论

评论列表(0条)

保存