精华内容
下载资源
问答
  • 找到能立刻开始的下一步行动 |进击

    万次阅读 2017-02-22 06:58:48
    所谓“下一步行动”,就是某一件事情的一个可以直接去做的步骤。但是,怎么找到这个步骤呢?

    这是自我规划系列的第五篇文章,前面的在这里:
    1. 超实用的自我规划模型
    2. 怎么发现愿景和目标
    3. 目标分解:每年10万被动收入
    4. 你离心想事成只差一个计划

    在“你离心想事成只差一个计划”一文中我们得出了下面的计划:

    计划中的每一个任务,往往还可以拆分为一系列的行动,这些行动都完成,任务就完成了。而拆分出来的行动序列,第一个要做的行动,就是我们这里所说的下一步行动。

    当你完成了任务到行动的拆分,每个任务就都对应了一个唯一的、明确无误的下一步行动,这样我们就能“看到就知道怎么去做”。

    而我们上面的计划表中,有一些任务,其实还没有到“看到就知道怎么去做”这种程度,还没有与之对应的下一步行动。

    这次文章,我们就来找任务的下一步行动。在此之前,我们先来定义一下“什么是可执行的下一步行动”。

    可执行的下一步行动

    所谓“下一步行动”,就是某一件事情的下一个可以直接去做的步骤。

    参看《搞定:无压工作的艺术》和《别让猴子跳回背上》。

    如果你的脑袋里只装着一件事情——即下一步行动,就可以立刻开始工作,也能够专注、高效的完成这一步行动。问题是,很多人都知道要聚焦在下一步行动上,可往往不知道怎么找到可以执行的下一步行动。这也是我们的很多计划失败的重要原因。

    小强升职记》中介绍了撰写下一步行动的四个秘诀:

    • 动词开头。一个好的行动应该是以动词开头的,比如“打电话给某某”、“准备会议资料”、“回复E-mail”等,以动词开头才能保证它具备可执行性。
    • 内容清晰。比如“准备会议资料”,虽然是动词开头,但是描述得不是很清晰,“需要准备哪些资料”、“几点开会”、“会议上要提出什么问题”,这些都需要进一步落实。所以说这样的下一步行动是失败的。
    • 描述结果。在任务开始之前对想要的结果进行描述,描述得越清晰,产生的能量就越大。比如你这样:“早晨9点带着做好的计划书在1号会议室讨论营销计划,说服与会者认同我的营销方案。”
    • 设定开始时间、周期、最后期限。在设定了这三个和时间有关的属性之后,就可以更合理地安排自己的时间,把握行动的进度,照顾别人的时间。

    如果你能够按照上述四个秘诀来拟定“下一步行动”,就有90%的可能找到“可执行的下一步”。所谓“可执行的下一步”,往往是简单到你只需要迈出右脚就行了。假如你还要考虑到底是迈右脚还是迈左脚,就说明你的下一步存在未决因素,不能立刻开始。

    下面是几个下一步行动:

    1. 晚上7点前打电话给妈妈
    2. 早上9:30之前给经理发周报
    3. 3月1日前确认男主年龄

    寻找下一步行动

    以我们出版一部小说的计划为例,确定角色这个里程碑中,“(确定)主角”这个任务就没有对应的行动序列,当然也就没有下一步行动。

    我们先来拆分,看看主角可能包含什么。也许有这些维度:

    • 性别
    • 长相
    • 年龄
    • 职业
    • 性格
    • 家庭背景
    • 主角个数

    再来想,我们应该最先确认哪一个信息?性别?长相?还是性格?

    我觉得是主角个数。如果是爱情小说,最起码有两个主角,一男一女。如果是武侠小说,则可能有多个主角,类似天龙八部那种。

    确定了主角个数后,就可以来填写每一个主角的信息。

    所以,这里,我们找到的下一步行动是:确定主角个数,某某日之前。然后再来排序其它信息,最终有可能得出下面的行动序列(先假定我们要出版的是都市爱情小说):

    1. 确认主角个数
    2. 确认女主长相
    3. 确认女主年龄
    4. 确认女主职业
    5. 确认女主性格
    6. 确认女主家庭
    7. 确认男主职业
    8. 确认男主年龄
    9. 确认男主性格
    10. 确认男主家庭
    11. 确认男主长相

    当前的下一步行动完成后,立刻从上面的行动序列出挑出下一个下一步行动,往复循环,下一步,下一步,下一步……

    当我们完成这11个行动序列,确定主角这个任务就完成了。

    现在,确定主角这个任务,是不是看到就能开始做了?

    再看一个例子,情节主线这个任务中的“结尾”,来找找它的下一步行动。

    按我的理解,撰写结尾又可以拆分为下面的序列:

    • 确认结局是悲剧还是喜剧
    • 撰写最后一个场景

    这两个哪个是下一步?我觉得应该是“确认结局是悲剧还是喜剧”。假如我们确定结局为悲剧(爱情小说的结尾通常屈服于悲剧模式),那接下来撰写最后一个场景,可以立即开始吗?恐怕还是不能……它应该可以拆分为下面的行动序列:

    • 确认悲剧形式(女主挂、男主挂、男女主一起挂、男女主都活着但被父母或其它压力拆散)
    • 撰写最后一个故事

    这里面的下一步就很清晰了,肯定是“确认悲剧形式”。

    小结

    当我们拿到一个任务,多问问自己下面这两个问题,往往就能找到下一步行动:

    1. 要完成这个任务,需要经过哪些步骤
    2. 哪个步骤需要第一个做

    多问问,多想想,多琢磨琢磨,就会找到下一步行动,就能开始行动。

    好啦,我们的自我规划系列,到这里就结束了,希望这个系列文章对你有用。关注我的微信订阅号“程序视界”,阅读更多实用文章。


    推荐阅读:

    展开全文
  • 先讲一个小故事,说明理解力比执行力更重要。 关羽进账,“曹军来袭。” 刘备道,“你来断后。” 关羽手起刀落,斩了刘禅。...如何命令,员工才会行动,而且是正确的行动呢? 关键在于告知他如果构建解决方案......

    先来一个小故事,说明一下理解力比执行力更重要。

    关羽进账,“曹军来袭。”
    刘备道,“你来断后。”
    关羽手起刀落,斩了刘禅。
    刘备道,“不是这个。”
    关羽又一刀,劈了刘封。
    刘备气道,“也不是这个”
    关羽刀光一动,刘备腿间血光一闪。。。。

    如何下命令,员工才会行动,而且是正确的行动呢? 关键在于告知他如何构建解决方案,采用正确的沟通方法和步骤。如下方法取自《The solution tango: seven simple steps to solutions in management》一书。

    1)运用宽松的语言

    宽松的语言增加了合作的概率,避免了双方的对抗。宽松语言的例子包括“如果我们……你觉得怎么样?”“如果你选择……会怎么样?”等。


    2)谈论有效的部分

    表达对有效部分的兴趣会鼓励人们更多地讨论他们的资源——他们的优势在哪里,如何利用这些解决现在面临的问题。“在问题之外,什么还有效”,这种问题中的例外情况提供了部分解决方案,是开始构建解决方案的完美起点。

    谈论行之有效的方法,而不是问题之所在。


    3)分析问题解决的原因

    原因很简单——一旦我们确定了问题是如何结束的,就可以学会更快地终结问题。

    有效的办法是问“这样的讨论怎么结束?”“当你们没有冲突的时候,做法有什么不同?”“如果你们达成了一致,你们会做什么不一样的事情?”,关注如何走出困境,而不是问题是如何发生的。


    4)提出构建解决方案的问题

    提一个谨慎的问题自身就是一种智慧。提问题比告诉别人怎么做有更多益处,回答者对他的回答有责任感,因此他更有可能去做自己刚刚说过的事情。

    提出构建解决方案的问题,解决方案自会出现。


    结论从问题开始,构建解决方案的问题有多种,需要组合使用:

    (1)   澄清性问题: 获得更多的信息

    “要帮助我更正确地看待这个问题,你还有什么能告诉我的么?”“有什么我忘了问你,或者你忘了告诉我,但是又对这个问题很重要的事情么?”


    (2)   持续性问题:关注有效的部分

    “什么应该继续保持?”“你工作的哪一部分很顺利,你希望继续保持?”


    (3)   设定目标的问题:目标即结果

    “要让这次会议有用,我们需要讨论什么问题?”“你希望在会议结束之后知道什么有用的信息?”“在这次会议之后有什么改变会让你觉得开这次会议是值得的?”


    (4)   会议之前是否有改变的问题:进度的估算

    “从1到10的范围里,假设10代表问题完全解决,1代表你决定召开这次会议的状态,你认为现在是几?”


    (5)   区分性问题

    “有没有什么时候问题没那么严重”“那时候有什么不同么?你们做了什么不一样的事情?”


    (6)   聚焦资源的问题

    “虽然你们团队出现了问题,但有什么事情是做得比较好的?”“你们的团队最强大的资产是什么?”


    (7)   提出例外情况的问题

    “你有没有解决过类似的问题,你是怎么做的,谁帮助了你,他是怎么帮你的?”“在之前的危机中,你觉得最有用的是什么?”


    (8)   未来方向的问题

    “想象一下问题已经解决了,那时候和现在相比有什么不同呢?”“要解决这个问题,你会走出的很小的一步是什么?”“假设现在你已经走出了很小的那一步,下一个很小的一步你会怎么走?”


    (9)   三角问题

    “如果情况有了改善,你的上司说你们应该做出什么改变?”“你的同事看到什么会觉得这次会议是有用的?”


    (10)建设性问题

    “如果你尝试……是不是会有用?”“你觉得……会不会有用?”“如果你的同事……会怎么样?”“现在,做……怎么样?会有用么?”

     

    问题是解决方案的助产士。在讨论解决方案的细节时,提出的问题越细越好,多多益善。

    总之,忘掉为什么,多问怎么办。

    展开全文
  • Android组件化方案

    万次阅读 多人点赞 2017-02-15 19:01:52
    随着APP版本不断的迭代,新功能的不断增加,业务也会变的越来越复杂,APP业务模块的数量有可能还会继续增加,而且每个模块的代码也变的越来越多,这样发展下去单一工程的APP架构势必会影响开发效率,增加项目的...

    版权声明:本文为博主原创文章,欢迎大家转载!
    转载请标明出处: http://blog.csdn.net/guiying712/article/details/55213884 ,本文出自:【张华洋的博客】


    Android组件化项目地址:Android组件化项目AndroidModulePattern

    Android组件化之终极方案地址:http://blog.csdn.net/guiying712/article/details/78057120

    1、为什么要项目组件化

    随着APP版本不断的迭代,新功能的不断增加,业务也会变的越来越复杂,APP业务模块的数量有可能还会继续增加,而且每个模块的代码也变的越来越多,这样发展下去单一工程下的APP架构势必会影响开发效率,增加项目的维护成本,每个工程师都要熟悉如此之多的代码,将很难进行多人协作开发,而且Android项目在编译代码的时候电脑会非常卡,又因为单一工程下代码耦合严重,每修改一处代码后都要重新编译打包测试,导致非常耗时,最重要的是这样的代码想要做单元测试根本无从下手,所以必须要有更灵活的架构代替过去单一的工程架构。

    单一工程模型

    上图是目前比较普遍使用的Android APP技术架构,往往是在一个界面中存在大量的业务逻辑,而业务逻辑中充斥着各种网络请求、数据操作等行为,整个项目中也没有模块的概念,只有简单的以业务逻辑划分的文件夹,并且业务之间也是直接相互调用、高度耦合在一起的;

    单一工程模型下的业务关系

    上图单一工程模型下的业务关系,总的来说就是:你中有我,我中有你,相互依赖,无法分离。
    然而随着产品的迭代,业务越来越复杂,随之带来的是项目结构复杂度的极度增加,此时我们会面临如下几个问题:

    1、实际业务变化非常快,但是单一工程的业务模块耦合度太高,牵一发而动全身;
    2、对工程所做的任何修改都必须要编译整个工程;
    3、功能测试和系统测试每次都要进行;
    4、团队协同开发存在较多的冲突.不得不花费更多的时间去沟通和协调,并且在开发过程中,任何一位成员没办法专注于自己的功能点,影响开发效率;
    5、不能灵活的对业务模块进行配置和组装;

    为了满足各个业务模块的迭代而彼此不受影响,更好的解决上面这种让人头疼的依赖关系,就需要整改App的架构。


    2、如何组件化

    组件化工程模型

    上图是组件化工程模型,为了方便理解这张架构图,下面会列举一些组件化工程中用到的名词的含义:

    名词含义
    集成模式所有的业务组件被“app壳工程”依赖,组成一个完整的APP;
    组件模式可以独立开发业务组件,每一个业务组件就是一个APP;
    app壳工程负责管理各个业务组件,和打包apk,没有具体的业务功能;
    业务组件根据公司具体业务而独立形成一个的工程;
    功能组件提供开发APP的某些基础功能,例如打印日志、树状图等;
    Main组件属于业务组件,指定APP启动页面、主界面;
    Common组件属于功能组件,支撑业务组件的基础,提供多数业务组件需要的功能,例如提供网络请求功能;

    **
    Android APP组件化架构的目标是告别结构臃肿,让各个业务变得相对独立,业务组件在组件模式下可以独立开发,而在集成模式下又可以变为arr包集成到“app壳工程”中,组成一个完整功能的APP;
    从组件化工程模型中可以看到,业务组件之间是独立的,没有关联的,这些业务组件在集成模式下是一个个library,被app壳工程所依赖,组成一个具有完整业务功能的APP应用,但是在组件开发模式下,业务组件又变成了一个个application,它们可以独立开发和调试,由于在组件开发模式下,业务组件们的代码量相比于完整的项目差了很远,因此在运行时可以显著减少编译时间。

    组件化工程下的业务关系

    这是组件化工程模型下的业务关系,业务之间将不再直接引用和依赖,而是通过“路由”这样一个中转站间接产生联系,而Android中的路由实际就是对URL Scheme的封装;
    如此规模大的架构整改需要付出更高的成本,还会涉及一些潜在的风险,但是整改后的架构能够带来很多好处:

    1、加快业务迭代速度,各个业务模块组件更加独立,不再出现业务耦合情况;
    2、稳定的公共模块采用依赖库方式,提供给各个业务线使用,减少重复开发和维护工作量;
    3、迭代频繁的业务模块采用组件方式,各业务研发可以互不干扰、提升协作效率,并控制产品质量;
    4、为新业务随时集成提供了基础,所有业务可上可下,灵活多变;
    5、降低团队成员熟悉项目的成本,降低项目的维护难度;
    6、加快编译速度,提高开发效率;
    7、控制代码权限,将代码的权限细分到更小的粒度;


    3、组件化实施流程

    1)组件模式和集成模式的转换

    Android Studio中的Module主要有两种属性,分别为:

    1、application属性,可以独立运行的Android程序,也就是我们的APP;

    apply plugin: ‘com.android.application’

    2、library属性,不可以独立运行,一般是Android程序依赖的库文件;

    apply plugin: ‘com.android.library’

    Module的属性是在每个组件的 build.gradle 文件中配置的,当我们在组件模式开发时,业务组件应处于application属性,这时的业务组件就是一个 Android App,可以独立开发和调试;而当我们转换到集成模式开发时,业务组件应该处于 library 属性,这样才能被我们的“app壳工程”所依赖,组成一个具有完整功能的APP;

    但是我们如何让组件在这两种模式之间自动转换呢?总不能每次需要转换模式的时候去每个业务组件的 Gralde 文件中去手动把 Application 改成 library 吧?如果我们的项目只有两三个组件那么这个办法肯定是可行的,手动去改一遍也用不了多久,但是在大型项目中我们可能会有十几个业务组件,再去手动改一遍必定费时费力,这时候就需要程序员发挥下懒的本质了。

    试想,我们经常在写代码的时候定义静态常量,那么定义静态常量的目的什么呢?当一个常量需要被好几处代码引用的时候,把这个常量定义为静态常量的好处是当这个常量的值需要改变时我们只需要改变静态常量的值,其他引用了这个静态常量的地方都会被改变,做到了一次改变,到处生效;根据这个思想,那么我们就可以在我们的代码中的某处定义一个决定业务组件属性的常量,然后让所有业务组件的build.gradle都引用这个常量,这样当我们改变了常量值的时候,所有引用了这个常量值的业务组件就会根据值的变化改变自己的属性;可是问题来了?静态常量是用Java代码定义的,而改变组件属性是需要在Gradle中定义的,Gradle能做到吗?

    Gradle自动构建工具有一个重要属性,可以帮助我们完成这个事情。每当我们用AndroidStudio创建一个Android项目后,就会在项目的根目录中生成一个文件 gradle.properties,我们将使用这个文件的一个重要属性:在Android项目中的任何一个build.gradle文件中都可以把gradle.properties中的常量读取出来;那么我们在上面提到解决办法就有了实际行动的方法,首先我们在gradle.properties中定义一个常量值 isModule(是否是组件开发模式,true为是,false为否)

    # 每次更改“isModule”的值后,需要点击 "Sync Project" 按钮
    isModule=false

    然后我们在业务组件的build.gradle中读取 isModule,但是 gradle.properties 还有一个重要属性: gradle.properties 中的数据类型都是String类型,使用其他数据类型需要自行转换;也就是说我们读到 isModule 是个String类型的值,而我们需要的是Boolean值,代码如下:

    if (isModule.toBoolean()) {
        apply plugin: 'com.android.application'
    } else {
        apply plugin: 'com.android.library'
    }

    这样我们第一个问题就解决了,当然了 每次改变isModule的值后,都要同步项目才能生效;

    2)组件之间AndroidManifest合并问题

    在 AndroidStudio 中每一个组件都会有对应的 AndroidManifest.xml,用于声明需要的权限、Application、Activity、Service、Broadcast等,当项目处于组件模式时,业务组件的 AndroidManifest.xml 应该具有一个 Android APP 所具有的的所有属性,尤其是声明 Application 和要 launch的Activity,但是当项目处于集成模式的时候,每一个业务组件的 AndroidManifest.xml 都要合并到“app壳工程”中,要是每一个业务组件都有自己的 Application 和 launch的Activity,那么合并的时候肯定会冲突,试想一个APP怎么可能会有多个 Application 和 launch 的Activity呢?

    但是大家应该注意到这个问题是在组件开发模式和集成开发模式之间转换引起的问题,而在上一节中我们已经解决了组件模式和集成模式转换的问题,另外大家应该都经历过将 Android 项目从 Eclipse 切换到 AndroidStudio 的过程,由于 Android 项目在 Eclipse 和 AndroidStudio开发时 AndroidManifest.xml 文件的位置是不一样的,我们需要在build.gradle 中指定下 AndroidManifest.xml 的位置,AndroidStudio 才能读取到 AndroidManifest.xml,这样解决办法也就有了,我们可以为组件开发模式下的业务组件再创建一个 AndroidManifest.xml,然后根据isModule指定AndroidManifest.xml的文件路径,让业务组件在集成模式和组件模式下使用不同的AndroidManifest.xml,这样表单冲突的问题就可以规避了。

    业务组件的目录结构

    上图是组件化项目中一个标准的业务组件目录结构,首先我们在main文件夹下创建一个module文件夹用于存放组件开发模式下业务组件的 AndroidManifest.xml,而 AndroidStudio 生成的 AndroidManifest.xml 则依然保留,并用于集成开发模式下业务组件的表单;然后我们需要在业务组件的 build.gradle 中指定表单的路径,代码如下:

      sourceSets {
            main {
                if (isModule.toBoolean()) {
                    manifest.srcFile 'src/main/module/AndroidManifest.xml'
                } else {
                    manifest.srcFile 'src/main/AndroidManifest.xml'
                }
            }
        }

    这样在不同的开发模式下就会读取到不同的 AndroidManifest.xml ,然后我们需要修改这两个表单的内容以为我们不同的开发模式服务。

    首先是集成开发模式下的 AndroidManifest.xml,前面我们说过集成模式下,业务组件的表单是绝对不能拥有自己的 Application 和 launch 的 Activity的,也不能声明APP名称、图标等属性,总之app壳工程有的属性,业务组件都不能有,下面是一份标准的集成开发模式下业务组件的 AndroidManifest.xml:

    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.guiying.girls">
    
        <application android:theme="@style/AppTheme">
            <activity
                android:name=".main.GirlsActivity"
                android:screenOrientation="portrait" />
            <activity
                android:name=".girl.GirlActivity"
                android:screenOrientation="portrait"
                android:theme="@style/AppTheme.NoActionBar" />
        </application>
    
    </manifest>
    

    我在这个表单中只声明了应用的主题,而且这个主题还是跟app壳工程中的主题是一致的,都引用了common组件中的资源文件,在这里声明主题是为了方便这个业务组件中有使用默认主题的Activity时就不用再给Activity单独声明theme了。

    然后是组件开发模式下的表单文件:

    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.guiying.girls">
    
        <application
            android:name="debug.GirlsApplication"
            android:allowBackup="true"
            android:icon="@mipmap/ic_launcher"
            android:label="@string/girls_name"
            android:supportsRtl="true"
            android:theme="@style/AppTheme">
            <activity
                android:name=".main.GirlsActivity"
                android:screenOrientation="portrait">
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />
    
                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
            </activity>
            <activity
                android:name=".girl.GirlActivity"
                android:screenOrientation="portrait"
                android:theme="@style/AppTheme.NoActionBar" />
        </application>
    
    </manifest>

    组件模式下的业务组件表单就是一个Android项目普通的AndroidManifest.xml,这里就不在过多介绍了。


    3)全局Context的获取及组件数据初始化

    当Android程序启动时,Android系统会为每个程序创建一个 Application 类的对象,并且只创建一个,application对象的生命周期是整个程序中最长的,它的生命周期就等于这个程序的生命周期。在默认情况下应用系统会自动生成 Application 对象,但是如果我们自定义了 Application,那就需要在 AndroidManifest.xml 中声明告知系统,实例化的时候,是实例化我们自定义的,而非默认的。

    但是我们在组件化开发的时候,可能为了数据的问题每一个组件都会自定义一个Application类,如果我们在自己的组件中开发时需要获取 全局的Context,一般都会直接获取 application 对象,但是当所有组件要打包合并在一起的时候就会出现问题,因为最后程序只有一个 Application,我们组件中自己定义的 Application 肯定是没法使用的,因此我们需要想办法再任何一个业务组件中都能获取到全局的 Context,而且这个 Context 不管是在组件开发模式还是在集成开发模式都是生效的。

    在 组件化工程模型图中,功能组件集合中有一个 Common 组件, Common 有公共、公用、共同的意思,所以这个组件中主要封装了项目中需要的基础功能,并且每一个业务组件都要依赖Common组件,Common 组件就像是万丈高楼的地基,而业务组件就是在 Common 组件这个地基上搭建起来我们的APP的,Common 组件会专门在一个章节中讲解,这里只讲 Common组件中的一个功能,在Common组件中我们封装了项目中用到的各种Base类,这些基类中就有BaseApplication 类

    BaseApplication 主要用于各个业务组件和app壳工程中声明的 Application 类继承用的,只要各个业务组件和app壳工程中声明的Application类继承了 BaseApplication,当应用启动时 BaseApplication 就会被动实例化,这样从 BaseApplication 获取的 Context 就会生效,也就从根本上解决了我们不能直接从各个组件获取全局 Context 的问题;

    这时候大家肯定都会有个疑问?不是说了业务组件不能有自己的 Application 吗,怎么还让他们继承 BaseApplication 呢?其实我前面说的是业务组件不能在集成模式下拥有自己的 Application,但是这不代表业务组件也不能在组件开发模式下拥有自己的Application,其实业务组件在组件开发模式下必须要有自己的 Application 类,一方面是为了让 BaseApplication 被实例化从而获取 Context,还有一个作用是,业务组件自己的 Application 可以在组件开发模式下初始化一些数据,例如在组件开发模式下,A组件没有登录页面也没法登录,因此就无法获取到 Token,这样请求网络就无法成功,因此我们需要在A组件这个 APP 启动后就应该已经登录了,这时候组件自己的 Application 类就有了用武之地,我们在组件的 Application的 onCreate 方法中模拟一个登陆接口,在登陆成功后将数据保存到本地,这样就可以处理A组件中的数据业务了;另外我们也可以在组件Application中初始化一些第三方库

    但是,实际上业务组件中的Application在最终的集成项目中是没有什么实际作用的,组件自己的 Application 仅限于在组件模式下发挥功能,因此我们需要在将项目从组件模式转换到集成模式后将组件自己的Application剔除出我们的项目;在 AndroidManifest 合并问题小节中介绍了如何在不同开发模式下让 Gradle 识别组件表单的路径,这个方法也同样适用于Java代码;

    业务组件的java目录结构

    我们在Java文件夹下创建一个 debug 文件夹,用于存放不会在业务组件中引用的类,例如上图中的 NewsApplication ,你甚至可以在 debug 文件夹中创建一个Activity,然后组件表单中声明启动这个Activity,在这个Activity中不用setContentView,只需要在启动你的目标Activity的时候传递参数就行,这样就就可以解决组件模式下某些Activity需要getIntent数据而没有办法拿到的情况,代码如下;

    public class LauncherActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            request();
            Intent intent = new Intent(this, TargetActivity.class);
            intent.putExtra("name", "avcd");
            intent.putExtra("syscode", "023e2e12ed");
            startActivity(intent);
            finish();
        }
    
        //申请读写权限
        private void request() {
            AndPermission.with(this)
                    .requestCode(110)
                    .permission(Manifest.permission.WRITE_EXTERNAL_STORAGE,
                            Manifest.permission.CAMERA, Manifest.permission.READ_PHONE_STATE)
                    .callback(this)
                    .start();
        }
    
    }

    接下来在业务组件的 build.gradle 中,根据 isModule 是否是集成模式将 debug 这个 Java代码文件夹排除:

    
        sourceSets {
            main {
                if (isModule.toBoolean()) {
                    manifest.srcFile 'src/main/module/AndroidManifest.xml'
                } else {
                    manifest.srcFile 'src/main/AndroidManifest.xml'
                    //集成开发模式下排除debug文件夹中的所有Java文件
                    java {
                        exclude 'debug/**'
                    }
                }
            }
        }

    4)library依赖问题

    在介绍这一节的时候,先说一个问题,在组件化工程模型图中,多媒体组件和Common组件都依赖了日志组件,而A业务组件有同时依赖了多媒体组件和Common组件,这时候就会有人问,你这样搞岂不是日志组件要被重复依赖了,而且Common组件也被每一个业务组件依赖了,这样不出问题吗?

    其实大家完全没有必要担心这个问题,如果真有重复依赖的问题,在你编译打包的时候就会报错,如果你还是不相信的话可以反编译下最后打包出来的APP,看看里面的代码你就知道了。组件只是我们在代码开发阶段中为了方便叫的一个术语,在组件被打包进APP的时候是没有这个概念的,这些组件最后都会被打包成arr包,然后被app壳工程所依赖,在构建APP的过程中Gradle会自动将重复的arr包排除,APP中也就不会存在相同的代码了;

    但是虽然组件是不会重复了,但是我们还是要考虑另一个情况,我们在build.gradle中compile的第三方库,例如AndroidSupport库经常会被一些开源的控件所依赖,而我们自己一定也会compile AndroidSupport库 ,这就会造成第三方包和我们自己的包存在重复加载,解决办法就是找出那个多出来的库,并将多出来的库给排除掉,而且Gradle也是支持这样做的,分别有两种方式:根据组件名排除或者根据包名排除,下面以排除support-v4库为例:

    dependencies {
        compile fileTree(dir: 'libs', include: ['*.jar'])
        compile("com.jude:easyrecyclerview:$rootProject.easyRecyclerVersion") {
            exclude module: 'support-v4'//根据组件名排除
            exclude group: 'android.support.v4'//根据包名排除
        }
    }

    library重复依赖的问题算是都解决了,但是我们在开发项目的时候会依赖很多开源库,而这些库每个组件都需要用到,要是每个组件都去依赖一遍也是很麻烦的,尤其是给这些库升级的时候,为了方便我们统一管理第三方库,我们将给给整个工程提供统一的依赖第三方库的入口,前面介绍的Common库的作用之一就是统一依赖开源库,因为其他业务组件都依赖了Common库,所以这些业务组件也就间接依赖了Common所依赖的开源库。

    dependencies {
        compile fileTree(dir: 'libs', include: ['*.jar'])
        //Android Support
        compile "com.android.support:appcompat-v7:$rootProject.supportLibraryVersion"
        compile "com.android.support:design:$rootProject.supportLibraryVersion"
        compile "com.android.support:percent:$rootProject.supportLibraryVersion"
        //网络请求相关
        compile "com.squareup.retrofit2:retrofit:$rootProject.retrofitVersion"
        compile "com.squareup.retrofit2:retrofit-mock:$rootProject.retrofitVersion"
        compile "com.github.franmontiel:PersistentCookieJar:$rootProject.cookieVersion"
        //稳定的
        compile "com.github.bumptech.glide:glide:$rootProject.glideVersion"
        compile "com.orhanobut:logger:$rootProject.loggerVersion"
        compile "org.greenrobot:eventbus:$rootProject.eventbusVersion"
        compile "com.google.code.gson:gson:$rootProject.gsonVersion"
        compile "com.github.chrisbanes:PhotoView:$rootProject.photoViewVersion"
    
        compile "com.jude:easyrecyclerview:$rootProject.easyRecyclerVersion"
        compile "com.github.GrenderG:Toasty:$rootProject.toastyVersion"
    
        //router
        compile "com.github.mzule.activityrouter:activityrouter:$rootProject.routerVersion"
    }

    5)组件之间调用和通信

    在组件化开发的时候,组件之间是没有依赖关系,我们不能在使用显示调用来跳转页面了,因为我们组件化的目的之一就是解决模块间的强依赖问题,假如现在要从A业务组件跳转到业务B组件,并且要携带参数跳转,这时候怎么办呢?而且组件这么多怎么管理也是个问题,这时候就需要引入“路由”的概念了,由本文开始的组件化模型下的业务关系图可知路由就是起到一个转发的作用。

    这里我将介绍开源库的“ActivityRouter” ,有兴趣的同学情直接去ActivityRouter的Github主页学习:ActivityRouter,ActivityRouter支持给Activity定义 URL,这样就可以通过 URL 跳转到Activity,并且支持从浏览器以及 APP 中跳入我们的Activity,而且还支持通过 url 调用方法。下面将介绍如何将ActivityRouter集成到组件化项目中以实现组件之间的调用;

    1、首先我们需要在 Common 组件中的 build.gradle 将ActivityRouter 依赖进来,方便我们在业务组件中调用:

    dependencies {
        compile fileTree(dir: 'libs', include: ['*.jar'])
        //router
        compile "com.github.mzule.activityrouter:activityrouter:$rootProject.routerVersion"
    }

    2、这一步我们需要先了解 APT这个概念,APT(Annotation Processing Tool)是一种处理注解的工具,它对源代码文件进行检测找出其中的Annotation,使用Annotation进行额外的处理。 Annotation处理器在处理Annotation时可以根据源文件中的Annotation生成额外的源文件和其它的文件(文件具体内容由Annotation处理器的编写者决定),APT还会编译生成的源文件和原来的源文件,将它们一起生成class文件。在这里我们将在每一个业务组件的 build.gradle 都引入ActivityRouter 的 Annotation处理器,我们将会在声明组件和Url的时候使用,annotationProcessor是Android官方提供的Annotation处理器插件,代码如下:

    
    dependencies {
        compile fileTree(dir: 'libs', include: ['*.jar'])
        annotationProcessor "com.github.mzule.activityrouter:compiler:$rootProject.annotationProcessor"
    }

    3、接下来需要在 app壳工程的 AndroidManifest.xml 配置,到这里ActivityRouter配置就算完成了:

     <!--声明整个应用程序的路由协议-->
            <activity
                android:name="com.github.mzule.activityrouter.router.RouterActivity"
                android:theme="@android:style/Theme.NoDisplay">
                <intent-filter>
                    <action android:name="android.intent.action.VIEW" />
    
                    <category android:name="android.intent.category.DEFAULT" />
                    <category android:name="android.intent.category.BROWSABLE" />
    
                    <data android:scheme="@string/global_scheme" /> <!-- 改成自己的scheme -->
                </intent-filter>
            </activity>
            <!--发送崩溃日志界面-->

    4、接下来我们将声明项目中的业务组件,声明方法如下:

    @Module("girls")
    public class Girls {
    }

    在每一个业务组件的java文件的根目录下创建一个类,用 注解@Module 声明这个业务组件;
    然后在“app壳工程”的 应用Application 中使用 注解@Modules 管理我们声明的所有业务组件,方法如下:

    @Modules({"main", "girls", "news"})
    public class MyApplication extends BaseApplication {
    }

    到这里组件化项目中的所有业务组件就被声明和管理起来了,组件之间的也就可以互相调用了,当然前提是要给业务组件中的Activity定义 URL。

    5、例如我们给 Girls组件 中的 GirlsActivity 使用 注解@Router 定义一个 URL:“news”,方法如下:

    @Router("girls")
    public class GirlsActivity extends BaseActionBarActivity {
    
        private GirlsView mView;
        private GirlsContract.Presenter mPresenter;
    
        @Override
        protected int setTitleId() {
            return R.string.girls_activity_title;
        }
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            mView = new GirlsView(this);
            setContentView(mView);
            mPresenter = new GirlsPresenter(mView);
            mPresenter.start();
        }
    }

    然后我们就可以在项目中的任何一个地方通过 URL地址 : module://girls, 调用 GirlsActivity,方法如下:

           Routers.open(MainActivity.this, "module://girls");

    组件之间的调用解决后,另外需要解决的就是组件之间的通信,例如A业务组件中有消息列表,而用户在B组件中操作某个事件后会产生一条新消息,需要通知A组件刷新消息列表,这样业务场景需求可以使用Android广播来解决,也可以使用第三方的事件总线来实现,比如EventBus


    6)组件之间资源名冲突

    因为我们拆分出了很多业务组件和功能组件,在把这些组件合并到“app壳工程”时候就有可能会出现资源名冲突问题,例如A组件和B组件都有一张叫做“ic_back”的图标,这时候在集成模式下打包APP就会编译出错,解决这个问题最简单的办法就是在项目中约定资源文件命名规约,比如强制使每个资源文件的名称以组件名开始,这个可以根据实际情况和开发人员制定规则。当然了万能的Gradle构建工具也提供了解决方法,通过在在组件的build.gradle中添加如下的代码:

        //设置了resourcePrefix值后,所有的资源名必须以指定的字符串做前缀,否则会报错。
        //但是resourcePrefix这个值只能限定xml里面的资源,并不能限定图片资源,所有图片资源仍然需要手动去修改资源名。
        resourcePrefix "girls_"

    但是设置了这个属性后有个问题,所有的资源名必须以指定的字符串做前缀,否则会报错,而且resourcePrefix这个值只能限定xml里面的资源,并不能限定图片资源,所有图片资源仍然需要手动去修改资源名;所以我并不推荐使用这种方法来解决资源名冲突。


    4、组件化项目的工程类型

    在组件化工程模型中主要有:app壳工程、业务组件和功能组件3种类型,而业务组件中的Main组件和功能组件中的Common组件比较特殊,下面将分别介绍。

    1)app壳工程

    app壳工程是从名称来解释就是一个空壳工程,没有任何的业务代码,也不能有Activity,但它又必须被单独划分成一个组件,而不能融合到其他组件中,是因为它有如下几点重要功能:

    1、app壳工程中声明了我们Android应用的 Application,这个 Application 必须继承自 Common组件中的 BaseApplication(如果你无需实现自己的Application可以直接在表单声明BaseApplication),因为只有这样,在打包应用后才能让BaseApplication中的Context生效,当然你还可以在这个 Application中初始化我们工程中使用到的库文件,还可以在这里解决Android引用方法数不能超过 65535 的限制,对崩溃事件的捕获和发送也可以在这里声明。

    2、app壳工程的 AndroidManifest.xml 是我Android应用的根表单,应用的名称、图标以及是否支持备份等等属性都是在这份表单中配置的,其他组件中的表单最终在集成开发模式下都被合并到这份 AndroidManifest.xml 中。

    3、app壳工程的 build.gradle 是比较特殊的,app壳不管是在集成开发模式还是组件开发模式,它的属性始终都是:com.android.application,因为最终其他的组件都要被app壳工程所依赖,被打包进app壳工程中,这一点从组件化工程模型图中就能体现出来,所以app壳工程是不需要单独调试单独开发的。另外Android应用的打包签名,以及buildTypes和defaultConfig都需要在这里配置,而它的dependencies则需要根据isModule的值分别依赖不同的组件,在组件开发模式下app壳工程只需要依赖Common组件,或者为了防止报错也可以根据实际情况依赖其他功能组件,而在集成模式下app壳工程必须依赖所有在应用Application中声明的业务组件,并且不需要再依赖任何功能组件。

    下面是一份 app壳工程 的 build.gradle文件

    apply plugin: 'com.android.application'
    
    static def buildTime() {
        return new Date().format("yyyyMMdd");
    }
    
    android {
        signingConfigs {
            release {
                keyAlias 'guiying712'
                keyPassword 'guiying712'
                storeFile file('/mykey.jks')
                storePassword 'guiying712'
            }
        }
    
        compileSdkVersion rootProject.ext.compileSdkVersion
        buildToolsVersion rootProject.ext.buildToolsVersion
        defaultConfig {
            applicationId "com.guiying.androidmodulepattern"
            minSdkVersion rootProject.ext.minSdkVersion
            targetSdkVersion rootProject.ext.targetSdkVersion
            versionCode rootProject.ext.versionCode
            versionName rootProject.ext.versionName
            multiDexEnabled false
            //打包时间
            resValue "string", "build_time", buildTime()
        }
    
        buildTypes {
            release {
                //更改AndroidManifest.xml中预先定义好占位符信息
                //manifestPlaceholders = [app_icon: "@drawable/icon"]
                // 不显示Log
                buildConfigField "boolean", "LEO_DEBUG", "false"
                //是否zip对齐
                zipAlignEnabled true
                // 缩减resource文件
                shrinkResources true
                //Proguard
                minifyEnabled true
                proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
                //签名
                signingConfig signingConfigs.release
            }
    
            debug {
                //给applicationId添加后缀“.debug”
                applicationIdSuffix ".debug"
                //manifestPlaceholders = [app_icon: "@drawable/launch_beta"]
                buildConfigField "boolean", "LOG_DEBUG", "true"
                zipAlignEnabled false
                shrinkResources false
                minifyEnabled false
                debuggable true
            }
        }
    
    
    }
    
    dependencies {
        compile fileTree(dir: 'libs', include: ['*.jar'])
        annotationProcessor "com.github.mzule.activityrouter:compiler:$rootProject.annotationProcessor"
        if (isModule.toBoolean()) {
            compile project(':lib_common')
        } else {
            compile project(':module_main')
            compile project(':module_girls')
            compile project(':module_news')
        }
    }

    2)功能组件和Common组件

    功能组件是为了支撑业务组件的某些功能而独立划分出来的组件,功能实质上跟项目中引入的第三方库是一样的,功能组件的特征如下:

    1、功能组件的 AndroidManifest.xml 是一张空表,这张表中只有功能组件的包名;

    2、功能组件不管是在集成开发模式下还是组件开发模式下属性始终是: com.android.library,所以功能组件是不需要读取 gradle.properties 中的 isModule 值的;另外功能组件的 build.gradle 也无需设置 buildTypes ,只需要 dependencies 这个功能组件需要的jar包和开源库。

    下面是一份 普通 的功能组件的 build.gradle文件

    apply plugin: 'com.android.library'
    
    android {
        compileSdkVersion rootProject.ext.compileSdkVersion
        buildToolsVersion rootProject.ext.buildToolsVersion
    
        defaultConfig {
            minSdkVersion rootProject.ext.minSdkVersion
            targetSdkVersion rootProject.ext.targetSdkVersion
            versionCode rootProject.ext.versionCode
            versionName rootProject.ext.versionName
        }
    
    }
    
    dependencies {
        compile fileTree(dir: 'libs', include: ['*.jar'])
    }

    Common组件除了有功能组件的普遍属性外,还具有其他功能

    1、Common组件的 AndroidManifest.xml 不是一张空表,这张表中声明了我们 Android应用用到的所有使用权限 uses-permission 和 uses-feature,放到这里是因为在组件开发模式下,所有业务组件就无需在自己的 AndroidManifest.xm 声明自己要用到的权限了。

    2、Common组件的 build.gradle 需要统一依赖业务组件中用到的 第三方依赖库和jar包,例如我们用到的ActivityRouter、Okhttp等等。

    3、Common组件中封装了Android应用的 Base类和网络请求工具、图片加载工具等等,公用的 widget控件也应该放在Common 组件中;业务组件中都用到的数据也应放于Common组件中,例如保存到 SharedPreferences 和 DataBase 中的登陆数据;

    4、Common组件的资源文件中需要放置项目公用的 Drawable、layout、sting、dimen、color和style 等等,另外项目中的 Activity 主题必须定义在 Common中,方便和 BaseActivity 配合保持整个Android应用的界面风格统一。

    下面是一份 Common功能组件的 build.gradle文件

    apply plugin: 'com.android.library'
    
    android {
        compileSdkVersion rootProject.ext.compileSdkVersion
        buildToolsVersion rootProject.ext.buildToolsVersion
    
        defaultConfig {
            minSdkVersion rootProject.ext.minSdkVersion
            targetSdkVersion rootProject.ext.targetSdkVersion
            versionCode rootProject.ext.versionCode
            versionName rootProject.ext.versionName
        }
    
    }
    
    dependencies {
        compile fileTree(dir: 'libs', include: ['*.jar'])
        //Android Support
        compile "com.android.support:appcompat-v7:$rootProject.supportLibraryVersion"
        compile "com.android.support:design:$rootProject.supportLibraryVersion"
        compile "com.android.support:percent:$rootProject.supportLibraryVersion"
        //网络请求相关
        compile "com.squareup.retrofit2:retrofit:$rootProject.retrofitVersion"
        compile "com.squareup.retrofit2:retrofit-mock:$rootProject.retrofitVersion"
        compile "com.github.franmontiel:PersistentCookieJar:$rootProject.cookieVersion"
        //稳定的
        compile "com.github.bumptech.glide:glide:$rootProject.glideVersion"
        compile "com.orhanobut:logger:$rootProject.loggerVersion"
        compile "org.greenrobot:eventbus:$rootProject.eventbusVersion"
        compile "com.google.code.gson:gson:$rootProject.gsonVersion"
        compile "com.github.chrisbanes:PhotoView:$rootProject.photoViewVersion"
    
        compile "com.jude:easyrecyclerview:$rootProject.easyRecyclerVersion"
        compile "com.github.GrenderG:Toasty:$rootProject.toastyVersion"
    
        //router
        compile "com.github.mzule.activityrouter:activityrouter:$rootProject.routerVersion"
    }
    

    2)业务组件和Main组件

    业务组件就是根据业务逻辑的不同拆分出来的组件,业务组件的特征如下:

    1、业务组件中要有两张AndroidManifest.xml,分别对应组件开发模式和集成开发模式,这两张表的区别请查看 组件之间AndroidManifest合并问题 小节。

    2、业务组件在集成模式下是不能有自己的Application的,但在组件开发模式下又必须实现自己的Application并且要继承自Common组件的BaseApplication,并且这个Application不能被业务组件中的代码引用,因为它的功能就是为了使业务组件从BaseApplication中获取的全局Context生效,还有初始化数据之用。

    3、业务组件有debug文件夹,这个文件夹在集成模式下会从业务组件的代码中排除掉,所以debug文件夹中的类不能被业务组件强引用,例如组件模式下的 Application 就是置于这个文件夹中,还有组件模式下开发给目标 Activity 传递参数的用的 launch Activity 也应该置于 debug 文件夹中;

    4、业务组件必须在自己的 Java文件夹中创建业务组件声明类,以使 app壳工程 中的 应用Application能够引用,实现组件跳转,具体请查看 组件之间调用和通信 小节;

    5、业务组件必须在自己的 build.gradle 中根据 isModule 值的不同改变自己的属性,在组件模式下是:com.android.application,而在集成模式下com.android.library;同时还需要在build.gradle配置资源文件,如 指定不同开发模式下的AndroidManifest.xml文件路径,排除debug文件夹等;业务组件还必须在dependencies中依赖Common组件,并且引入ActivityRouter的注解处理器annotationProcessor,以及依赖其他用到的功能组件。

    下面是一份普通业务组件的 build.gradle文件

    if (isModule.toBoolean()) {
        apply plugin: 'com.android.application'
    } else {
        apply plugin: 'com.android.library'
    }
    
    android {
        compileSdkVersion rootProject.ext.compileSdkVersion
        buildToolsVersion rootProject.ext.buildToolsVersion
    
        defaultConfig {
            minSdkVersion rootProject.ext.minSdkVersion
            targetSdkVersion rootProject.ext.targetSdkVersion
            versionCode rootProject.ext.versionCode
            versionName rootProject.ext.versionName
        }
    
        sourceSets {
            main {
                if (isModule.toBoolean()) {
                    manifest.srcFile 'src/main/module/AndroidManifest.xml'
                } else {
                    manifest.srcFile 'src/main/AndroidManifest.xml'
                    //集成开发模式下排除debug文件夹中的所有Java文件
                    java {
                        exclude 'debug/**'
                    }
                }
            }
        }
    
        //设置了resourcePrefix值后,所有的资源名必须以指定的字符串做前缀,否则会报错。
        //但是resourcePrefix这个值只能限定xml里面的资源,并不能限定图片资源,所有图片资源仍然需要手动去修改资源名。
        //resourcePrefix "girls_"
    
    
    }
    
    dependencies {
        compile fileTree(dir: 'libs', include: ['*.jar'])
        annotationProcessor "com.github.mzule.activityrouter:compiler:$rootProject.annotationProcessor"
        compile project(':lib_common')
    }

    Main组件除了有业务组件的普遍属性外,还有一项重要功能

    1、Main组件集成模式下的AndroidManifest.xml是跟其他业务组件不一样的,Main组件的表单中声明了我们整个Android应用的launch Activity,这就是Main组件的独特之处;所以我建议SplashActivity、登陆Activity以及主界面都应属于Main组件,也就是说Android应用启动后要调用的页面应置于Main组件。

            <activity
                android:name=".splash.SplashActivity"
                android:launchMode="singleTop"
                android:screenOrientation="portrait"
                android:theme="@style/SplashTheme">
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />
    
                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
            </activity>

    5、组件化项目的混淆方案

    组件化项目的Java代码混淆方案采用在集成模式下集中在app壳工程中混淆,各个业务组件不配置混淆文件。集成开发模式下在app壳工程中build.gradle文件的release构建类型中开启混淆属性,其他buildTypes配置方案跟普通项目保持一致,Java混淆配置文件也放置在app壳工程中,各个业务组件的混淆配置规则都应该在app壳工程中的混淆配置文件中添加和修改。

    之所以不采用在每个业务组件中开启混淆的方案,是因为 组件在集成模式下都被 Gradle 构建成了 release 类型的arr包,一旦业务组件的代码被混淆,而这时候代码中又出现了bug,将很难根据日志找出导致bug的原因;另外每个业务组件中都保留一份混淆配置文件非常不便于修改和管理,这也是不推荐在业务组件的 build.gradle 文件中配置 buildTypes (构建类型)的原因。


    6、工程的build.gradle和gradle.properties文件

    1)组件化工程的build.gradle文件

    在组件化项目中因为每个组件的 build.gradle 都需要配置 compileSdkVersion、buildToolsVersion和defaultConfig 等的版本号,而且每个组件都需要用到 annotationProcessor,为了能够使组件化项目中的所有组件的 build.gradle 中的这些配置都能保持统一,并且也是为了方便修改版本号,我们统一在Android工程根目录下的build.gradle中定义这些版本号,当然为了方便管理Common组件中的第三方开源库的版本号,最好也在这里定义这些开源库的版本号,然后在各个组件的build.gradle中引用Android工程根目录下的build.gradle定义的版本号,组件化工程的 build.gradle 文件代码如下:

    buildscript {
        repositories {
            jcenter()
            mavenCentral()
        }
    
        dependencies {
            //classpath "com.android.tools.build:gradle:$localGradlePluginVersion"
            //$localGradlePluginVersion是gradle.properties中的数据
            classpath "com.android.tools.build:gradle:$localGradlePluginVersion"
        }
    }
    
    allprojects {
        repositories {
            jcenter()
            mavenCentral()
            //Add the JitPack repository
            maven { url "https://jitpack.io" }
            //支持arr包
            flatDir {
                dirs 'libs'
            }
        }
    }
    
    task clean(type: Delete) {
        delete rootProject.buildDir
    }
    
    // Define versions in a single place
    //时间:2017.2.13;每次修改版本号都要添加修改时间
    ext {
        // Sdk and tools
        //localBuildToolsVersion是gradle.properties中的数据
        buildToolsVersion = localBuildToolsVersion
        compileSdkVersion = 25
        minSdkVersion = 16
        targetSdkVersion = 25
        versionCode = 1
        versionName = "1.0"
        javaVersion = JavaVersion.VERSION_1_8
    
        // App dependencies version
        supportLibraryVersion = "25.3.1"
        retrofitVersion = "2.1.0"
        glideVersion = "3.7.0"
        loggerVersion = "1.15"
        eventbusVersion = "3.0.0"
        gsonVersion = "2.8.0"
        photoViewVersion = "2.0.0"
    
        //需检查升级版本
        annotationProcessor = "1.1.7"
        routerVersion = "1.2.2"
        easyRecyclerVersion = "4.4.0"
        cookieVersion = "v1.0.1"
        toastyVersion = "1.1.3"
    }
    

    2)组件化工程的gradle.properties文件

    在组件化实施流程中我们了解到gradle.properties有两个属性对我们非常有用:

    1、在Android项目中的任何一个build.gradle文件中都可以把gradle.properties中的常量读取出来,不管这个build.gradle是组件的还是整个项目工程的build.gradle;

    2、gradle.properties中的数据类型都是String类型,使用其他数据类型需要自行转换;

    利用gradle.properties的属性不仅可以解决集成开发模式和组件开发模式的转换,而且还可以解决在多人协同开发Android项目的时候,因为开发团队成员的Android开发环境(开发环境指Android SDK和AndroidStudio)不一致而导致频繁改变线上项目的build.gradle配置。

    在每个Android组件的 build.gradle 中有一个属性:buildToolsVersion,表示构建工具的版本号,这个属性值对应 AndroidSDK 中的 Android SDK Build-tools,正常情况下 build.gradle 中的 buildToolsVersion 跟你电脑中 Android SDK Build-tools 的最新版本是一致的,比如现在 Android SDK Build-tools 的最新的版本是:25.0.3,那么我的Android项目中 build.gradle 中的 buildToolsVersion 版本号也是 25.0.3,但是一旦一个Android项目是由好几个人同时开发,总会出现每个人的开发环境 Android SDK Build-tools 是都是不一样的,并不是所有人都会经常升级更新 Android SDK,而且代码是保存到线上环境的(例如使用 SVN/Git 等工具),某个开发人员提交代码后线上Android项目中 build.gradle 中的 buildToolsVersion 也会被不断地改变。

    另外一个原因是因为Android工程的根目录下的 build.gradle 声明了 Android Gradle 构建工具,而这个工具也是有版本号的,而且 Gradle Build Tools 的版本号跟 AndroidStudio 版本号一致的,但是有些开发人员基本很久都不会升级自己的 AndroidStudio 版本,导致团队中每个开发人员的 Gradle Build Tools 的版本号也不一致。

    如果每次同步代码后这两个工具的版本号被改变了,开发人员可以自己手动改回来,并且不要把改动工具版本号的代码提交到线上环境,这样还可以勉强继续开发;但是很多公司都会使用持续集成工具(例如Jenkins)用于持续的软件版本发布,而Android出包是需要 Android SDK Build-tools 和 Gradle Build Tools 配合的,一旦提交到线上的版本跟持续集成工具所依赖的Android环境构建工具版本号不一致就会导致Android打包失败。

    为了解决上面问题就必须将Android项目中 build.gradle 中的 buildToolsVersion 和 GradleBuildTools 版本号从线上代码隔离出来,保证线上代码的 buildToolsVersion 和 Gradle Build Tools 版本号不会被人为改变。

    具体的实施流程大家可以查看我的这篇博文 AndroidStudio本地化配置gradle的buildToolsVersion和gradleBuildTools


    7、组件化项目Router的其他方案-ARouter

    在组件化项目中使用到的跨组件跳转库ActivityRouter可以使用阿里巴巴的开源路由项目:阿里巴巴ARouter

    ActivityRouter和ARouter的接入组件化项目的方式是一样的,ActivityRouter提供的功能目前ARouter也全部支持,但是ARouter还支持依赖注入解耦,页面、拦截器、服务等组件均会自动注册到框架。对于大家来说,没有最好的只有最适合的,大家可以根据自己的项目选择合适的Router。

    下面将介绍ARouter的基础使用方法,更多功能还需大家去Github自己学习;

    1、首先 ARouter 这个框架是需要初始化SDK的,所以你需要在“app壳工程”中的应用Application中加入下面的代码,注意:在 debug 模式下一定要 openDebug

        if (BuildConfig.DEBUG) {
                //一定要在ARouter.init之前调用openDebug
                ARouter.openDebug();
                ARouter.openLog();
           }
           ARouter.init(this);

    2、首先我们依然需要在 Common 组件中的 build.gradle 将ARouter 依赖进来,方便我们在业务组件中调用:

    dependencies {
        compile fileTree(dir: 'libs', include: ['*.jar'])
        //router
        compile 'com.alibaba:arouter-api:1.2.1.1'
    }

    3、然后在每一个业务组件的 build.gradle 都引入ARouter 的 Annotation处理器,代码如下:

    
    android {
        defaultConfig {
        ...
        javaCompileOptions {
            annotationProcessorOptions {
            arguments = [ moduleName : project.getName() ]
            }
        }
        }
    }
    
    dependencies {
        compile fileTree(dir: 'libs', include: ['*.jar'])
        annotationProcessor 'com.alibaba:arouter-compiler:1.0.3'
    }

    4、由于ARouter支持自动注册到框架,所以我们不用像ActivityRouter那样在各个组件中声明组件,当然更不需要在Application中管理组件了。 我们给 Girls组件 中的 GirlsActivity 添加注解:@Route(path = “/girls/list”),需要注意的是这里的路径至少需要有两级,/xx/xx,之所以这样是因为ARouter使用了路径中第一段字符串(/*/)作为分组,比如像上面的”girls”,而分组这个概念就有点类似于ActivityRouter中的组件声明 @Module ,代码如下:

    @Route(path = "/girls/list")
    public class GirlsActivity extends BaseActionBarActivity {
    
        private GirlsView mView;
        private GirlsContract.Presenter mPresenter;
    
        @Override
        protected int setTitleId() {
            return R.string.girls_activity_title;
        }
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            mView = new GirlsView(this);
            setContentView(mView);
            mPresenter = new GirlsPresenter(mView);
            mPresenter.start();
        }
    }

    然后我们就可以在项目中的任何一个地方通过 URL地址 : /girls/list, 调用 GirlsActivity,方法如下:

          ARouter.getInstance().build("/girls/list").navigation();

    8、结束语

    组件化相比于单一工程优势是显而易见的:

    1. 组件模式下可以加快编译速度,提高开发效率;
    2. 自由选择开发框架(MVC /MVP / MVVM /);
    3. 方便做单元测试;
    4. 代码架构更加清晰,降低项目的维护难度;
    5. 适合于团队开发;

    最后贴出Android组件化Demo地址:Android组件化项目AndroidModulePattern

    想要学习更多Android组件化知识,请查看 :Android组件化之终极方案


    感谢以下文章提供的帮助
    1. http://www.cnblogs.com/chenxibobo/p/6187954.html
    2. https://kymjs.com/code/2016/10/18/01/
    3. https://zhuanlan.zhihu.com/p/23388989
    4. https://zhuanlan.zhihu.com/p/23147164?refer=moduth

    感谢以下开源项目
    1. https://github.com/mzule/ActivityRouter
    2. https://github.com/alibaba/ARouter

    展开全文
  • 摘要:BlinkOn3会议11月份刚在Google的MountainView办公地点举行,本文选取了BlinkOn3的几个话题,如为Blink绘图瘦身的SlimmingPaint,多优先级的Blink调度...和大家一起分享Blink项目目前取得的进展和下一步发展方向。

    摘要:BlinkOn3会议11月份刚在Google的MountainView办公地点举行,本文选取了BlinkOn3的几个话题,如为Blink绘图瘦身的SlimmingPaint,多优先级的Blink调度器,Oilpan垃圾回收机制,用JavaScript开发新的DOM特性等等,和大家一起分享Blink项目目前取得的进展和下一步发展方向。

    原创文章,转载请以链接形式注明原始出处http://blog.csdn.net/hongbomin/article/details/41091679.

    Blink最新技术进展

    根据BlinkOn3会议讨论的话题,我们大致可以了解到Blink项目的最新进展和下一步的发展方向,总体上来说,优化Blink在移动平台的图形性能和内存消耗方面仍然是目前Blink项目的重中之重,也是Chromium整个项目的重中之重,此外,提升WebApp使用体验、提高代码安全性和健壮性方面以及持续的代码重构,一直以来都备受重视。这里我们选取了其中几个话题,尝试从所要解决的问题及其采用方案的角度对其作简要分析。

    Slimming Paint: 将绘图的瘦身行动进行到底

    Slimming是减肥的意思,这个词用的很形象。一直以来Blink项目都努力提高绘图的性能,比如引入实现端绘图(--enable-impl-side-painting)技术将页面内容的绘制工作从主线程中解放出来,由Compositor线程将繁重的绘制任务安排在单独的线程中进行,而主线程仅仅需要把页面内容的绘图命令录制成SkPicture,交给Compositor线程就可以了,比起将内容直接绘制成像素,录制命令的开销要小的多,尽管实现端绘图可有效地减轻主线程的负担,但纵观整个Blink的渲染流水线,还存在不少问题。

    首先,Blink页面中的渲染层管理问题。Blink一直沿用了早期WebKit硬件加速的页面渲染框架,为了使页面能够按正确的次序显示页面中重叠、半透明、CSS动画等内容,Blink会将页面分层多个RenderLayer构建RenderLayer树,每个RenderObject都属于一个RenderLayer,但一个RenderLayer可能包含多个RenderObject。从页面的合成角度看,出于内存方面的考虑,不是每个RenderLayer都需要自己独立的后端存储,所以便有GraphicsLayer,RenderLayer与GraphicsLayer的关系类似于RenderObject与RenderLayer的关系,也就是,一个RenderLayer必定属于GraphicsLayer,而GraphicsLayer可能包含多个RenderLayer。在页面发生更新时,Blink需要做的事情是遍历整个RenderLayer树,计算出有哪些RenderLayer需要更新,再调用RenderLayer::paint将更新的内容绘制到所属的GraphicsLayer上。不难看出,Blink不仅要计算更新的区域,还要维护RenderLayer树以及GraphicsLayer树。

    其次,页面存在潜在的基本合成问题。对于以下的HTML页面,将会得到错误的合成结果:

    <html>
    <body>
      <canvas id="canvas"style="background:red;width:100px;height:100px"></canvas>
      <div id="overlap"style="background:green;margin-top:-50px;margin-left:50px;width:100px;height:100px"></div>
    </body>
    </html>

    正确的合成结果应该是绿色的矩形叠加在红色的矩形之上。但Blink的输出是,红色的总是在绿色之上,原因就是红色的<canvas>元素有自己独立的RenderLayer,而<div>元素没有独立的RenderLayer,而是与<body>共享同一个RenderLayer,<div>元素本身是不会参与overlap检测的。

    Slimming Paint目标就是要重写整个Blink和Chromium的渲染流水线,解决上述提到的两个问题,同时提高命令录制和绘制的性能。这是一次比较大的架构调整,Blink代码关于绘图方面的逻辑会有比较大的改动:

    • Paint操作将会从RenderObject移到一个专门的Painter中执行;
    • RenderLayer和GraphicsLayer将被全部删除,取而代之的PaintList;
    • Compositor将PaintList执行分层算法进行层化(Layerization) 操作;

    大致的流程如下图所示:


    在新的渲染流水线中,Blink不用再维护RenderLayer和GraphicsLayer,唯一要做的是,通过每个RenderObject的Paint生成整个页面内容的PaintList和为Compositor提供必要的分层提示信息。PaintList类似于DisplayList,包含了绘制次序,绘制命令,变换矩阵,Clip区域等信息,而Compositor从这些信息中完全能够计算出如何对页面进行分层。

    Blink Scheduler:优先级轮转的任务队列

    Blink Scheduler是由Google伦敦团队主导的一项优化,旨在解决任务队列的“拥堵”问题,简而言之,一些高优先级的任务被一些优先级低、执行时间过长的任务阻塞,导致高优先级的任务延迟执行,从而影响整体的流畅度。

    Blink主线程的任务来自系统中各个模块,如下图所示,所有的这些任务都是由主消息循环统一调度。每当向消息循环发布一个任务时, 这个任务会被添加到任务队列的队尾,消息循环会按照先进先出(FIFO)的顺序依次从任务队列中取出一个任务,然后再执行,直到任务队列为空。所以,如果一个高优先级的任务到达消息循环后,它必须等待在它之前所有的任务都执行完毕后才有机会得以执行。


    Blink Scheduler通过引入多优先级任务队列,正在尝试解决上述问题。每个任务都有一个优先级,消息循环按照优先级将任务添加到相应的队列中,如果单纯只是对每个任务指定一个优先级,可能带来一个问题就是如何解决任务之间的依赖性,为此,Blink将任务划分为三个类别:1)Compositor;2)Default;3)Idle,其中Compositor任务的优先级最高,Default任务次之,Idle任务优先级最低。按优先级执行任务。然而,如果静态地指定Compositor任务总是最高的,页面加载的性能降低了14%,显然也是不可取的。

    所以,Blink Scheduler采用了上下文感知的动态优先级策略,例如,

    • 当用户交互时,Compositor任务更重要,而资源加载任务没有那么重要,提升Compositor任务优先级至最高;
    • 当浏览页面时,资源加载任务变得重要了,而Compositor任务次之,提升Default任务优先级至最高;
    • 当开始新的一帧时,Compositor任务和Default任务具有相同的优先级;

    在实现方面,Blink Scheduler对Chromium的base/message_loop底层代码将会有较大的改动,还会在content层次上增加对BlinkScheduler的抽象,实现多优先级任务队列的调度,支持将任务发布到不同的任务队列上,支持选择下一个待处理的任务队列,支持动态修改优先级策略等,如下图所示:


    Oilpan:Blink专属的自动垃圾回收器

    Oilpan项目引入的背景是,Chromium在浏览网页时,有时会发生莫名奇妙的内存泄露问题。根据Blink的内存泄露检测器(LeakDetector)给出的数据显示,ChromiumWebView在访问946个网站之后,有294个WebCore::Document对象没有得到释放。


    在Oilpan项目之前,Blink和Chromium都采用引用计数技术(referencecounting)来管理内存,每个对象内部都一个引用计数,表明当前对象被引用了多少次,当引用技术归零时,对象就会被自动释放掉,这种方式一直以来都存在一个缺陷就是循环引用问题,就A引用了,B又引用了A,最后导致A和B都没有机会释放,此外,C++中启用引用计数还存在其他几个方面的问题:

    • 引用计数器的增减开销问题;
    • C++中可以通过Raw指针轻易地绕开RefPtr的管理,一旦使用不当,将导致use-after-free内存错误,存在安全问题;

    尽管引用计数存在上述一些问题,但它很轻量级,仍然是C++程序中广泛使用的自动内存管理计数。Blink项目并不满足这种轻量级的内存管理方法,于是Oilpan项目提上日程,要实现对Blink对象的自动回收机制。比起引用计数技术,Oilpan垃圾回收器确实是个庞然大物,它实现了一个一般只有虚拟机才需要的高级特性,然而Blink项目力求精益求精,追求最好!

    Oilpan实现了一种跟踪式的垃圾回收机制,具有如下特点:

    • Blink中所有的对象都将分配在一个受托管的堆中,每个对象都提供了一个trace的方法,用来建立与堆中其他对象的可达关系,因此,从根节点(一般DOMWindow)出发,Blink的对象在托管堆中形成了一个对象图,那些由根节点不可达的对象将会被GC掉,这样就避免了循环引用问题。
    • Oilpan的GC并不会随时都发生,它会被推迟到消息循环中执行,因为当消息循环执行完任务队列中最后一个任务时,此时Blink的栈为空,没有在栈中分配的对象了。
    • 一旦需要执行GC时,Blink首先要确保所有运行的线程到达了一个“安全点”,不会再分配新的对象,然后从根节点出发,计算堆中所有对象的传递可达性,并标记(mark)所有可达的对象,最后每个线程开始清理(sweep)属于自己的那部分堆空间,回收所有未被标记的对象,将其插入到空间列表中。
    • 与V8引擎的GC相比,Oilpan的GC会牵扯到Blink所有的线程,Database线程,File线程等等,所有的线程都共享一个Oilpan的堆空间。
    • Oilpan提供了两个超类GarbageCollected和GarbageCollectedFinalized,来保证它们的子类都分配在由Oilpan管理的堆中。

    截止到目前,Oilpan基础框架已经比较稳定,modules/中所有对象默认都启用了Oilpan,但Node层次结构还未正式启用。

    关于Oilpan,大家可能比较关心它的性能和内存开销问题,评价Oilpan是否成功有三点,第一,比引用计数更快,第二,不能使性能变差,第三,不会增加内存使用量的峰值。根据最新的性能和内存使用评测结果来看,执行效率和内存使用的峰值都不是问题,但因GC带来的暂停时间是个问题,特别是对于一些动画的benchmark,在Nexus7设备上有些GC操作要花费超过50ms,显然是不能接受的,下一步的方向就是优化stop-the-world所耗费的时间。

    ServiceWorker:极大地提升Web App的离线缓存能力

    ServiceWorker是一项比较新的Web技术,是Chromium团队在吸收了ChromePackaged App的Event Page机制,同时吸取了HTML5 AppCache标准失败的教训之后,提出一套新的W3C规范,旨在提高WebApp的离线缓存能力,缩小WebApp与NativeApp之间差距。

    目前Web App在离线缓存方面并不完善,尽管有HTML5 AppCache标准允许Web开发者通过manifest文件指定WebApp可以被浏览器缓存的文件清单,但由于这套机制不够灵活,存在较大的局限性,被很多Web开发者抱怨。ServiceWorker试图改变这一现状。

    ServiceWorker可以看做Web Worker规范的一个有趣的应用,类似于SharedWorker,专为WebApp离线使用而设计的,它是一段可以安装在Web网站上的JavaScript代码,它运行在一个独立的Worker上下文中。当访问网站时,ServiceWorker会监听网络请求,如资源获取,更新等,甚至在离线情况都能收到这些事件,Web开发者可以决定在ServiceWorker的代码中如何处理这些事件。

    ServiceWorker一个典型的应用情景是,当访问一个网站时,在有可用网络的情况下,浏览器从服务器端下载资源,将网页显示给用户,但在网络不可用的情况,即离线状态,如果再次访问这个网站,那么浏览器会报告404错误,有了ServiceWorker,情形就好的多了,ServiceWorker可以监听网络请求事件,一旦发现当前处于离线状态,ServiceWorker会转而从缓存中读取一些内存展现给用户,让用户即时在离线状态下,也可以使用网站的服务,大大增强了Web页面的离线能力。

    安装了ServiceWorker的页面称为“受控页面”,ServiceWorker是浏览器到服务器端之间的一个中间层,可以转接所有的网络请求,包括XMLHttpRequest请求加载资源,也可以创建和调出所有存储在本地缓存的数据,ServiceWorker和受控页面之间还可以相互发送消息。

    Blink-in-JavaScript: 用JavaScript实现DOM的新特性

    用C++实现过DOM API的同学应该都有亲身体会,为Blink新增加一个DOM特性确实是件比较繁琐的事情,为了实现一个DOM新特性,你需要修改IDL,IDL解释器会生成一堆C++代码,然后还得用C++根据固定的模式编写具体特性的实现代码。众所周知,C++代码难写,需要仔细的考虑每个对象的生命周期,稍有不慎,内存问题最终会导致安全问题,这样导致开发效率低下,而且日后维护成本也大。

    Blink-in-JS使Blink开发者使用JavaScript实现DOM新特性成为可能,其基本思想就是用C++实现DOM的核心部分导出一组JSAPI作为基础库,基于这些已有的JavaScriptAPI,尽可能使用JavaScript实现DOM的其他部分,例如XSLT,editingAPI,DOMWindow.atob/btoa以及ScriptRegexp等。以XSLT为例,Blink的C++实现很复杂,但实际上用的并不多,主要是一些企业级用户,对于这样一类不是很受欢迎或者很快将会过时的API,Blink-in-JS可以将复杂的C++实现从Blink代码库移除掉,取而代之使用JavaScript去实现这类API。

    Blink-in-JS可以帮助Blink开发者提升开发效率以及代码的可维护性,但副作用有可能是导致那些用JS实现的API运行时性能和内存开销比C++的要多,所以,对于性能要求较高的API,建议还是用C++实现更好。对于内存开销问题,被JIT过的JS代码大小是C++二进制代码的20倍之多,相应的解决办法是,启用JavaScript代码的延迟编译,只有当app请求调用这个特性时,才开始编译它的JavaScript代码,而且编译过的JS代码使用完了之后会被丢弃掉,下次请求时需要再重新编译,这实际上只是折中方案。

    以DOMWindow.atob为例,Blink-in-JS的编程模型如下:

    // WindowBase64.idl
    interface WindowBase64 {
     [ImplementedInJS]DOMString atob(DOMString str);
    };
    // WindowBase64.js
    installClass(“WindowBase64”, function() {
     return {atob:function atob(str) {
     // Here |this| isequal to |window|.
     returnbase64Encode(str);
     }};
    });

    安全模型是设计和实现Blink-in-JS时一个非常关键的考虑因素,Blink既要保证Blink-in-JS和应用层JavaScript代码能够操作相同的DOM对象,又要保证WebApp的JavaScript代码不能访问Blink-in-JS中的JS对象。Blink-in-JS借鉴了ChromeExtension的安全模型机制,也使用了V8引擎中“world”隔离Blink-in-JS和应用层JavaScript代码的运行环境。在ChromeExtension中,World提供了完全隔离的JS运行环境,但不同的World之间可以通过DOMWrapper共享所有的DOM对象,如下图所示,


    所以,理论上Blink-In-JS可以达到和Chrome Extension相同的安全级别。

    目前Blink项目的状态

    在过去半年时间内,Blink项目演进速度还是相当快的,仅从7月份到9月份,代码行数从264万增加到269万,每天平均有52个commit,Blink社区共收到151个Intents的贡献请求,其中Google占了51%,剩余的来自整个开源社区,这个数字说明Blink已经发展的相当不错,社区的参与积极性还是比较高的。

    截止到11月份,Blink已经落地的特性有:


    还未落地的特性有:


    总结

    本文简要地介绍了目前Blink项目中几个比较重要的子项目,比如为了进一步提升渲染性能而重写整个Blink/Chromium渲染流水线,为Blink绘图瘦身的SlimmingPaint技术,优化高优先级任务处理的BlinkScheduler,还有解决不明内存泄露问题的Oilpan自动垃圾回收器,还有Blink-in-JS实现了开发效率和代码安全的双提升,以及ServiceWorker能够灵活地支持WebApp离线能力,除了上面提到的,还有一些没有特别做介绍,但对后续Blink项目演进有比较大影响的变化,例如,和Chromium代码库的合并,如果只是文件目录的合并倒也没有太大变化,但如果是源码级别的合并,改动就大了,包括合并base和WTF基础库等,还有ProjectWarden项目成立的初衷就是专门负责重构Blink代码,简化排版和绘图的流程,清理Blink中各种诡异的问题,如Repaint-after-layout, 排版过程中不能修改RenderTree等。这些变化已经发生或者正在发生,使Blink项目得以快速向前演进,下一步技术方向仍然是以性能为主,尤其是针对移动设备的,以构建WebApp平台为基本出发点,逐步缩小与NativeApp的差距,与此同时,通过代码重构和架构调整,提高Blink代码的可维护性、可靠性和安全性,从而提高开发效率。

    注:本文所有图片均来自BlinkOn会议的Slides,版权归原作者所有。

    原创文章,转载请以链接形式注明原始出处http://blog.csdn.net/hongbomin/article/details/41091679.

    相关链接

    BlinkOn2所有PPT资源汇总:http://bit.ly/blinkon2

    BlinkOn3所有PPT资源汇总:http://bit.ly/blinkon3


    展开全文
  • 2018 年 1 月,在教育部公布的《普通高中课程方案和语文学科课程标准(2017年版)》中,人工智能、物联网、大数据处理正式被划入新课标。 2018 年 4 月,教育部印发《高等学校人工智能创新行动计划》,又提出要构建...
  • 解决方案怎么写

    千次阅读 2013-05-06 10:18:35
    网络安全解决方案  写方案不难,知道怎么写才难。关于写方案我只总结一点,结构化地去组织你... 另外真正写方案的人,对自己写过的方案是永远不会满意的,只有这样,每次都会进步一点点,解决方案水平质量就
  • 2011年9月14日百万员工素质提升行动培训在深圳市六六五电子商务有限公司以下简称六六五...行动工作方案的通知和民治街道站工作站的有关精神并结合六六五企业实际而特别安排的据悉下一步为了实现六六五职工素质有一个整
  • 服务器和应用系统迁移方案

    万次阅读 2012-10-26 13:57:49
    服务器和应用系统迁移方案一、迁移方案总体思路新旧系统的迁移是一个整体系统工程。迁移必须保证用户系统建设的相关要求,在迁移过程中,我们需要重点考虑几个问题:1、数据迁移如何保障“业务中断停机时间”。业务...
  • 三下乡活动方案.doc

    2021-01-18 17:07:43
     为了让我校“三下乡”这个品牌活动更出色,并更一步丰富“第二课堂”的内容,这次活动我们做了充分的准备,主要紧扣一个目标,抓住二个重点,着力一个推进,强化三个结合,实现三大行动,抒写三个篇章。...
  • XXX客户2020年护网行动总结报告

    千次阅读 2021-01-04 17:36:19
    目录 XXX客户2020年护网行动总结报告 ...下一步工作 (一)基础运维方面 (二)安全防护方面 (三)安全监测方面 (四)应急处置方面 XXX客户2020年护网行动总结报告 202X年X月X日-202X年X月X
  • 关于HW护网行动的一些知识

    万次阅读 多人点赞 2020-08-13 17:26:19
    由于今年疫情的影响,今天知道了2020年的HW行动在下周一(8月17日)并且时间居然加长了,延长到两个月,估计各位朋友都在准备了吧。届时观看红蓝两房的精彩演绎。 随着《网络安全法》和《等级保护制度条例2.0》的...
  • 大数据实验室建设方案

    千次阅读 2020-03-13 11:30:25
    大数据实验室建设方案 北京红亚华宇科技有限公司 二零二零年 ...
  • 前言:因为博主近期看了一篇名为“ 抖音、吃鸡、王者荣耀:你的自律,是如何被顶级产品经理一步一步毁掉的 ” 的文章,小有感触,于是顺着自己的思路写了一些自己对信息时代一些特殊的现象的分析以及自己的看法。...
  • 目标与行动

    千次阅读 2011-12-04 10:27:57
    对于一个团队来说,行动的目标必须是明确的、具体的,只有这样,才能让团队成员明确下一步努力的方向。有了明确、具体的目标,不管到哪一个阶段,也不管在实现目标的进程中遇到了什么意外的情况,都能够保证团队成员...
  •  数据挖掘起始于20世纪半叶,是在当时多个学科的基础上发展起来的。随着数据库技术的广泛应用,数据的积累不断膨胀,导致简单的查询和统计已经无法满足企业的商业需求,急需一些革命性的技术去挖掘数据背后的信息...
  • 通过对关键问题定义,回答清晰后就可以进入下一步,对业务流程进行完整的梳理了。 梳理业务流程 梳理业务流程需要基于对产品战略的清晰定义。业务流程围绕产品战略目标而设计,需要明确业务目标,流程围绕...
  • 分布式事务解决方案全解析

    千次阅读 多人点赞 2021-01-22 16:45:37
    对于刚刚接触分布式系统的伙伴来说,分布式看起来非常...目前已有Dubbo、SpringCloud等较好的分布式框架,但分布式事务仍是分布式系统一大痛点,本文结合一些经典博客文章,简单解析一些常见的分布式事务解决方案
  • 问题描述 如题 解决思路 解决这个问题主要用到了动态规划的思想,那么什么是动态规划呢...我们一网格的左上角(起点)为坐标原点,向右为m轴,向为n轴,以每个小网格的边长为单位一,红色的数字代表从原点到该点的...
  • pc端项目,rem布局,swiper作为步骤条(上一步下一步)的功能。 发现在屏幕拖动,宽高的变化,窗口大小的变化 会引起swiper自动滑动(到下一步)。 在下载文件链接时候回轻微引起一点点宽高的变化,导致直接滑到...
  • 网络安全解决方案

    千次阅读 2017-01-13 13:52:43
    黑白名单功能用来检测用户所关心的设备(通过IP或MAC来识别)是否在网络中出现及出现时间,一提醒用户是否进行下一步的操作。 配置管理: 支持同时对每多台设备进行配置/备份和软件升级,以减少管理员的工作量,...
  • 通过对关键问题定义,回答清晰后就可以进入下一步,对业务流程进行完整的梳理了。 梳理业务流程 梳理业务流程需要基于对产品战略的清晰定义。业务流程围绕产品战略目标而设计,需要明确业务目标,流程...
  • PHP Fuzzing行动——源码审计

    千次阅读 2014-05-13 12:18:17
     PHP Fuzzing行动——源码审计   作者:Shahin Ramezany 译者:riusksk(泉哥:http://riusksk.blogbus.com)     目录: Section 1: 20种PHP源码快速审计方式   Section 2: PHP源码审计自动化
  • HTTPS 原理及安全加密方案

    千次阅读 2017-09-23 21:16:12
    HTTPS 原理及安全加密方案 一、什么是HTTPS 在说HTTPS之前先说说什么是HTTP,HTTP就是我们平时浏览网页时候使用的一种协议。HTTP协议传输的数据都是未加密的,也就是明文的,因此使用HTTP协议传输隐私信息非常不...
  • 关于HW护网行动的一些知识【转载】

    千次阅读 2020-09-01 16:42:56
    由于今年疫情的影响,今天知道了2020年的HW行动在下周一(8月17日)并且时间居然加长了,延长到两个月,估计各位朋友都在准备了吧。届时观看红蓝两房的精彩演绎。 随着《网络安全法》和《等级保护制度条例2.0》的...
  • 同时,针对流程复杂的异地结算,腾讯公司还将通过微信医疗支付解决方案,把城乡居民医保的异地结算环节转移至线上,给患者带来更多便利,并提升医院的结算效率与资金流转透明度。(via:雷锋网) 8. 15家人工智能...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 23,331
精华内容 9,332
关键字:

下一步行动方案