public class MainActivity extends AppCompatActivity
{
private static final String TAG = "MainActivity";
private Request request;
private Bean bean;
private AppHolder holder;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
request = new Request.Builder();
bean = new Bean();
holder = new AppHodler(this);
//TODO 使用request、bean和holder
}
}
我们当然可以手动new 调用类的构造函数给这三个对象赋值,也就是所谓的"正转".
乍一看这是没有问题的,但这是因为我们现在只有这一个Activity ,也只有三个对象需要依赖,并且这三个依赖并没有互相依赖.但是,如果这是一个实际的项目的话,怎么可能只有一个Activity 呢?而且就算是一个Activity 也不可能仅仅依赖三个对象.
那么问题来了,如果这是一个实际的项目,如果这些依赖的对象还有互相依赖,如果这些类的构造函数发生了改变,如果逻辑实现的子类发生了变更,会发生什么?
Boom! 难道要把每一个依赖这些改变的类的Java 文件中的new 都修改一遍吗?这也太蠢了吧!
此时依赖注入闪亮登场,它有助于我们解除这种耦合.
使用依赖注入最大的好处就是你不需要知道一个对象是怎么来的了,你只管使用它,这可以让你的代码更加整洁.
并且如果后来它的构造函数或者是具体实现类发生了改变,那都与你现在所写的代码无关,它们的改变不会迫害你去更新现有的代码.
而在传统的软件开发过程中,我们通常要在一些控制器中去主动依赖一些对象,如果这些对象的依赖方式在未来频繁地发生改变,那我们的程序是无法经受住考验的.
这就是所谓控制反转,它将获得依赖对象的方式反转了.
2.常见的依赖注入框架
- 在服务器后端,一般使用
Spring 框架进行依赖注入。 - 在
Android 上,一般使用Dagger 系列进行依赖注入。
3.实现自己的依赖注入框架
有些同学可能知道Dagger 实现了Java的依赖注入标准(JSR-330 ),这个标准使用的有些注解确实让人有点摸不着头脑,而且Dagger 使用的门槛也较高,估计应该有不少人看了许多《Dagger 完全入门》之类的文章,然而到最后还是没搞懂Dagger 到底是怎么一回事.
所以我就想,能不能搞一个稍微亲民一点的依赖注入框架让我直接先能用上.我不是大神,所以它不一定要实现JSR-330 ,也不一定使用注解处理器来追求极致的效率,但它必须要好理解,里面的概念必须是常见的.
在参考了服务器上Spring 框架的依赖注入后,我决定使用xml 作为依赖注入的配置文件,本来想上Github 看看有没有现成的轮子可以让我"抄抄"之类的,谁知道逛了一圈下来之后才发现Android 开发者除了Dagger 和Dagger2 根本没得选,这更加坚定了我造轮子的信心.
使用xml 是有优势的,xml 是最常见的配置文件,它能更明确的表达依赖关系。所以就有了Liteproj 这个库与Dagger 不同,Liteproj 不使用Java 来描述对象间的依赖关系,而是像Spring 一样使用xml .
Liteproj 目前的实现中也没有使用注解处理器而是使用了反射,因为Liteproj 追求的并非是极致的性能,而是便于理解和上手以及轻量化和易用性,它的诞生并不是为了取代Dagger2 或者其他的一些依赖注入工具,而是在它们所没有涉及的领域做一个补全。
客官请移步 : Liteproj
4.xml解析
既然选择了xml ,那么就要需要解决解析xml 的问题.
经过考虑之后最终选择了dom4j作为xml 解析依赖库.其实Android 本身自带了xml 的解析器,而且它的效率也不错,那我为什么还要使用dom4j 呢,那当然是因为它好用啊。Android 自带的xml 解析器是基于事件驱动的,而dom4j 提供了面向对象的xml 操作接口,我觉得这会给我的编码带来极大的便利,可以降低开发难度.
比如dom4j 中的Document->Element->Attribute 等抽象,非常好地描述了xml 的结构,你甚至无需看它的文档就能简单上手,这可比XmlPullParser 中定义的一堆常量和事件好理解多了.
而且dom4j 也是老牌的xml 解析库,大名鼎鼎的hibernate 也使用它来解析xml 配置文件.
解析xml ,首先要解决assets 文件夹下的xml 文件解析问题,这个还算比较好处理,使用AssetManager 获取Java 标准流,然后把他交给dom4j 解析就可以了。
但是想要解析res/xml 文件夹下的xml 就比较麻烦了,熟悉安卓的人应该都知道,打包后的APK ,res 文件夹下除了raw 文件夹会原样保留,其他文件夹里的内容都会被编译压缩,为了解析res/xml 下的xml ,我依赖AXML这个库编写了一个Axml 到dom4j 的转换层,这样一来解析结果就可以共用一套依赖图生成方案。
由此Liteproj 现在支持解析assets 、res/raw 、res/xml 三个位置的xml 文件,使用@Using 注解在你需要注入的组件中标注你要使用那些xml
@Retention(RUNTIME)
@Target({TYPE})
public @interface Using
{
@XmlRes
@RawRes
int[] value();//res/xml 或 res/raw 文件夹下的xml
String[] assets() default {};//assets 文件夹下的xml
}
//使用@Using注解
@Using({R.xml.all_test,R.xml.test2,R.raw.test2,assets = {"test3.xml"}})
public class MainActivity extends AppCompatActivity
{
//TODO
}
5.对象构造适配
Java 是一门灵活的程序设计语言,由此诞生了多种对象构造方式。如传统的使用构造函数构造对象,又或者是工厂模式,Builder 模式,JavaBean 模式等。Liteproj 必须从一开始就兼容这些现有方案,否则就是开倒车了。
在Liteproj 中你需要为你的依赖关系在xml 中编写一些配置.
第一行是惯例的<?xml version="1.0" encoding="utf-8"?> ,第二行是最外层是dependency 标签,这个标签必须要指定一个owner 的属性来指定此依赖配置文件所兼容的类型,下面的xml 中我指定了android.app.Application 作为此xml 所兼容的类型,那么所有从这个类型派生 的类型都可以使用这个配置文件(其他类型在满足一定条件时也可以使用,见下文标题"生命周期和对象所有权")
<?xml version="1.0" encoding="utf-8"?>
<dependency owner="android.app.Application">
</dependency>
首先从最原始的对象生成方式开始,下面的代码将会使用new 来构造对象.
在配置文件中,你可以使用var 标签声明一个依赖,并用name 属性指定它在上下文中的唯一名字,使用type 属性指定它的类型,使用provider 属性指定它的提供模式,有两种模式可以选择,singleton 和factory ,singleton 保证每次返回的对象都是相同的,而factory 则是每次都会重新创建一个新的对象,factory 还是默认的行为,你可以不写provider 属性,那么它默认就是factory 的.
然后var 标签中包裹的new 标签表明此依赖使用构造函数创建,使用arg 标签填入构造函数的参数并用ref 属性引用一个上文中已经存在的另一个已经声明的var 的name .
这里我引用了一个特殊的name ->owner ,这个依赖不是你使用var 声明的,而是默认导入的,也就是我们的android.app.Application 实例,除此之外还有另外一个特殊的var ,那就是null ,它永远提供Java 中的null 值.
Liteproj 会按照arg 标签ref 所引用的类型的顺序自动去查找类的public 构造函数.不过Liteproj 的对象生成是惰性 的,这意味这只有你真正使用到该对象它才会被创建,在xml 中配置的其实是依赖关系.
//xml配置文件
<?xml version="1.0" encoding="utf-8"?>
<dependency owner="android.app.Application">
<var
name="holder"
provider="singleton"
type="org.kexie.android.liteproj.sample.AppHolderTest">
<new>
<arg ref="owner"/>
<!--可以有多个arg-->
<!--如<arg ref="otherRef"/>-->
</new>
</var>
</dependency>
//java bean
public class AppHolderTest
{
final Context context;
public AppHolderTest(Context context)
{
this.context = context;
}
@Override
public String toString()
{
return super.toString() + context;
}
}
Liteproj 也支持使用Builder 模式创建对象,这在xml 配置中都很直观.
使用builder 标签指定此依赖使用Builder 模式生成,指定builder 的type 为okhttp3.Request$Builder ,使用action 标签指定最后是调用build 方法生成所需要的对象(当然这也是默认行为,你可以不写出action 属性),并使用arg 标签给builder 赋值,不过要注意,这里的arg 标签是有name 的,它将会映射到Builder 对象的方法调用上去给Builder 赋值.
<var
name="request"
type="okhttp3.Request"
provider="singleton">
<builder
action="build"
type="okhttp3.Request$Builder">
<arg name="url" ref="url"/>
</builder>
</var>
下面的代码模拟了工厂模式的使用场景.
使用factory 标签表明此依赖使用工厂函数生成,使用type 属性标明工厂类,并使用action 标明需要调用的工厂函数.
你可能注意到了下面出现了一个新的属性val ,它是用来引用字面值的,之前的ref 只能引用标注名字的var 但是无法引用字面值,所以我加入了一个新的属性val ,它可以在arg 标签中使用,与ref 属性不能同时出现,如果val 以一个@ 开头,那么它的内容就是@ 后面的的字符串,否则他会被转换成数字或布尔值.
<var
name="bean"
type="org.kexie.android.liteproj.sample.Bean"
provider="factory">
<factory
action="test"
type="org.kexie.android.liteproj.sample.Factory">
<arg val="@asdasdd"/>
</factory>
</var>
//一个简单的工厂类,包含一个工厂方法test
public class Factory
{
public static Bean test(String text)
{
Log.d("test",text);
return new Bean();
}
}
public class Bean
{
public float field;
public String string;
Object object;
public void setObject(Object object)
{
this.object = object;
}
@Override
public String toString()
{
return super.toString() + "\n" + field + "\n" + object + "\n" + string;
}
}
代码还是上面的代码,只不过这次加了点东西,factory ,builder ,new 定义了对象的构造方式,我们还可以用field 和property 标签在对象生成后为对象赋值,通过name 属性指定要赋值给哪个字段或属性,property 所指定的name 应该是一个方法,它的命名应该符合Java 的setter 标准,比如name="abc" ,对应void setAbc(YourType) 方法
<var
name="bean"
type="org.kexie.android.liteproj.sample.Bean"
provider="factory">
<factory
action="test"
type="org.kexie.android.liteproj.sample.Factory">
<arg val="@asdasdd"/>
</factory>
<field
name="field"
val="100"/>
<field
name="string"
val="@adadadad"/>
<property
name="object"
ref="owner"/>
</var>
我知道每次重复写字面值很蠢,所以提供了val 转换为var 的方法,让字面值可以像var 一样被ref 使用
<var name="url" val="@http://www.hao123.com"/>
复制代码
最后在这里提一点无论是factory 还是builder 都不允许返回null 值,默认导入的null 只是为了兼容某些特殊情况而设计的,factory 和builder 返回null 是没有意义的.
<?xml version="1.0" encoding="utf-8"?>
<dependency owner="android.app.Application">
<var name="url" val="@http://www.hao123.com"/>
<var
name="request"
type="okhttp3.Request"
provider="singleton">
<builder
type="okhttp3.Request$Builder">
<arg name="url" ref="url"/>
</builder>
</var>
<var
name="bean"
type="org.kexie.android.liteproj.sample.Bean"
provider="factory">
<factory
action="test"
type="org.kexie.android.liteproj.sample.Factory">
<arg val="@asdasdd"/>
</factory>
<field
name="field"
val="100"/>
<field
name="string"
val="@adadadad"/>
<property
name="object"
ref="owner"/>
</var>
<var
name="holder"
type="org.kexie.android.liteproj.sample.AppHolderTest">
<new>
<arg ref="owner"/>
</new>
</var>
</dependency>
6.生命周期和对象所有权
如果说Android 开发中影响范围最广泛的概念是什么,我想那一定就是生命周期了。
因为你会发现几乎什么东西都能跟生命周期扯上关系,在组件创建的时候订阅或请求数据,并一定要记得在组件销毁的时候取消订阅和清理数据,要不然你就等着内存泄漏和迷之报错吧。
还有一个和生命周期有关联的词,那就是对象所有权.
如果Activity 或者Service 引用了Application 的资源,这很合理,因为Application 的生命周期比Activity 要长,不必担心内存泄漏,但如果Application 引用了Activity 的资源,这就有点不合理了,因为Activity 可能随时被杀掉,而Application 的生命周期又比Activity 长,这就容易造成本该在Activity 中释放的资源一直被Application 持有,进而造成内存泄漏,所以Application 不应该有Activity 或者Service 上资源的对象所有权。
所以Liteproj 从一开始就设计成和组件的生命周期绑定在一起,并制定了合理的对象所有权。
Liteproj 支持对5 组件进行依赖注入:
Application ,无特殊要求,会在attachBaseContext 之后与onCreate 之前执行依赖注入Activity ,至少是FragmentActivity (AppCompatActivity 继承了FragmentActivity )Service ,需要继承Liteproj 的org.kexie.android.liteproj.LiteService Fragment ,继承appcompat 的Fragment 即可ViewModel ,需要继承Liteproj 的org.kexie.android.liteproj.LiteViewModel
可以看到Liteproj 的倾入性还是很低的,除了Service 和ViewModel 需要强制继承基类,其他组件的基本上都无需代码改动.
图是用ProcessOn画的:
Service 和Activity 可以使用Application 的xml 配置文件,因为Application 的生命周期比Service 和Activity 都长,同理Fragment 可以使用Activity 的xml 配置文件,而ViewModel 由于不能违背MVVM 的设计原则(ViewModel 不应该知道他是与哪一个View 进行交互的),所以除了自己能使用自己的xml 配置文件之外只允许它使用Application 的xml 配置文件.
总结
现在新技术层出不穷,如果每次出新的技术,我们都深入的研究的话,很容易分散精力。新的技术可能很久之后我们才会在工作中用得上,当学的新技术无法学以致用,很容易被我们遗忘,到最后真的需要使用的时候,又要从头来过(虽然上手会更快)。
我觉得身为技术人,针对新技术应该是持拥抱态度的,入了这一行你就应该知道这是一个活到老学到老的行业,所以面对新技术,不要抵触,拥抱变化就好了。
Flutter 明显是一种全新的技术,而对于这个新技术在发布之初,花一个月的时间学习它,成本确实过高。但是周末花一天时间体验一下它的开发流程,了解一下它的优缺点、能干什么或者不能干什么。这个时间,并不是我们不能接受的。
如果有时间,其实通读一遍 Flutter 的文档,是最全面的一次对 Flutter 的了解过程。但是如果我们只有 8 小时的时间,我希望能关注一些最值得关注的点。
CodeChina开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》
(跨平台开发(Flutter)、java基础与原理,自定义view、NDK、架构设计、性能优化、完整商业项目开发等)
开发流程,了解一下它的优缺点、能干什么或者不能干什么。这个时间,并不是我们不能接受的。
如果有时间,其实通读一遍 Flutter 的文档,是最全面的一次对 Flutter 的了解过程。但是如果我们只有 8 小时的时间,我希望能关注一些最值得关注的点。
CodeChina开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》
(跨平台开发(Flutter)、java基础与原理,自定义view、NDK、架构设计、性能优化、完整商业项目开发等)
[外链图片转存中…(img-tAj1jCa7-1630506772430)]
|