网络科技

    今日:668| 主题:246446
收藏本版
互联网、科技极客的综合动态。

[其他] Android业务组件化开发实践 - 张涛

[复制链接]
木已凉兮 发表于 2016-10-18 18:00:42
169 9

立即注册CoLaBug.com会员,免费获得投稿人的专业资料,享用更多功能,玩转个人品牌!

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
写在前面

   本文原创,转载请以链接形式注明地址: http://kymjs.com/code/2016/10/18/01
  借用阿布倪盟博的一句话:“在MDCC中冯森林老师的《回归初心,从容器化到组件化》,为我们这些没有那么多精力折腾黑科技开发者们打开了另一扇门” 。
  组件化并不是新话题,其实很早很早以前我们开始为项目解耦的时候就讨论过的。但那时候我们说的是功能组件化。比如很多公司都常见的,网络请求模块、登录注册模块单独拿出来,交给一个团队开发,而在用的时候只需要接入对应模块的功能就可以了。
  今天我们来讨论一下业务组件化,拿出手机,打开淘宝或者大众点评来看看,里面的美食电影酒店外卖就是一个一个的业务。如果我们在一个项目里面去写的时候,总会出现或多或少的代码耦合,最典型的有时为了赶上线时间而先复制粘贴一段类似的代码过来,结果这段代码引用的资源可能是另一个模块独立的资源或代码。但是如果将一个项目作为独立的工程来运行,就完全可以避免这种情况了。但是这并不是业务组件化最大的优势,我认为最大的优势是它大大缩减了工程结构直接降低了编译时间。
  代码实现

  注意,组件化不是插件化,插件化是在[运行时],而组件化是在[编译时]。换句话说,插件化是基于多 APK 的,而组件化本质上还是只有一个 APK。
  代码实现上核心思路要紧记一句话:开发时是 application,发版时是 library。
  来看一段 gradle 代码:
  [code]if (isDebug.toBoolean()) {
    apply plugin: 'com.android.application'
} else {
    apply plugin: 'com.android.library'
}[/code]      非常好理解,我们在开发的时候,module 如果是一个库,会使用 com.android.library 插件,如果是一个应用,则使用 com.android.application 插件,我们通过一个开关来控制这个状态的切换。
   然后因为我们需要在 library 和 application 之间切换,manifest文件也需要提供两套。
   
