Android组件化过程中ButterKnife在Library中的使用

起因

​ 最近一直在做Android框架方面的工作,先是把原来的网络框架由Netroid改为了Retrofit+Rxjava,然后在这次修改的基础上,有做了大量的基础的Activity和Fragment的封装,以及对应的MVP的架构封装,也是经过多次修改调整后,基本成型了,随后单开一篇文章介绍这个Android架构,这个架构适合小项目,小团队使用,也是根据网上的大牛提供的思路,自己实践来的,经过几次进化后,应付中小项目应该是绰绰有余了。

​ 中小型项目框架涵盖的关键字:MVP、Retrofit、RXAndroid、RxAndroidLifeCycle、EventBus、LitePal、CommonUtils(涵盖非常全面的工具类库)等。

有句话说的好:既得陇,复望蜀。

​ 在中小型项目的框架成功后,我觉得不如就此机会把组件化的架构一并实现,于是就开始学习组件化的架构组织,一步一步的把自己的项目架构慢慢的转成了组件化的开发,不过目前还没有完成,有几个问题需要处理:

  1. 引入路由组件
  2. 封装常用的自定义View
  3. 封装业务组件:通用Dialog,通用RecycleView、通用进度条等,这个就很随意了,主要还是看开发者自己的主观意愿。

​ 在初步的完成了封装后,发现了一个问题,就是我把原来在单一项目框架下的源码转移到了组件化框架下的子业务框架下,说白了就是在组件化开启的前提下,把源码从App转移到了Library下,从理论上来说,应该不会有什么问题,但是历史实践告诉我们,理论往往是靠不住的,在转移的过程中,我就遇到了ButterKnife造成的问题。

问题

下面我们来看一下具体的问题:

logo

如果你强制编译,Build控制台就会很贴心的用中文再告诉你一次:

logo

元素值必须为常量表达式?怎么就不是常量了?!我们知道R.id.XX其实就是引入了R文件中的一个静态常量,所谓的静态常量其实就是static final的中文说法,于是我点开了R文件:

logo

好吧,没有final…….正常的Application中的R文件应该是这样的:

logo

为什么缺少了final关键字呢?原因其实谷歌已经给出了:

从ADT14开始Library中的R文件才从静态常量变为非常量.因为如果在多个Library中可能出现id冲突的问题.在ADT14以前则采用的是将所有的资源文件和相关的代码重新随着主项目一起编译,导致编译速度过慢.因此,从ADT14开始就变成了非常量的id了。

虽然这个改动的出发点是好的,但是随着组件化越来越普及,业务组件放在library的情况会越来越多,ButterKnife需要在Library中使用的情况也越来越多,所以,JakeWharton大神终于在大家的呼唤下,让ButterKnife支持Library了。

解决

官方的解决方案:

logo

第一步

在项目的build.gradle中的dependencies中加入:classpath ‘com.jakewharton:butterknife-gradle-plugin:9.0.0-rc1’

第二步

在业务子Library的Build.gradle中加入:apply plugin: ‘com.jakewharton.butterknife’

这里要注意:

1
2
3
4
5
6
if (isModule.toBoolean()) {
apply plugin: 'com.android.application'
} else {
apply plugin: 'com.android.library'
}
apply plugin: 'com.jakewharton.butterknife'

不论你是否开启组件化,都要把这个插件加入进去。

第三步

这里要注意了,敲黑板!

如果你的ButterKnife的依赖是集成在本业务Library中的,那你就不用管我下面说的话了,如果不是,一定要看仔细!

我的ButterKnife就不是封装在本业务的Library中的,而是在其底层的一个名为BaseLib的Library中引入的,该Library主要是提供BaseActivity和BaseFragment等基础组件的,在该Library中引入也是为了合理的处理所有ButterKnife的解绑操作,那么这时,如果你要在你的业务Library中使用ButterKnife,你不必重复引入ButterKnife的依赖,而是在业务Library中加入:

1
2
3
4
5
dependencies {
api project(':BaseLib')
//这里要将ButterKnife的注解加入主项目中去,因为子项目无法通过api引入
annotationProcessor "com.jakewharton:butterknife-compiler:$butterknife_version"
}

否则就会出现一个问题,那就是你的项目在编译时不会报错,但是在运行时就会报空指针的错误:

1
2
3
4
5
6
2018-10-26 16:33:50.166 15872-15872/? W/System.err: java.lang.RuntimeException: Unable to start activity ComponentInfo{com.xxx.read/com.xxx.read.ReadActivityMainActivity}: java.lang.NullPointerException: Attempt to invoke virtual method 'void android.view.View.setVisibility(int)' on a null object reference
2018-10-26 16:33:50.167 15872-15872/? W/System.err: at com.dhcc.commonutils.BarUtils.setStatusBarAlpha(BarUtils.java:294)
2018-10-26 16:33:50.167 15872-15872/? W/System.err: at com.dhcc.commonutils.BarUtils.setStatusBarAlpha4Drawer(BarUtils.java:385)
2018-10-26 16:33:50.167 15872-15872/? W/System.err: at com.dhcc.read.ReadActivityMainActivity.initView(ReadActivityMainActivity.java:95)
2018-10-26 16:33:50.167 15872-15872/? W/System.err: at com.dhcc.baseLib.base.activity.BaseActivity.onCreate(BaseActivity.java:51)
2018-10-26 16:33:50.167 15872-15872/? W/System.err: at com.dhcc.read.ReadActivityMainActivity.onCreate(ReadActivityMainActivity.java:228)

这个问题比较特殊,并不是所有人都会遇到,如果你遇到了,记得按照我这样处理,就可以了。

第四步

把业务Library项目中的所有关于ButterKnife的R引用,改为R2引用即可。
如:

1
2
3
4
5
6
@BindView(R2.id.toolbar)
Toolbar toolbar;
@BindView(R2.id.fake_status_bar)
View fakeStatusBar;
@BindView(R2.id.textView)
TextView textView;

这里注意一下,并不是所有的R文件都改成R2,如果是R.layout这类R引用就不要更改,保持原状即可,否则会在运行时报出找不到View的指定ID的错误。

如:

1
2
3
4
5
6
7
8
9
/**
* 绑定布局
*
* @return 布局 Id
*/

@Override
public int bindLayout() {
return R.layout.read_activity_main;
}

总结

任何一个架构的形成都有门槛,有坑,有知识,有趣,我不愿意做那种被安排的程序员,所以,这些坑其实都是我的乐趣所在,今天拿出我的趟坑经验分享给大家,是希望看到的朋友可以走的比我更远。

谢谢你的阅读。

热评文章