精华内容
下载资源
问答
  • 行业-电子政务-医疗装置中电子组件选择性激活.zip
  • 2021-2025年中国控制线缆组件行业目标市场选择策略研究报告.pdf
  • 2021-2025年中国光伏组件行业调研及渠道选择策略研究报告.pdf
  • 2021-2025年中国光伏组件生产装备行业调研及渠道选择策略研究报告.pdf
  • 行业分类-设备装置-用于负载分接转换开关的选择器开关组件
  • 如今,随着市场由4G向5G网络解决方案迁移,蜂窝通信行业正在为实现更快数据传输速度、更低延迟以及容量、用户密度和可靠性的巨大飞跃奠定基础。例如,5G不仅可以提高数据速率(100倍)和网络容量(10倍),还可将...
  • 相比传统的组件生产工艺, MBB 主要在电池图形设计及电池片间的互联工艺上发生改变,电池片采用更细更 窄的主栅,并在封装时采用圆形焊丝代替焊带。由于采用多主栅降低了电流损耗, 同时圆形焊丝相比焊带对于电池的...
  • 如今,随着市场由4G向5G网络解决方案迁移,蜂窝通信行业正在为实现更快数据传输速度、更低延迟以及容量、用户密度和可靠性的巨大飞跃奠定基础。例如,5G不仅可以提高数据速率(100倍)和网络容量(10倍),还可将...
  • 行业资料-电子功用-具有可选择性调节的直接和间接内重整的内重整燃料电池组件.pdf
  • 行业资料-电子功用-具有可选择性调节的直接和间接内重整的内重整燃料电池组件的介绍分析.rar
  • 那个职业选择的数据大概有五千多条,有点多,需要拉接口调。这里写个模拟的,结构就是一级一级套对象,代码如下:function getJobs__list(level,arg1,arg2){ // 这是展示的 var Jobs__list = {

    设计图长这样:
    这里写图片描述

    如图,大概分为三级。点击事件上有个小trick要注意,那就是,第一级点击后显示第二级,第二级点击之后是toggle第三级。

    那个职业选择的数据大概有五千多条,有点多,需要拉接口调。这里写个模拟的,结构就是一级一级套对象,代码如下:

    function getJobs__list(level,arg1,arg2){
        // 这是展示的
        var Jobs__list = {
            'worker':{
                'coder':{
                    'shit-coder':'',
                    'bullshit-coder':''
                }
            },
            'buyer':{
                'mbuyer':{
                    'boybuyer':'',
                    'dadbuyer':''
                },
                'wbuyer':{
                    'girlbuyer':'',
                }
            },
    
        };
    
    
        if(level===1){
            var _jobs__flist = {}
    
            for(key in Jobs__list){
                _jobs__flist[key] = {}
            }
            return _jobs__flist
        };
    
        if(level === 2 && arg1){
    
    
            return Jobs__list[arg1]
    
    
        };
    
        if( level === 3 && arg1 && arg2){
    
            console.log(arg1,arg2)
    
            return Jobs__list[arg2][arg1]
    
        }
    
    }
    
    
    function getJobSearchResult(searchname){
        //这是用于搜索的
        var o = {
            'algricultrue':{
                'fish':{
                    'fish-buyer':{
                        securitylevel:'2',
                    },
                    'fisherman':{
                        securitylevel:'拒保'
                    }
                }
            }
        }
        o['algricultrue']['fish'][searchname] = {
            securitylevel : '1'
        }
        var results = []
        for( key in o){
            if(o.hasOwnProperty(key)){
                for(key2 in o[key]){
                    if(o[key].hasOwnProperty(key2)){
                        for (key3 in o[key][key2]){
                            if(o[key][key2].hasOwnProperty(key3)){
                                var s = ''
                                s = key + '-' + key2 + '-' + key3 
                                var so = {}
                                so[s]= o[key][key2][key3]['securitylevel']
                                results.push(so)
                            }
                        }
                    }
                }
            }
        }
    
        return so   
    }

    忽略丑丑的代码吧,这就是个很粗糙的假冒伪劣接口。

    这是个组件:

    Vue.component('stackedList',{
        template:"<article class='stackedlist'>\
        <div v-for='(f__value,f__key,f__index) in flist' :class='listkind+\"__flist\"'>\
            <header :class='listkind+\"__f\"' @click='showslist(f__key,f__index)'>{{f__key}}</header>\
            <article v-show='ssonshow[f__index]' :class='listkind+\"__slist\"' v-for='(s__value,s__key,s__index) of f__value'>\
                <header :class='listkind+\"__s\"' @click='showtlist(s__key,f__key,s__index,f__index)'>{{s__key}}</header>\
                <article :class='listkind+\"__tlist\"'>\
                <p :class='listkind+\"__t\"'  v-for='(t__value,t__key,t__index) of s__value' @click='select(t__key)' v-show='tsonshow[f__index][s__index]'>{{t__key}}</p>\
                </article>\
            </article>\
        </div>\
    </article>\
        ",
        props:['listkind','flist'],
        data:function() {
            return {        
                ssonshow:[],
                tsonshow:[],
            }
        },
        methods:{
            showslist:function(f__key,f__index){
    
                this.$emit('getflist',2,f__key)
    
                this.ssonshow[f__index] = true
    
                this.tsonshow[f__index] = []
    
            },
            showtlist:function(s__key,f__key,s__index,f__index){
                this.$emit('getslist',3,s__key,f__key)
    
                this.tsonshow[f__index][s__index] = !(!!this.tsonshow[f__index][s__index])
            },
            select:function(t__key){
    
                this.$emit('passvalue',t__key)
            }
        }
    })

    组件要套在Jobs组件中(jobs才是那个整体的大组件)

    
    Vue.component('jobs',{
        template:"<div class='jobs'><div class='info info__job'>\
                    <span class='pretip' >职业</span>\
                    <input type='text' class='answer' v-model='jobselected' placeholder='请选择职业' @click.prevent='togglejobbox(true,$event)'/>\
    </div>\
    <div class='jobbox--curtain' v-show='jobboxonshow'>\
        <div class='jobbox'>\
            <header>\
                <p class='jobbox__header'>查询职业类别</p>\
                <div class='jobbox__close' @click='togglejobbox(false,$event)'>+</div>\
            </header>\
            <input class='jobbox__search' type='text' placeholder='搜索职业' @input='showsearchresult' v-model='searchname'/>\
            <stacked-list v-show='!searchname.trim()' :listkind='listkind' :flist='flist' @passvalue='selectjob'  @getflist='getflist' @getslist='getslist'></stacked-list>\
            <article class='jobbox__searchresult' v-show='searchname.trim()' >\
                <p class='jobbox__searchresult__item' v-for='(securitylevel,searchresult) in searchresults'><span class='jobbox__searchresult__item__job'>{{searchresult}}</span><span class='jobbox__searchresult__item__level'>{{securitylevel}}</span></p>\
            </article>\
        </div>\
    </div></div>"
    ,
        props:['jobselected','flist'],
    
        data:function(){
    
            return {
    
                jobboxonshow : false,
    
                searchname:'',
    
                listkind:'jobbox',
    
                searchresults: {}           
            }
        },
        methods:{
    
            togglejobbox:function(ifshow,e){
    
                e.target.blur()
    
                this.jobboxonshow = ifshow ?  true :false ;
            },
    
            showsearchresult:function(){
    
                if(!this.searchname){
                    console.error('no searchname')
                    return
                }
    
                var results =  getJobSearchResult(this.searchname)
    
                this.searchresults = results
    
            },
    
            selectjob:function(jobvalue){
    
                this.$emit('selectjob',jobvalue)
    
                this.jobboxonshow = false
            },
            getflist: function(level,f__key){
                this.$emit('getflist',2,f__key)
            },
            getslist: function(level,s__key,f__key){
                this.$emit('getslist',3,s__key,f__key)
    
            }
        }
    })

    然后在主体的VM里挂上< jobs >< / jobs >即可,注意prop哦~

    这里其实有多个人都可以选职业,要注意把接口调用的数据返回放在一个vm中,多个组件共用一个性质。或者干脆就一个组件,只是v-model对应不同的人,做个区分就行。然后因为是一级一级查询的,要把数据先缓存在本地,如果Local查不到,再去调接口。

    展开全文
  • 小程序实现三级选择组件

    千次阅读 2018-08-13 00:30:19
    原文链接:小程序实现三级选择组件 效果 实现过程 &lt;view class="section"&gt; &lt;view class="section__title"&gt;{{title}}&lt;/view&gt; &lt;...

    原文链接:小程序实现三级选择器组件

    效果

    这里写图片描述

    实现过程

    <view class="section">
        <view class="section__title">{{title}}</view>
        <picker bindchange="bindPickerChange" value="{{multiIndex}}" range="{{multiArray}}" mode="multiSelector" bindcolumnchange="columnchange">
          <view class="picker">  
          <view class='words'>
            <!-- {{multiArray[2][multiIndex[2]]}} -->
            {{multiArray[0][multiIndex[0]]}},{{multiArray[1][multiIndex[1]]}},{{multiArray[2][multiIndex[2]]}}
          </view>
          <image src='/image/right.png'></image>  
        </view> 
        </picker>
      </view>

    在小程序已有的picker组件中设置mode="multiSelector",使之成为多列选择器。

    在多列选择器中有两个重要的参数:

    properties: {
        multiArray: Array,
        multiIndex: Array
      }

    multiArray是一个二维数组,存放选择器每一列上的选项列表。multiIndex是一个一维数组,存放每一列被选中的值,例:[0,0,0] 表示第一列选中了第0个选项,第二列也选中了第0个选项,以此类推。这两个参数都由用到三列选择器的页面传入。

    在小程序已有的picker组件中绑定bindPickerChange事件和columnchange事件,当用户确定选择器的选项值或改变某列选项值时会分别触发这两个事件:

    methods: {
        //这个只有在点确定的时候才会触发
        bindPickerChange: function (e) {
          this.triggerEvent("multiSelectorValue", e.detail)// 更新下标字段multiIndex,event.detail = {value: value}
        },
        columnchange: function (e) {
          this.triggerEvent("multiSelectorColumn", e.detail)// detail包含当前改变的列和改变的列的数值,event.detail = {column: column, value: value}
        }
      }

    设置multiSelectorValue事件和multiSelectorColumn事件,来让用到三列选择器的页面捕捉到选择器选项值的改变。

    在要用到三列选择器的页面里引入三列选择器组件,比如叫v-picker-multiSelector:

    <v-picker-multiSelector multiArray="{{multiArray}}" multiIndex="{{multiIndex}}"  bind:multiSelectorValue="receiveMultiSelectorValue" bind:multiSelectorColumn="receiveMultiSelectorColumn"></v-picker-multiSelector>

    通过设置receiveMultiSelectorValue函数和receiveMultiSelectorColumn函数来接收选项值的变化:

    //当用户改变种植区某列选项时触发的事件
    receiveMultiSelectorColumn: function (e) {
      const column = e.detail.column
      const columnValue = e.detail.value
      switch (column) {
        case 0:
          this.data.multiIndex[0] = columnValue //更新省值
    
          this.data.multiArray[1] = this.testGetCity(this.data.provinceList[columnValue]) //获取市列表
          this.data.cityList = this.data.multiArray[1] //更新市列表
          this.data.multiIndex[1] = 0 // 将市默认选择第一个
    
          this.data.multiArray[2] = this.testGetPlantingArea(this.data.cityList[0]) //获取区列表
          this.data.plantingAreaList = this.data.multiArray[2] //更新种植区列表
          this.data.multiIndex[2] = 0 // 将区默认选择第一个
    
          this.setData({
            multiArray: this.data.multiArray,
            multiIndex: this.data.multiIndex
          })
          break
    
        case 1:
          this.data.multiIndex[1] = columnValue //更新市值
    
          //this.data.multiArray[2] = this.getPlantingArea(this.data.cityList[columnValue])//获取区列表
          this.data.multiArray[2] = this.testGetPlantingArea(this.data.cityList[columnValue]) //测试用,获取区列表
          this.data.plantingAreaList = this.data.multiArray[2] //更新种植区列表
          this.data.multiIndex[2] = 0 // 将区默认选择第一个
    
          this.setData({
            multiArray: this.data.multiArray,
            multiIndex: this.data.multiIndex
          })
          break
      }
    }
    

    当三列选择器的某列值改变时,页面从组件里接收到改变的列数(column)和该列被选择的值(columnValue)。对column进行判断,如果column=0,那么根据columnValue的值向后端请求回该省的市列表,并根据市列表的的第一位向后端请求回该市的区列表。如果column=1,那么那么根据columnValue的值向后端请求回该市的区列表。

    receiveMultiSelectorValue: function (e) {
          this.setData({
            multiIndex: e.detail.value
          })
          this.data.region[0] = this.data.multiArray[0][this.data.multiIndex[0]]
          this.data.region[1] = this.data.multiArray[1][this.data.multiIndex[1]]
          this.data.region[2] = this.data.multiArray[2][this.data.multiIndex[2]]
          this.setData({
            region: this.data.region
          })
          //console.log(this.data.region)
        }

    当用户确定三列选择器的选项时,页面从组件中接收到multiIndex的值,并对选项值进行更新。

    展开全文
  • 美团内部各个Android开发团队也在尝试和实践不同的组件化方案,并且在组件化通信框架上也有很多高质量的产出。最近,我们团队对美团零售收银和美团轻收银两款Android App进行了组件化改造。本文主要介绍我们的组件化...

    背景

    组件化作为Android客户端技术的一个重要分支,近年来一直是业界积极探索和实践的方向。美团内部各个Android开发团队也在尝试和实践不同的组件化方案,并且在组件化通信框架上也有很多高质量的产出。最近,我们团队对美团零售收银和美团轻收银两款Android App进行了组件化改造。本文主要介绍我们的组件化方案,希望对从事Android组件化开发的同学能有所启发。

    为什么要组件化

    近年来,为什么这么多团队要进行组件化实践呢?组件化究竟能给我们的工程、代码带来什么好处?我们认为组件化能够带来两个最大的好处:

    提高组件复用性

    可能有些人会觉得,提高复用性很简单,直接把需要复用的代码做成Android Module,打包AAR并上传代码仓库,那么这部分功能就能被方便地引入和使用。但是我们觉得仅仅这样是不够的,上传仓库的AAR库是否方便被复用,需要组件化的规则来约束,这样才能提高复用的便捷性。

    降低组件间的耦合

    我们需要通过组件化的规则把代码拆分成不同的模块,模块要做到高内聚、低耦合。模块间也不能直接调用,这需要组件化通信框架的支持。降低了组件间的耦合性可以带来两点直接的好处:第一,代码更便于维护;第二,降低了模块的Bug率。

    组件化之前的状态

    我们的目标是要对团队的两款App(美团零售收银、美团轻收银)进行组件化重构,那么这里先简单地介绍一下这两款应用的架构。总的来说,这两款应用的构架比较相似,主工程Module依赖Business Module,Business Module是各种业务功能的集合,Business Module依赖Service Module,Service Module依赖Platform Module,Service Module和Platform Module都对上层提供服务,有所不同的是Platform Module提供的服务更为基础,主要包括一些工具Utils和界面Widget,而Service Module提供各种功能服务,如KNB、位置服务、网络接口调用等。这样的话,Business Module就变得非常臃肿和繁杂,各种业务模块相互调用,耦合性很强,改业务代码时容易“牵一发而动全身”,即使改一小块业务代码,可能要连带修改很多相关的地方,不仅在代码层面不利于进行维护,而且对一个业务的修改很容易造成其他业务产生Bug。

    组件化之前的状态

    组件化方案调研

    为了得到最适合我们业态和构架的组件化方案,我们调研了业界开源的一些组件化方案和公司内部其他团队的组件化方案,在此做个总结。

    开源组件化方案调研

    我们调研了业界一些主流的开源组件化方案。

    号称业界首个支持渐进式组件化改造的Android组件化开源框架。无论页面跳转还是组件间调用,都采用CC统一的组件调用方式完成。

    得到的方案采用路由 + 接口下沉的方式,所有接口下沉到base中,组件中实现接口并在IApplicationLike中添加代码注册到Router中。

    组件间调用需指定同步实现还是异步实现,调用组件时统一拿到RouterResponse作为返回值,同步调用的时候用RouterResponse.getData()来获取结果,异步调用获取时需要自己维护线程。

    阿里推出的路由引擎,是一个路由框架,并不是完整的组件化方案,可作为组件化架构的通信引擎。

    聚美的路由引擎,在此基础上也有聚美的组件化实践方案,基本思想是采用路由 + 接口下沉的方式实现组件化。

    美团其他团队组件化方案调研

    • 美团收银ComponentCenter

    美团收银的组件化方案支持接口调用和消息总线两种方式,接口调用的方式需要构建CCPData,然后调用ComponentCenter.call,最后在统一的Callback中进行处理。消息总线方式也需要构建CCPData,最后调用ComponentCenter.sendEvent发送。美团收银的业务组件都打包成AAR上传至仓库,组件间存在相互依赖,这样导致mainapp引用这些组件时需要小心地exclude一些重复依赖。在我们的组件化方案中,我们采用了一种巧妙的方法来解决这个问题。

    • 美团App ServiceLoader

    美团App的组件化方案采用ServiceLoader的形式,这是一种典型的接口调用组件通信方式。用注解定义服务,获取服务时取得一个接口的List,判断这个List是否为空,如果不为空,则获取其中一个接口调用。

    • WMRouter

    美团外卖团队开发的一款Android路由框架,基于组件化的设计思路。主要提供路由、ServiceLoader两大功能。之前美团技术博客也发表过一篇WMRouter的介绍:《WMRouter:美团外卖Android开源路由框架》。WMRouter提供了实现组件化的两大基础设施框架:路由和组件间接口调用。支持和文档也很充分,可以考虑作为我们团队实现组件化的基础设施。

    组件化方案

    组件化基础框架

    在前期的调研工作中,我们发现外卖团队的WMRouter是一个不错的选择。首先,WMRouter提供了路由+ServiceLoader两大组件间通信功能,其次,WMRouter架构清晰,扩展性比较好,并且文档和支持也比较完备。所以我们决定了使用WMRouter作为组件化基础设施框架之一。然而,直接使用WMRouter有两个问题:

    1. 我们的项目已经在使用一个路由框架,如果使用WMRouter,需要把之前使用的路由框架改成WMRouter路由框架。
    2. WMRouter没有消息总线框架,我们调研的其他项目也没有适合我们项目的消息总线框架,因此我们需要开发一个能够满足我们需求的消息总线框架,这部分会在后面详细描述。

    组件化分层结构

    在参考了不同的组件化方案之后,我们采用了如下分层结构:

    1. App壳工程:负责管理各个业务组件和打包APK,没有具体的业务功能。
    2. 业务组件层:根据不同的业务构成独立的业务组件,其中每个业务组件包含一个Export Module和Implement Module。
    3. 功能组件层:对上层提供基础功能服务,如登录服务、打印服务、日志服务等。
    4. 组件基础设施:包括WMRouter,提供页面路由服务和ServiceLoader接口调用服务,以及后面会介绍的组件消息总线框架:modular-event。

    整体架构如下图所示:

    分层结构

    业务组件拆分

    我们调研其他组件化方案的时候,发现很多组件方案都是把一个业务模块拆分成一个独立的业务组件,也就是拆分成一个独立的Module。而在我们的方案中,每个业务组件都拆分成了一个Export Module和Implement Module,为什么要这样做呢?

    1. 避免循环依赖

    如果采用一个业务组件一个Module的方式,如果Module A需要调用Module B提供的接口,那么Module A就需要依赖Module。同时,如果Module B需要调用Module A的接口,那么Module B就需要依赖Module A。此时就会形成一个循环依赖,这是不允许的。

    循环依赖

    也许有些读者会说,这个好解决:可以把Module A和Module B要依赖的接口放到另一个Module中去,然后让Module A和Module B都去依赖这个Module就可以了。这确实是一个解决办法,并且有些项目组在使用这种把接口下沉的方法。

    但是我们希望一个组件的接口,是由这个组件自己提供,而不是放在一个更加下沉的接口里面,所以我们采用了把每个业务组件都拆分成了一个Export Module和Implement Module。这样的话,如果Module A需要调用Module B提供的接口,同时Module B需要调用Module A的接口,只需要Module A依赖Module B Export,Module B依赖Module A Export就可以了。

    组件结构

    1. 业务组件完全平等

    在使用单Module方案的组件化方案中,这些业务组件其实不是完全平等,有些被依赖的组件在层级上要更下沉一些。但是采用Export Module+Implement Module的方案,所有业务组件在层级上完全平等。

    1. 功能划分更加清晰

    每个业务组件都划分成了Export Module+Implement Module的模式,这个时候每个Module的功能划分也更加清晰。Export Module主要定义组件需要对外暴露的部分,主要包含:

    • 对外暴露的接口,这些接口用WMRouter的ServiceLoader进行调用。
    • 对外暴露的事件,这些事件利用消息总线框架modular-event进行订阅和分发。
    • 组件的Router Path,组件化之前的工程虽然也使用了Router框架,但是所有Router Path都是定义在了一个下沉Module的公有Class中。这样导致的问题是,无论哪个模块添加/删除页面,或是修改路由,都需要去修改这个公有的Class。设想如果组件化拆分之后,某个组件新增了页面,还要去一个外部的Java文件中新增路由,这显然难以接受,也不符合组件化内聚的目标。因此,我们把每个组件的Router Path放在组件的Export Module中,既可以暴露给其他组件,也可以做到每个组件管理自己的Router Path,不会出现所有组件去修改一个Java文件的窘境。

    Implement Module是组件实现的部分,主要包含:

    • 页面相关的Activity、Fragment,并且用WMRouter的注解定义路由。
    • Export Module中对外暴露的接口的实现。
    • 其他的业务逻辑。

    组件功能划分

    组件化消息总线框架modular-event

    前文提到的实现组件化基础设施框架中,我们用外卖团队的WMRouter实现页面路由和组件间接口调用,但是却没有消息总线的基础框架,因此,我们自己开发了一个组件化消息总线框架modular-event。

    为什么需要消息总线框架

    之前,我们开发过一个基于LiveData的消息总线框架:LiveDataBus,也在美团技术博客上发表过一篇文章来介绍这个框架:《Android消息总线的演进之路:用LiveDataBus替代RxBus、EventBus》。关于消息总线的使用,总是伴随着很多争论。有些人觉得消息总线很好用,有些人觉得消息总线容易被滥用。

    既然已经有了ServiceLoader这种组件间接口调用的框架,为什么还需要消息总线这种方式呢?主要有两个理由:

    1. 更进一步的解耦

    基于接口调用的ServiceLoader框架的确实现了解耦,但是消息总线能够实现更彻底的解耦。接口调用的方式调用方需要依赖这个接口并且知道哪个组件实现了这个接口。消息总线方式发送者只需要发送一个消息,根本不用关心是否有人订阅这个消息,这样发送者根本不需要了解其他组件的情况,和其他组件的耦合也就越少。

    1. 多对多的通信

    基于接口的方式只能进行一对一的调用,基于消息总线的方式能够提供多对多的通信。

    消息总线的优点和缺点

    总的来说,消息总线最大的优点就是解耦,因此很适合组件化这种需要对组件间进行彻底解耦的场景。然而,消息总线被很多人诟病的重要原因,也确实是因为消息总线容易被滥用。消息总线容易被滥用一般体现在几个场景:

    1. 消息难以溯源

    有时候我们在阅读代码的过程中,找到一个订阅消息的地方,想要看看是谁发送了这个消息,这个时候往往只能通过查找消息的方式去“溯源”。导致我们在阅读代码,梳理逻辑的过程不太连贯,有种被割裂的感觉。

    1. 消息发送比较随意,没有强制的约束

    消息总线在发送消息的时候一般没有强制的约束。无论是EventBus、RxBus或是LiveDataBus,在发送消息的时候既没有对消息进行检查,也没有对发送调用进行约束。这种不规范性在特定的时刻,甚至会带来灾难性的后果。比如订阅方订阅了一个名为login_success的消息,编写发送消息的是一个比较随意的程序员,没有把这个消息定义成全局变量,而是定义了一个临时变量String发送这个消息。不幸的是,他把消息名称login_success拼写成了login_seccess。这样的话,订阅方永远接收不到登录成功的消息,而且这个错误也很难被发现。

    组件化消息总线的设计目标

    1. 消息由组件自己定义

    以前我们在使用消息总线时,喜欢把所有的消息都定义到一个公共的Java文件里面。但是组件化如果也采用这种方案的话,一旦某个组件的消息发生变动,都会去修改这个Java文件。所以我们希望由组件自己来定义和维护消息定义文件。

    1. 区分不同组件定义的同名消息

    如果消息由组件定义和维护,那么有可能不同组件定义了重名的消息,消息总线框架需要能够区分这种消息。

    1. 解决前文提到的消息总线的缺点

    解决消息总线消息难以溯源和消息发送没有约束的问题。

    基于LiveData的消息总线

    之前的博文《Android消息总线的演进之路:用LiveDataBus替代RxBus、EventBus》详细阐述了如何基于LiveData构建消息总线。组件化消息总线框架modular-event同样会基于LiveData构建。使用LiveData构建消息总线有很多优点:

    1. 使用LiveData构建消息总线具有生命周期感知能力,使用者不需要调用反注册,相比EventBus和RxBus使用更为方便,并且没有内存泄漏风险。
    2. 使用普通消息总线,如果回调的时候Activity处于Stop状态,这个时候进行弹Dialog一类的操作就会引起崩溃。使用LiveData构建消息总线完全没有这个风险。

    组件消息总线modular-event的实现

    解决不同组件定义了重名消息的问题

    其实这个问题还是比较好解决的,实现的方式就是采用两级HashMap的方式解决。第一级HashMap的构建以ModuleName作为Key,第二级HashMap作为Value;第二级HashMap以消息名称EventName作为Key,LiveData作为Value。查找的时候先用组件名称ModuleName在第一级HashMap中查找,如果找到则用消息名EventName在第二级HashName中查找。整个结构如下图所示:

    消息总线结构

    对消息总线的约束

    我们希望消息总线框架有以下约束:

    1. 只能订阅和发送在组件中预定义的消息。换句话说,使用者不能发送和订阅临时消息。
    2. 消息的类型需要在定义的时候指定。
    3. 定义消息的时候需要指定属于哪个组件。

    如何实现这些约束

    1. 在消息定义文件上使用注解,定义消息的类型和消息所属Module。
    2. 定义注解处理器,在编译期间收集消息的相关信息。
    3. 在编译器根据消息的信息生成调用时需要的interface,用接口约束消息发送和订阅。
    4. 运行时构建基于两级HashMap的LiveData存储结构。
    5. 运行时采用interface+动态代理的方式实现真正的消息订阅和发送。

    整个流程如下图所示:

    实现流程

    消息总线modular-event的结构

    • modular-event-base:定义Anotation及其他基本类型
    • modular-event-core:modular-event核心实现
    • modular-event-compiler:注解处理器
    • modular-event-plugin:Gradle Plugin

    Anotation

    • @ModuleEvents:消息定义
    @Retention(RetentionPolicy.SOURCE)
    @Target(ElementType.TYPE)
    public @interface ModuleEvents {
        String module() default "";
    }
    
    • @EventType:消息类型
    @Retention(RetentionPolicy.SOURCE)
    @Target(ElementType.FIELD)
    public @interface EventType {
        Class value();
    }
    

    消息定义

    通过@ModuleEvents注解一个定义消息的Java类,如果@ModuleEvents指定了属性module,那么这个module的值就是这个消息所属的Module,如果没有指定属性module,则会把定义消息的Java类所在的包的包名作为消息所属的Module。

    在这个消息定义java类中定义的消息都是public static final String类型。可以通过@EventType指定消息的类型,@EventType支持java原生类型或自定义类型,如果没有用@EventType指定消息类型,那么消息的类型默认为Object,下面是一个消息定义的示例:

    //可以指定module,若不指定,则使用包名作为module名
    @ModuleEvents()
    public class DemoEvents {
    
        //不指定消息类型,那么消息的类型默认为Object
        public static final String EVENT1 = "event1";
    
        //指定消息类型为自定义Bean
        @EventType(TestEventBean.class)
        public static final String EVENT2 = "event2";
    
        //指定消息类型为java原生类型
        @EventType(String.class)
        public static final String EVENT3 = "event3";
    }
    

    interface自动生成

    我们会在modular-event-compiler中处理这些注解,一个定义消息的Java类会生成一个接口,这个接口的命名是EventsDefineOf+消息定义类名,例如消息定义类的类名为DemoEvents,自动生成的接口就是EventsDefineOfDemoEvents。消息定义类中定义的每一个消息,都会转化成接口中的一个方法。使用者只能通过这些自动生成的接口使用消息总线。我们用这种巧妙的方式实现了对消息总线的约束。前文提到的那个消息定义示例DemoEvents.java会生成一个如下的接口类:

    package com.sankuai.erp.modularevent.generated.com.meituan.jeremy.module_b_export;
    
    public interface EventsDefineOfDemoEvents extends com.sankuai.erp.modularevent.base.IEventsDefine {
      com.sankuai.erp.modularevent.Observable<java.lang.Object> EVENT1();
    
      com.sankuai.erp.modularevent.Observable<com.meituan.jeremy.module_b_export.TestEventBean> EVENT2(
          );
    
      com.sankuai.erp.modularevent.Observable<java.lang.String> EVENT3();
    }
    

    关于接口类的自动生成,我们采用了square/javapoet来实现,网上介绍JavaPoet的文章很多,这里就不再累述。

    使用动态代理实现运行时调用

    有了自动生成的接口,就相当于有了一个壳,然而壳下面的所有逻辑,我们通过动态代理来实现,简单介绍一下代理模式和动态代理:

    • 代理模式
      给某个对象提供一个代理对象,并由代理对象控制对于原对象的访问,即客户不直接操控原对象,而是通过代理对象间接地操控原对象。
    • 动态代理
      代理类是在运行时生成的。也就是说Java编译完之后并没有实际的class文件,而是在运行时动态生成的类字节码,并加载到JVM中。

    在动态代理的InvocationHandler中实现查找逻辑:

    1. 根据interface的typename得到ModuleName。
    2. 调用的方法的methodname即为消息名。
    3. 根据ModuleName和消息名找到相应的LiveData。
    4. 完成后续订阅消息或者发送消息的流程。

    消息的订阅和发送可以用链式调用的方式编码:

    • 订阅消息
    ModularEventBus
            .get()
            .of(EventsDefineOfModuleBEvents.class)
            .EVENT1()
            .observe(this, new Observer<TestEventBean>() {
                @Override
                public void onChanged(@Nullable TestEventBean testEventBean) {
                    Toast.makeText(MainActivity.this, "MainActivity收到自定义消息: " + testEventBean.getMsg(),
                            Toast.LENGTH_SHORT).show();
                }
            });
    
    • 发送消息
    ModularEventBus
            .get()
            .of(EventsDefineOfModuleBEvents.class)
            .EVENT1()
            .setValue(new TestEventBean("aa"));
    

    订阅和发送的模式

    • 订阅消息的模式
    1. observe:生命周期感知,onDestroy的时候自动取消订阅。
    2. observeSticky:生命周期感知,onDestroy的时候自动取消订阅,Sticky模式。
    3. observeForever:需要手动取消订阅。
    4. observeStickyForever:需要手动取消订阅,Sticky模式。
    • 发送消息的模式
    1. setValue:主线程调用。
    2. postValue:后台线程调用。

    组件化总结

    本文介绍了美团行业收银研发组Android团队的组件化实践,以及业界首创强约束组件消息总线modular-event的原理和使用。我们团队很早之前就在探索组件化改造,前期有些方案在落地的时候遇到很多困难。我们也研究了很多开源的组件化方案,以及公司内部其他团队(美团App、美团外卖、美团收银等)的组件化方案,学习和借鉴了很多优秀的设计思想,当然也踩过不少的坑。我们逐渐意识到:任何一种组件化方案都有其适用场景,我们的组件化架构选择,应该更加面向业务,而不仅仅是面向技术本身。

    后期工作展望

    我们的组件化改造工作远远没有结束,未来可能会在以下几个方向继续进行深入的研究:

    1. 组件管理:组件化改造之后,每个组件是个独立的工程,组件也会迭代开发,如何对这些组件进行版本化管理。
    2. 组件重用:现在看起来对这些组件的重用是很方便的,只需要引入组件的库即可,但是如果一个新的项目到来,需求有些变化,我们应该怎样最大限度的重用这些组件。
    3. CI集成:如何更好的与CI集成。
    4. 集成到脚手架:集成到脚手架,让新的项目从一开始就以组件化的模式进行开发。

    参考资料

    1. Android消息总线的演进之路:用LiveDataBus替代RxBus、EventBus
    2. WMRouter:美团外卖Android开源路由框架
    3. 美团外卖Android平台化架构演进实践

    作者简介

    海亮,美团高级工程师,2017年加入美团,目前主要负责美团轻收银、美团收银零售版等App的相关业务及模块开发工作。

    招聘

    美团餐饮生态诚招Android高级/资深工程师和技术专家,Base北京、成都,欢迎有兴趣的同学投递简历到chenyuxiang@meituan.com。

    展开全文
  • 可以说,EC2的出现的的确确改变了Infra的世界,改变了服务器行业的整个生态圈。用AWS官方的话说,“使用 Amazon EC2 可避免前期的硬件投入,因此您能够快速开发和部署应用程序。通过使用 Amazon EC2,您可以根据...

    要说最简单易用的AWS组件,可能是我们已经用过的存储组件S3。要说最核心的AWS组件,则非EC2莫属。所谓云计算嘛,计算组件才是其灵魂所在。因此我们在组件这一篇中,先不讲最简单的,而是先讲最重要的。

    EC2全称是亚马逊弹性计算云(Amazon Elastic Compute Cloud),在AWS整个云计算体系中,它负责提供的是安全而且可以随时启用并且自动调整大小的计算能力。

    这个说法,相当高大上。具体什么意思?

    1. 首先,所谓提供计算能力,就是虚拟机,虚拟服务器。以前我们想建个个人站点,需要购买并配置自己的服务器,现在不用了,直接去AWS上租用EC2。(相当于阿里云的ECS)
    2. 可以随时启用,意思是在需要时随时启动应用服务器,不用事先准备也无需任何承诺。——这是一种Disposable的计算资源。甚至你可以只使用1个小时,也没问题。——就好像Airbnb共享民宿一样共享服务器。共享同时,你还是拥有对计算资源的完全控制。
    3. 自动调整大小,云中提供的计算能力和计算容量可以自动调整,根据需求而自动扩展(或者收缩)。

    这种“随叫随到”的计算资源好在何处?让我们先来看看EC2这类云计算服务出现之前的世界是个啥样。
    没有EC2的时候,你要手工构建自己的Web服务器,你在服务器市场上进行各种各样的筛选,已经是大费周章了,终于选定一款,你下单购买,从下单到服务器运抵你的机房,应该是需要一周到几周的时间吧。

    然后,从设置到部署,从CPU到RAM,全都都要手工调整,等到真正用上这些服务器,时间很可能是“月”为计。

    想象一下:系统上线后,如果发现所购买的服务器并不完全满足我们的业务需求。。。

    很显然,这种服务器的手工部署方式不符合于当前从技术到商业模式都迅猛更新的时代环境。我们所需要的是:快、即时调整,迅速更新。

    可以说,EC2的出现的的确确改变了Infra的世界,改变了服务器行业的整个生态圈。用AWS官方的话说,“使用 Amazon EC2 可避免前期的硬件投入,因此您能够快速开发和部署应用程序。通过使用 Amazon EC2,您可以根据自身需要启动任意数量的虚拟服务器、配置安全和网络以及管理存储。Amazon EC2 允许您根据需要进行缩放以应对需求变化或流行高峰,降低流量预测需求。”
    在这里插入图片描述
    好了,既然我们不愿意回到原始社会,进行漫长的等待,冒着很大的危险,发现得到的东西并不适用。现在我们就进入AWS的管理控制台,选择EC2,那里有各种类型的虚拟运算服务器,有大有小,从弱到强,随时等待我们的召唤。几分钟之内,就能启动任意数量的服务器实例,而且也不用害怕启动起来的服务器不合心意,因为无论是类型,还是大小都可以随时调整。
    各种服务器类型
    咱们这就开始,在Service中,输入EC2进行查找,并选择该服务。实际上在All Services下面的第一个服务组Compute中,很容易发现EC2。
    在这里插入图片描述
    这就进入了EC2的仪表盘(Dashboard)界面,如下图所示。
    在这里插入图片描述
    这个界面的主区域的资源(Resource)面板中,给出了当前区域的服务器以及相关资源的状态列表,例如,可以看到正在运行的实例,为EC2实例分配的弹性IP地址(Elastic IPs),以及专用主机(Dedicated Hosts)快照和安全组(Security Groups)和置放群组(Placement Group)等诸多配置,这些我们以后都要介绍。现在我们没有启动任何实例,全部资源都是初始状态。

    资源面板下面还有其它一些面板,包括启动实例(Launch Instance)、服务运行状况(Service Health)可用区状态(Availability Zone Status)和计划事件(Scheduled events)等。

    而在仪表盘左侧的菜单栏中,还有需要与EC2服务器相关的设定,包括实例(Instances)、映像(Images)、Elastic Block Store、网络和安全(Network & Security)、负载均衡(Load Balancing)以及弹性伸缩(Auto Scaling)。

    这些,都是与云服务器EC2相关的重要设定,以后有机会再介绍。

    上图的界面中直接选择“启动实例”,开始启动一个新的EC2实例。在这个过程中,就可以顺便了解到EC2相关的基本概念。

    第一步,我们就需要选择一个AMI(Amazon Machine Image)作为服务器的“原型(prototype)”。
    在这里插入图片描述
    那么,什么是AMI呢?

    AMI是Amazon 系统映像 ,负责提供启动实例所需的信息。因此,在启动实例时,必须指定 AMI。他就好比是一个云服务器实例的原始样本。

    • 从同一个AMI,可以启动多个具有相同配置的实例。(这种需求在大规模并发式的系统中常见)
    • 需要不同的配置的实例时,就选择不同的AMI来启动实例。

    AMI 包括以下内容:

    • 一个或多个 EBS 快照,包含实例根卷的信息,如操作系统、应用程序服务器和应用程序;
    • 启动权限,用于控制哪些AWS账户可以使用AMI启动实例。
    • 块设备映射,用于指定启动实例时要附加到实例的卷。

    如何得到AMI: AWS自带有一系列的AMI,也可以从MarketPlace购买别人的AMI,或者创建自己的AMI。另外还有一种社区AMI(Community AMI)是免费的,有好人做出来分享给我们用。

    我们此处选择Amazon Linux 2 AMI之后,点击Next,就进入第二步,实例类型的选择界面。
    在这里插入图片描述
    什么是EC2实例类型,如何选择实例类型?今天写累了,明天再写。未完待续。不积跬步无以至千里,今天只是个开始,以后慢慢介绍更多内容。本文作者——黄佳,版权所有,转载务必注明出处及作者。

    下一篇文章:戳这里

    本文作者新书《零基础学机器学习》, 链接:https://item.jd.com/12763913.html
    在这里插入图片描述

    博客新手,写作不易,请点赞,请打赏。欢迎挑错,给您鞠躬。

    展开全文
  • EasyUI常用组件(基础)

    万次阅读 2016-07-15 10:36:24
    本文主要内容是介绍EasyUI的一些常用组件的使用,都是一些非常基础的知识,适合入门者学习,主要包括Base(基础)、Layout(布局)、菜单和按钮、表单、窗口、表格和树等的使用。要求完全掌握这些内容,学会查阅文档...
  • JS 地区、职业、行业选择控件

    热门讨论 2013-07-24 20:46:25
    完整的地区、职业、行业JS选择控件,带样式的哦~
  • 微擎模板函数、组件

    千次阅读 2017-09-22 16:46:28
    官方文档里模板函数写的很少只有几个!但这些组件直接调用很方便开发者开发,因此将其整理,方便查找调用!使用前请务必 load()->func('tpl'); 加载模板组件函数
  • 微擎自带组件 函数

    千次阅读 2018-04-19 15:33:10
    官方文档里模板函数写的很少只有几个!但这些组件直接调用很方便开发者开发,因此将其整理,方便查找调用!... * 【表单控件】: 范围日期选择器 * @param string $name 表单input名称 * @param ...
  • OpenStack组件详解

    千次阅读 2021-01-25 22:05:48
    用户不管理或者控制底层的云基础架构,但是可以控制操作系统、存储、发布应用程序,以及可能限度的控制选择的网络组件(例如,防火墙) 四:云计算发布模型 私有云:云基础架构被一个组织独立地操作,可能被这个组织...
  • RabbitMQ-基础组件封装

    千次阅读 2018-12-31 10:54:00
    基础组件封装思路和架构设计方案 支持高性能的序列化转换, 异步化发送消息 支持消息生产实例与消费实例的链接池化缓存化, 提升性能 支持可靠性投递消息, 保障消息100%不丢失 支持消费端的幂等操作, 避免消费...
  • 基于组件的开发思路

    千次阅读 2016-11-06 20:50:33
    1. 4个基本特性:组件组件之间的协同,组件插座,组件的使用者。 2. 高度的以体系结构为中心。 3. 业务组件5要素:组件粒度层次、体系结构视点、分布层、功能类(或层)和开发生命周期视点。 4. 五种粒度...
  • 大数据开源组件图谱

    万次阅读 2017-04-13 13:50:38
    是一种高吞吐量的分布式发布订阅消息系统,它可以处理消费者规模网站中的所有动作流数据,目前已成为大数据系统在异步和分布式消息之间的最佳选择。 数据处理   Spark 是一个高速、通用大数据计算处理引擎...
  • Microsoft Office 2010组件介绍

    千次阅读 2014-12-23 16:39:52
    由于许多初级开发人员未曾使用过或...每一代的Microsoft Office都有一个以上的版本,每个版本都根据使用者的实际需要,选择了不同的组件。 Word  Microsoft Word是文字处理软件。它被认为是Office的主要程序。它在文字
  • 下拉选择列表重排序组件 左滑删除组件 点赞组件 优惠券组件 右划菜单组件 仿 minUI 加载动画组件 语音播放组件 圆形进度条组件(模仿ElementUI 或者其他的) 已从weui拆分出的样式 weui-
  • 【本人性格不好,被惹到了必然句...互联网行业的薪资水准相对较高,刚入行一个月,半年,或者一年超过其他行业薪资很正常。 那么,互联网行业究竟有哪些职位呢,又分别适合哪些传统行业转型? 1.产品 2.UI 3.CSS...
  • MatLab 组件大全

    千次阅读 2016-02-03 07:30:00
    MATLAB 矩阵实验室 7.0.1 Simulink 仿真 6.1 Aerospace Blockset ...
  • 文章目录使用意义基础使用给组件添加属性单一根元素 使用意义 有时候一组HTML代码可能会在多出使用(比如页眉页脚)。如果我们把这样的代码到处都进行复制粘贴,虽然一时方便了,但后期维护代价非常高,而且代码重复性较...
  • 为减少永磁涡流耦合调速器由于结构...其次,连接板的数量选择8块时,在提高其低阶固有频率的同时,铜转子组件的力学变形和温升情况均可以达到最优化,上述研究结果可以为永磁涡流耦合传动器的减振降噪设计提供参考依据。
  • Web应用的组件化开发

    万次阅读 2014-06-14 21:54:33
    1. 为什么要做组件化? 无论前端也好,后端也好,都是整个软件体系的一部分。软件产品也是产品,它的研发过程也必然是有其目的。绝大多数软件产品是追逐利润的,在产品目标确定的情况下,成本有两个途径来优化:...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 70,331
精华内容 28,132
关键字:

行业选择组件