Android业务组件化开发实践 - 张涛-1 (Android,房地产业,大众点评,百分点,成交量)
    举个栗子?

   你可以根据这个项目一起看: https://github.com/kymjs/Modularity
  假设有一个项目,这个项目包含一个叫 explorer 的文件浏览器的模块和一个叫 memory-box 的笔记的模块。因为这两个功能相对独立,我们将这两个功能拆分成两个 module,再加上原本项目的 app module,总共三个。
   在 explorer 的根目录建立一个作为开关的 properties 文件(写一个全局变量也可以,怎么简单怎么来),方便用来改变当前是开发状态还是发版状态(debug & release)。 从gradle中读取这个文件中的值,来切换不同状态所需要调用的配置。顺便一提,当你修改了 properties 文件中的值时,必须要重新 sync 一下。 详细配置过程可以看看这篇文章: http://www.zjutkz.net/
  遇到的问题

   阿布他们的项目大量的用了 databinding 和 dagger,然而我们项目并没有用这些,用了这两个库的可以看看他是怎么爬坑的: 魔都三帅
  当你采用了组件化开发的时候,一定会遇到这几个问题,这几个问题除了第三个都只能规避,没有好的处理办法:
  1、module 中 Application 调用的问题
  2、跨 module 的 Activity 或 Fragment 跳转问题
  3、AAR 或 library project 重复依赖
  4、资源名冲突
  解决 Application 冲突

   由于 module 在开发过程中是以 application 的形式存在的,如果这个 module 调用了类似 ((XXXApplication)getApplication()).xxx() 这种代码的话,最终 release 项目时一定会发生类转换异常。因为在 debug 状态下的 module 是一个 application,而在 release 状态下它只是一个 lib。所以也就是在 debug 和 release 时获取到的 Application 不是同一个类的对象。
  这个问题还好,我们只要在 application 里面尽量不要写方法实现,不要做强转操作就好。
   如果确实要区分,业务模块在 debug 状态和 release 状态有不同的行为,可以通过扩展 BuildConfig 这个类,在代码中通过 boolean 值来执行不同的逻辑。只需要在 gradle 中加入(具体代码用法可查看 【line:48】 ):
  [code]if (isDebug.toBoolean()) {
    buildConfigField 'boolean', 'ISAPP', 'true'
} else {
    buildConfigField 'boolean', 'ISAPP', 'false'
}[/code]   有些人喜欢将 application 单例,写一个静态的对象,然后在代码里面需要context的时候用这个全局单例。这样的情况我送大家一个工具类(其实是从冯老师代码里偷来的): Common
  [code]public class App {
    public static final Application INSTANCE;
   
    static {
        Application app = null;
        try {
            app = (Application) Class.forName("android.app.AppGlobals").getMethod("getInitialApplication").invoke(null);
            if (app == null)
                throw new IllegalStateException("Static initialization of Applications must be on main thread.");
        } catch (final Exception e) {
            LogUtils.e("Failed to get current application from AppGlobals." + e.getMessage());
            try {
                app = (Application) Class.forName("android.app.ActivityThread").getMethod("currentApplication").invoke(null);
            } catch (final Exception ex) {
                LogUtils.e("Failed to get current application from ActivityThread." + e.getMessage());
            }
        } finally {
            INSTANCE = app;
        }
    }
}[/code]  跨 module 跳转

  如果单独是 Activity 跳转,常见的做法是:隐式启动 Activity、或者定义 scheme 跳转。
  但是如果界面是一个 Fragment 就比较麻烦了,我推荐的是直接通过类名跳转。
  首先创建一个所有界面类名的列表
  [code]public class RList {
    public static final String ACTIVITY_MEMORYBOX_MAIN = "com.kymjs.app.memory.module.main.MainActivity";
   
    public static final String FRAGMENT_MEMORYBOX_MAIN = "com.kymjs.app.memory.module.list.MainFragment";
}[/code]  在获取 Fragment 的时候就可以根据列表中的类名来读取指定的 Fragment 了。
  [code]public class FragmentRouter {

    public static Fragment getFragment(String name) {
        Fragment fragment;
        try {
            Class fragmentClass = Class.forName(name);
            fragment = (Fragment) fragmentClass.newInstance();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return fragment;
    }
}[/code]  同理,Activity 其实也可以用这种方法来跳转:
  [code]public static void startActivityForName(Context context, String name) {
    try {
        Class clazz = Class.forName(name);
        startActivity(context, clazz);
    } catch (ClassNotFoundException e) {
        throw new RuntimeException(e);
    }
}[/code]   最后,对于这个 RList 类,我们还可以通过 Gradle 脚本来生成,就像 R 文件一样,这样子开发就要方便很多了。
  重复依赖

     重复依赖问题其实在开发中经常会遇到,比如你 compile 了一个A,然后在这个库里面又 compile 了一个B,然后你的工程中又 compile 了一个同样的B,就依赖了两次。
   默认情况下,如果是 aar 依赖,gradle 会自动帮我们找出新版本的库而抛弃旧版本的重复依赖。但是如果你使用的是 project 依赖,gradle 并不会去去重,最后打包就会出现代码中有重复的类了。
   一种是 将 compile 改为 provided,只在最终的项目中 compile 对应的代码;
   还可以使用这种方案:

Android业务组件化开发实践 - 张涛-2 (Android,房地产业,大众点评,百分点,成交量)
   可以将所有的依赖写在 shell 层的 module,这个 shell 并不做事情,他只用来将所有的依赖统一成一个入口交给上层的 app 去引入,而项目所有的依赖都可以写在 shell module 里面。
    资源名冲突

  因为分了多个 module,在合并工程的时候总会出现资源引用冲突,比如两个 module 定义了同一个资源名。
  这个问题也不是新问题了,做 SDK 基本都会遇到,可以通过设置 resourcePrefix 来避免。设置了这个值后,你所有的资源名必须以指定的字符串做前缀,否则会报错。
  但是 resourcePrefix 这个值只能限定 xml 里面的资源,并不能限定图片资源,所有图片资源仍然需要你手动去修改资源名。
  项目结构
12下一页
友荐云推荐




上一篇:Docker can run on windows 10, without VMs (stable)
下一篇:Pinterest Adds Pin Engagement & Website Retargeting by @DannyNMIGoodwin
酷辣虫提示酷辣虫禁止发表任何与中华人民共和国法律有抵触的内容!所有内容由用户发布,并不代表酷辣虫的观点,酷辣虫无法对用户发布内容真实性提供任何的保证,请自行验证并承担风险与后果。如您有版权、违规等问题,请通过"联系我们"或"违规举报"告知我们处理。

董莎 发表于 2016-10-18 23:18:09
我可以轻视你,鄙视你,小看你,不看你.
回复 支持 反对

使用道具 举报

如烟绕梁 发表于 2016-10-18 23:32:18
赞一个!
回复 支持 反对

使用道具 举报

晓旋 发表于 2016-10-18 23:40:26
围观 围观 沙发在哪里!!!
回复 支持 反对

使用道具 举报

刘应峰 发表于 2016-10-21 09:51:43
时间过的这么快 为马俺还没升级
回复 支持 反对

使用道具 举报

刻痕深深 发表于 2016-10-21 15:41:55
好,很好,非常好!
回复 支持 反对

使用道具 举报

hxenvzil 发表于 2016-10-22 19:03:03
人生如戏,全靠演技。
回复 支持 反对

使用道具 举报

安荷 发表于 2016-10-26 13:23:45
怀揣两块,胸怀500万!  
回复 支持 反对

使用道具 举报

yjk132659 发表于 2016-10-28 14:42:11
very good
回复 支持 反对

使用道具 举报

一朵奇葩向阳开 发表于 2016-11-21 10:25:34
今天没事来逛逛
回复 支持 反对

使用道具 举报

*滑动验证:
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

我要投稿

推荐阅读

扫码访问 @iTTTTT瑞翔 的微博
回页顶回复上一篇下一篇回列表手机版
手机版/CoLaBug.com ( 粤ICP备05003221号 | 文网文[2010]257号 )|网站地图 酷辣虫

© 2001-2016 Comsenz Inc. Design: Dean. DiscuzFans.

返回顶部 返回列表