请选择 进入手机版 | 继续访问电脑版

网络科技

    今日:272| 主题:266325
收藏本版 (1)
互联网、科技极客的综合动态。

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

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

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

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

x
写在前面

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

  注意,组件化不是插件化,插件化是在[运行时],而组件化是在[编译时]。换句话说,插件化是基于多 APK 的,而组件化本质上还是只有一个 APK。
  代码实现上核心思路要紧记一句话:开发时是 application,发版时是 library。
  来看一段 gradle 代码:
  1. if (isDebug.toBoolean()) {
  2.     apply plugin: 'com.android.application'
  3. } else {
  4.     apply plugin: 'com.android.library'
  5. }
复制代码
     非常好理解,我们在开发的时候,module 如果是一个库,会使用 com.android.library 插件,如果是一个应用,则使用 com.android.application 插件,我们通过一个开关来控制这个状态的切换。
   然后因为我们需要在 library 和 application 之间切换,manifest文件也需要提供两套。
   

Android业务组件化开发实践 - 张涛

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】 ):
  1. if (isDebug.toBoolean()) {
  2.     buildConfigField 'boolean', 'ISAPP', 'true'
  3. } else {
  4.     buildConfigField 'boolean', 'ISAPP', 'false'
  5. }
复制代码
  有些人喜欢将 application 单例,写一个静态的对象,然后在代码里面需要context的时候用这个全局单例。这样的情况我送大家一个工具类(其实是从冯老师代码里偷来的): Common
  1. public class App {
  2.     public static final Application INSTANCE;
  3.    
  4.     static {
  5.         Application app = null;
  6.         try {
  7.             app = (Application) Class.forName("android.app.AppGlobals").getMethod("getInitialApplication").invoke(null);
  8.             if (app == null)
  9.                 throw new IllegalStateException("Static initialization of Applications must be on main thread.");
  10.         } catch (final Exception e) {
  11.             LogUtils.e("Failed to get current application from AppGlobals." + e.getMessage());
  12.             try {
  13.                 app = (Application) Class.forName("android.app.ActivityThread").getMethod("currentApplication").invoke(null);
  14.             } catch (final Exception ex) {
  15.                 LogUtils.e("Failed to get current application from ActivityThread." + e.getMessage());
  16.             }
  17.         } finally {
  18.             INSTANCE = app;
  19.         }
  20.     }
  21. }
复制代码
跨 module 跳转

  如果单独是 Activity 跳转,常见的做法是:隐式启动 Activity、或者定义 scheme 跳转。
  但是如果界面是一个 Fragment 就比较麻烦了,我推荐的是直接通过类名跳转。
  首先创建一个所有界面类名的列表
  1. public class RList {
  2.     public static final String ACTIVITY_MEMORYBOX_MAIN = "com.kymjs.app.memory.module.main.MainActivity";
  3.    
  4.     public static final String FRAGMENT_MEMORYBOX_MAIN = "com.kymjs.app.memory.module.list.MainFragment";
  5. }
复制代码
在获取 Fragment 的时候就可以根据列表中的类名来读取指定的 Fragment 了。
  1. public class FragmentRouter {
  2.     public static Fragment getFragment(String name) {
  3.         Fragment fragment;
  4.         try {
  5.             Class fragmentClass = Class.forName(name);
  6.             fragment = (Fragment) fragmentClass.newInstance();
  7.         } catch (Exception e) {
  8.             throw new RuntimeException(e);
  9.         }
  10.         return fragment;
  11.     }
  12. }
复制代码
同理,Activity 其实也可以用这种方法来跳转:
  1. public static void startActivityForName(Context context, String name) {
  2.     try {
  3.         Class clazz = Class.forName(name);
  4.         startActivity(context, clazz);
  5.     } catch (ClassNotFoundException e) {
  6.         throw new RuntimeException(e);
  7.     }
  8. }
复制代码
  最后,对于这个 RList 类,我们还可以通过 Gradle 脚本来生成,就像 R 文件一样,这样子开发就要方便很多了。
  重复依赖

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

Android业务组件化开发实践 - 张涛

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

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

   

Android业务组件化开发实践 - 张涛

Android业务组件化开发实践 - 张涛

   app是最终工程的目录
   explorer和 memory-box 是两个功能模块,他们在开发阶段是以独立的 application,在 release 时才会作为 library 引入工程。
   router有两个功能,一个是作为路由,用于提供界面跳转功能。另一个功能是前面讲的 shell ,作为依赖集合,让各业务 module 接入。 base-res 是一些通用的代码,即每个业务模块都会接入的部分,它会在 router 中被引入。
   最终代码可以查看: https://github.com/kymjs/Modularity



上一篇: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-2017 Comsenz Inc. Design: Dean. DiscuzFans.

返回顶部 返回列表