










模块:
特点:
组件:
特点:
application组件: 是指该组件本身就可以运行并打包成apk
lib组件: 是指该组件属于app的一部分,可以供其它组件使用但是本身不能打包成apk
正常一个App中可以有多个module(模块),但是一般只会有一个module是设置为application的,其他均设置为library; 组件化开发就是要每个module都可以运行起来,因此在开发期间每个module均设置为application,发布时再进行合并。
Android项目中代码量达到一定程度,编译将是一件非常痛苦的事情; 短则一两分钟,长则达到五六分钟;随着app业务的壮大,模块越来越多,代码量超10万是很正常的
这个时候我们会遇到以下问题:
组件化开发可以有效降低代码模块的耦合度,使代码架构更加清晰,同时模块化的编译可以有效减少编译时间,当然总的编译时间是不会减少的,只是App模块化之后开发某个模块时,只需要编译特定模块,可以快速编译调试
基础类库主要是将各个组件中都会用到的一些基础库统一进行封装,例如网络请求、图片缓存、sqlite操作、数据加密等基础类库
这样可以避免各个组件都在自己的组件中单独引用,而且引用的版本可能都不一样,导致整个工程外部库混乱,统一了基础类库后,基础类库保持相对的稳定,这样各个组件对外部库的使用是相对可控的,防止出现一些外部库引出的极端问题,而且这样的话对于库的升级也比较好管理
对于每个组件都有一些是公共的抽象,例如我们工程中自己定义的BaseActivity、BaseFragment、自定义控件等,这些对于每个组件都是一样的,每个组件都基于一样的基础工程开发,一方面可以减少开发工作,另一方面也可以让各个组件的开发人员能够统一架构框架
这样每个组件的技术代码框架看起来都是一样的,也便于后期代码维护和人员互备
应用的主要业务逻辑在此实现,上面的几部分都实现以后,剩余的主要体力工作就是实现各个拆分出来的业务模块
壳工程主要用于将各个组件组合起来和做一些工程初始化,初始化包含了后续各个组件会用到的一些库的初始化,也包括ApplicationContext的初始化工作
在 Android 系统中,应用是以 Apk 的形式存在的,应用都需要安装才能使用; 但实际上 Android 系统安装应用的方式相当简单,其实就是把应用 Apk 拷贝到系统不同的目录下、然后把 so 解压出来而已
常见的应用安装目录有:
Apk 的构成,一个常见的 Apk 会包含如下几个部分:
其实 Android 系统在打开应用之后,也只是开辟进程,然后使用 ClassLoader 加载 classes.dex 至进程中,执行对应的组件而已
那大家可能会想一个问题,既然 Android 本身也是使用类似反射的形式加载代码执行,凭什么我们不能执行一个 Apk 中的代码呢?
这其实就是插件化的目的: 让 Apk 中的代码(主要是指 Android 组件)能够免安装运行,这样能够带来很多收益,最显而易见的优势其实就是通过网络热更新、热修复
说起热修复,已经是目前Android开发必备技能
我所了解的一种实现方式就是类加载方案,即 dex 插桩; 这种思路在插件化中也会用到;除此之外,还有底层替换方案,即修改替换 ArtMethod;采用类加载方案的主要是以腾讯系为主,包括微信的 Tinker、饿了么的 Amigo;采用底层替换方案主要是阿里系的 AndFix 等热修复的应用场景
热修复就是在APP上线以后,如果突然发现有缺陷了,如果重新走发布流程可能时间比较长,重新安装APP用户体验也不会太好; 热修复就是通过发布一个插件,使APP运行的时候加载插件里面的代码,从而解决缺陷,并且对于用户来说是无感的(用户也可能需要重启一下APP)
Java负责加载class文件的就是类加载器(ClassLoader); APP启动的时候,会创建一个自己的ClassLoader实例,我们可以通过下面的代码拿到当前的ClassLoader
<pre mdtype="fences" cid="n108" lang="" class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">ClassLoader classLoader = getClassLoader();
Log.i(TAG, "[onCreate] classLoader" + ":" + classLoader.toString());</pre>
然后我们在看一下构造函数。在ClassLoader 这个类中的 loadClass() 方法,它调用的是另一个2个参数的重载 loadClass() 方法
<pre mdtype="fences" cid="n115" lang="" class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
</pre>
我们点进去深入看一下loadClass这个方法:
<pre mdtype="fences" cid="n120" lang="" class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" > protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
c = findClass(name);
}
}
return c;
}
</pre>
通过分析loadClass方法,你会发现,ClassLoader加载类的方法就是loadClass,是通过双亲委派模型(Parents Delegation Model)实现类的加载的; 既在加载一个字节码文件时,会询问当前的classLoader是否已经加载过此字节码文件
如果加载过,则直接返回,不再重复加载; 如果没有加载过,则会询问它的Parent是否已经加载过此字节码文件,同样的,如果已经加载过,就直接返回parent加载过的字节码文件,而如果整个继承线路上的classLoader都没有加载过,才由child类加载器(即,当前的子classLoader)执行类的加载工作
整个流程大致可以归纳成如下三步
1、加载流程
检查当前的 classLoader 是否已经加载琮这个 class ,有则直接返回,没有则进行第2步。 调用父 classLoader 的 loadClass() 方法,检查父 classLoader 是否有加载过这个 class ,有则直 接返回,没有就继续检查上上个父 classLoader ,直到顶层 classLoader 。 如果所有的父 classLoader 都没有加载过这个 class ,则最终由当前 classLoader 调用 findClass() 方法,去dex文件中找出并加载这个 class
2、优点
而采用这种类的加载机制的优点就是如果一个类被classLoader继承线路上的任意一个加载器加载过,后续在整个系统的生命周期中,这个类都不会再被加载,大大提高了类的加载效率
类加载的共享功能: 一些Framework层级的类一旦被顶层,classLoader加载过,会缓存到内存中,以后在任何地方用到,都不会去重新加载,大大提高了效率;
类加载的隔离功能: 不同继承线路上的 classLoader 加载的类,肯定不是同一个类,这样可以避免某些开发者自己去写一 些代码冒充核心类库,来访问核心类库中可见的成员变量。如 java.lang.String 在应用程序启动前就 已经被系统加载好了,如果在一个应用中能够简单的用自定义的String类把系统中的String类替换掉 的话,会有严重的安全问题
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。