关于创建Android Library所须要知道的一切
Android 库(Library)在结构上与 Android 应用模块同样。应用模块所能够包括的东西。在库中都同意存在,包括代码文件、资源文件和manifest文件等。
应用模块编译后生成的是一个apk文件,能够直接在设备上执行,可是,库模块编译后生成的是一个Android Archive文件,简称AAR。
AAR文件无法像apk文件一样直接在设备上执行,我们一般用它作为Android app的依赖。
普通JAR文件仅仅能包括代码文件和清单文件,而ARR文件不仅能够包括代码文件。还能够包括Android的资源文件和manifest文件。这样。我们就能够把资源文件像布局文件、图片文件等和Java代码文件一起分享出去。能够说ARR文件是真正专属于Android的“JAR”包。
库模块在下面情况下非常实用:
- 创建多个app。这些app须要使用多个同样的组件。像activity、service或UI 布局等。
- 创建一个app。而这个app可能须要依据须要编译成多个APK版本号,比方免费版和付费版,而两个版本号都须要使用到同样的组件。
在不论什么一种情况下,你仅仅须要将要重用的文件放到库模块中,然后以依赖项的形式为每一个应用模块加入库就可以。
创建库模块
在你的工程中,创建一个新的库模块。能够遵循例如以下的步骤:
- 点击 File > New > New Module.
- 在Create New Module的窗体中。选择Android Library。并点击下一步(Next)。
在该窗体中还有一个选项用于创建一个Java Library,Java Library就是我们所知的传统的JAR文件。JAR文件在非常多工程中十分实用,尤其当你想分享代码给其它工程的时候。可是JAR文件并不同意包括Android资源文件和manifest文件,而资源文件在Android项目中对代码重用具有非常大的帮助。所以本篇主要对Android库作介绍。 - 为你的库命名并选择最低SDK版本号号,然后点击Finish,完毕创建。
仅仅要Gradle同步完毕后,库模块就会出现左边的工程面板中。
应用模块转成库模块
假设你有一个已经存在的应用模块。并想重用它的全部代码。你能够把它转成一个库模块:
1.打开属于该应用模块下的build.gradle文件,在最顶部,你能够看见例如以下的显示:
java apply plugin: ‘com.android.application‘
2.把应用的插件改成库的插件:
java apply plugin: ‘com.android.library‘
3.点击Sync Project with Gradle Files.
处理完上面这些,整个模块的结构不会被改变,可是该模块已经变为了库模块,编译后生成的是AAR文件而不再是APK文件了。
加入库作为应用的依赖
为了在应用模块中使用库模块。你须要作例如以下的处理:
1.加入库到工程中有两种方式(假设你是在同样项目中创建的库模块,则该模块已经存在,您能够跳过此步骤)
- 加入编译后的ARR(或JAR)文件:
1.点击 File > New Module.
2.在Create New Module的窗体中,点击 Import .JAR/.AAR Package 然后点击 Next.
3.输入ARR或JAR文件所在的路径。并点击Finish。创建后例如以下所看到的:
- 导入外部库模块到工程中:
1.点击 File > New > Import Module.
2.输入Library模块所在的路径,并点击Finish。创建后例如以下所看到的:
这两种引入库的方式有所不同。假设直接引入的是库模块,你能够对库的代码进行编辑。
可是假设导入的是AAR文件,那么则无法进行编辑,就像JAR文件一样。
2.当库模块或AAR文件引入到工程后。请确保库被列在settings.gradle文件里,就例如以下所看到的。当中mylibrary是库的名称:
include ‘:app‘, ‘:mylibrary‘
3.打开应用模块下的build.gralde文件,并在dependencies块中加入新的一行,使之成为该应用的依赖。例如以下片段所看到的:
dependencies {
compile project(":mylibrary")
}
4.点击 Sync Project with Gradle Files.
配置完上面的信息后,名为mylibrary的库模块就会成为应用的依赖。
然后你就能够在应用模块中读取不论什么属于库模块的代码和资源文件。
还有一种使用本地aar文件的方式
事实上我们还有一种引入本地aar文件的方式。首先在工程的下先建立一个aar文件夹,专门用于存放aar文件,然后在应用的build.gradle加入例如以下配置:
repositories {
flatDir {
dirs ‘../aar‘ // aar文件夹
}
}
然后将aar文件复制到工程/aar文件夹下,在应用模块的dependencies中加入aar引用:
compile(name: ‘mylibrary-debug‘, ext: ‘aar‘)
通过上面的配置,这样aar就被引入过来了。这样的方式与上面介绍的引入方式有点不同,上面作法是把引入的aar文件封装成一个独立的模块,然后以compile project的方式引入。而如今的这样的方式有点像jar包的引入方式。
注意:
依据上面的条件,假设把flatDir配置在project的gradle文件里allprojects.repositories块下面,发现app项目无法识别到aar文件。通过规律发现,无论aar文件放在哪里,仅仅要在app的gradle中配置flatDir都能够被识别。可是假设flatDir配置在project的gradle中,仅仅能把aar文件放到app的模块下才干被识别。
生成AAR文件
我们能够通过点击Build > Make Project生成aar文件,aar文件会在project-name/module-name/build/outputs/aar/ 下生成。普通情况下会有两个aar文件,一个debug版本号。一个release版本号。
当我们拿到后aar文件后。就能够把它公布出去。其它小伙伴就能够利用上面的方式引入aar文件到工程中了。
AAR文件解刨
AAR 文件的文件扩展名为 .aar,该文件本身就是一个zip文件,必须要包括下面内容:
- /AndroidManifest.xml
- /classes.jar
- /res/
- /R.txt (由R.java转换而来)
此外,AAR文件可能包括下面可选条目中的一个或多个:
- /assets/
- /libs/name.jar
- /jni/abi_name/name.so(当中 abi_name 是 Android 支持的 ABI 之中的一个)
- /proguard.txt
- /lint.jar
库的私有资源
默认情况下库中的全部资源都是公开状态。也就是说同意应用模块直接訪问。可是假设你想让库中的资源仅供内部使用,而不想暴露给外部。您应通过声明一个或多个公开资源的方式来使用这样的自己主动私有标识机制。资源包括您项目的 res/ 文件夹中的全部文件,比如图像、布局等。
首先,在库的res/values/下新建一个public.xml文件(假设不存在的话),然后在public.xml中定义公开的资源名。下面的演示样例代码能够创建两个名称分别为 lib_main_layout和 mylib_public_string的公开布局资源和字符串资源:
<resources>
<public name="lib_main_layout" type="layout"/>
<public name="mylib_public_string" type="string"/>
</resources>
上面的定义的两个资源表示公开状态,能够被外部依赖直接訪问。而没有被定义在当中的资源都为隐式私有状态,外部依赖无法合法訪问。
当中name为资源名,type是资源类型有:string、layout、drawable、dimen等。
注意,假设想让库中的全部资源都为私有的,你必须要在public.xml中定义至少一个属性。
在外部依赖使用库私有资源的时候,你是无法通过R点的方式进行提示的。这也为了不暴露私有资源的一种手段。假设你强制使用了该资源,编译器会发出警告:
从上面能够看出。lib_main_layout和mylib_public_string资源都能够直接使用的,而未定义的都为私有资源,外部依赖使用的时候,编译器会发出警告信息。
可是这里有一点须要注意,使用私有资源并不会发生不论什么错误。应用模块能够正常的使用这些私有资源。之所以提供这样的机制,是为了告诉你。库模块并不想把这些资源暴露给你,可能这些资源有特殊用途之类的。假设你真想使用私有资源。而且不想编译器发出如上的警告,你能够把私有资源拷到自己的应用模块下。
隐私的赋予资源私有属性不仅能够一定程度上防止外部使用,而且还同意你重命名或删除私有资源时,不会影响到使用到该库的应用模块。私有资源不在代码自己主动完毕和 Theme Editor 的作用范围内,而且假设您尝试引用私有资源,Lint 将显示警告。
库开发注意事项
将库模块引用加入至您的Android 应用模块后,库模块会依据优先级的顺序与应用模块进行合并。
资源合并冲突
- 构建工具会将库模块中的资源与相关应用模块的资源合并。
假设在两个模块中均定义了同样的资源 ID,那就默认使用应用模块的资源。
- 假设多个 AAR 库之间发生冲突,将使用依赖项列表首先列出(位于 dependencies 块顶部)的库中的资源。
为了避免经常使用资源 ID 的资源冲突。请使用在模块(或在全部项目模块)中具有唯一性的前缀或其它一致的命名方案。
我们举个样例来证明观点1。观点2感兴趣的同学能够自己验证。
首先在库模块mylibraryone中定义了例如以下的string资源:
<resources>
<string name="app_name">My Library</string>
<string name="test_one">My name is Library</string>
<string name="my_library">Library</string>
</resources>
通过该库的R文件,这三个资源文件的id值为:app_name=0x7f020000、my_library=0x7f020001、test_one=0x7f020002
然后在应用模块mytesttwo中这也定义了例如以下的string资源:
<resources>
<string name="app_name">MyTestTwo</string>
<string name="test_one">My name is App</string>
</resources>
请注意。当中资源名app_name 和test_one 和库中定义的string资源名一样。
我们把mylibraryone库该作为mytesttwo应用的依赖,并又一次编译。大家能够发如今应用模块生成了两个R文件:
当中第一个是库合并过来后的R文件,而第二个是应用自己的R文件。
我们对照下。两个R文件的内容:
mylibraryone:
public final class R {
public static final class string {
public static final int app_name = 0x7f040000;
public static final int my_library = 0x7f040001;
public static final int test_one = 0x7f040002;
}
}
mytesttwo:
public final class R {
.....
public static final class mipmap {
public static final int ic_launcher=0x7f020000;
}
public static final class string {
public static final int app_name=0x7f040000;
public static final int my_library=0x7f040001;
public static final int test_one=0x7f040002;
}
}
mylibraryone库的R文件仅仅包括自己的资源,而且全部的资源值都发生了改变。而且库中的资源id也都合并到应用的R文件里了。
从上面的两个文件能够看出一个特性:
用库的R文件和应用的R文件都能訪问到库的资源。可是无法用库的R文件訪问应用资源。
既然如今库的资源和应用的资源如今进行了合并。那当我们使用test_one字符串的时候用的是哪一个呢?我们在应用模块下直接输出id值来瞧瞧:
Log.d("cryc","App:"+Integer.toHexString(com.example.mytesttwo.R.string.test_one)+"");
Log.d("cryc","App:"+getString(com.example.mytesttwo.R.string.test_one)+"");
Log.d("cryc","Library:"+Integer.toHexString(com.example.mylibraryone.R.string.test_one)+"");
Log.d("cryc","Library:"+getString(com.example.mylibraryone.R.string.test_one));
Log.d("cryc","Library:"+Integer.toHexString(com.example.mylibraryone.R.string.my_library));
Log.d("cryc","Library:"+getString(com.example.mylibraryone.R.string.my_library));
输出结果:
App:7f040002
App:My name is App
Library:7f040002
Library:My name is App
Library:7f040001
Library:Library
大家能够看出,假设库和应用的资源名冲突了,无论使用哪个R文件。都那默认使用应用的资源。
大家也许还有疑问。假设我在库中使用test_one资源,那究竟是使用库的资源还是应用的资源?答案是应用的资源,由于库被合并到应用后。库的R文件资源id值都发生了变化。
而我们用R文件去訪问资源的时候。都是拿变化后的R文件去訪问,所以假设有资源冲突默认都是以应用资源为准。所以这里我也能够得出还有一个结论:
当库和应用模块资源冲突的情形下,无论在应用中还是在库中使用该资源。都默认以应用资源为主。前提是应用模块有依赖该库模块。
所以为了避免经常使用资源 ID 的资源冲突。请使用在模块(或在全部项目模块)中具有唯一性的前缀或其它一致的命名方案。
比方库名是PullToRefresh。那么该库下的资源命名能够用ptr作为前缀。
关于R文件:
R文件(R.java)是由Android 资源打包工具AAPT(Android Asset Packaging Tool))自己主动生成,包括了res文件夹下全部资源的Id。
每当创建一个新资源,会自己主动地在R文件里加入该资源的id。我们能够在代码中使用该id,执行不论什么有关该资源的操作。注意,假设我们手动删除R文件。编译器会自己主动创建。
R文件是一个java文件,由于它是被自己主动创建的,所以Android studio 会把它进行隐藏,详细位置在 app/build/generated/source/r/debug
资源冲突和私有资源的问题
当Library模块中存在私有资源,假设应用模块资源名和私有资源名冲突了,编译器会发出警告:
当我们在应用中使用该资源时,也会发出该警告:
尽管我们使用该资源时用的是应用模块的资源。可是库已经把test_one标为私有资源,为了规范化。我能够採取例如以下措施:
- 在应用模块中更换不同的资源名。不要与库中的资源名一样。
- 假设真的要使用同名资源,使用tools标记为重写状态:
<resources xmlns:tools="http://schemas.android.com/tools">
<string name="app_name">MyTestTwo</string>
<string name="test_one" tools:override="true">My name is App</string>
</resources>
此方式并无法取消私此资源是私有资源的状态,仅仅只是取消了资源文件里的警告而已。
asserts合并冲突
当应用依赖库时,应用的assert文件夹会和库的asserts文件夹进行合并,假设有同样路径文件,则以应用模块的为准。比如,应用模块存在asserts/ha.json文件,库模块下也有asserts/ha.json文件,由于两个路径一样。当合并后apk中仅仅保留应用模块asserts/ha.json。假设库模块的ha.json文件是存放在assert/json文件夹下。那么当合并后,两个json文件都存在。 由于它们路径不一样。一个是asserts/ha.json 还有一个是asserts/json/ha.json。
谷歌官方说:工具不支持在库模块中使用原始资源文件(保存在 assets/ 文件夹中),可是经过我的測试,在应用模块中能够任意使用库中的assets资源并无不论什么问题。
关于asserts文件夹
Android资源文件大致能够分为两种:
第一种是res文件夹下存放的可编译的资源文件:这样的资源文件系统会在R.java里面自己主动生成该资源文件的ID,所以訪问这样的资源文件比較简单。通过R.XXX.ID就可以;
另外一种是assets文件夹下存放的原生资源文件:
由于系统在编译的时候不会编译assets下的资源文件。所以我们不能通过R.XXX.ID的方式訪问它们。
那我么能不能通过该资源的绝对路径去訪问它们呢?由于apk安装之后会放在/data/app/**.apk文件夹下。以apk形式存在,asset/res和被绑定在apk里,并不会解压到/data/data/YourApp文件夹下去。所以我们无法直接获取到assets的绝对路径。由于它们根本就没有。
还好Android系统为我们提供了一个AssetManager工具类。查看官方API可知,AssetManager提供相应用程序的原始资源文件进行訪问;这个类提供了一个低级别的API。它同意你以简单的字节流的形式打开和读取和应用程序绑定在一起的原始资源文件。
应用模块的 minSdkVersion 必须大于或等于库定义的版本号
库作为相关应用模块的一部分编译,因此。库模块中使用的 API 必须与应用模块支持的平台版本号兼容。
每一个库模块都会创建自己的 R 类
在您构建相关应用模块时,库模块将先编译到 AAR 文件里。然后再加入到应用模块中。因此,每一个库都有其自己的 R 类,并依据库的软件包名称命名。
从主模块和库模块生成的 R 类会在所需的全部软件包(包括主模块的软件包和库的软件包)中创建。
库模块可能包括自己的 ProGuard 配置文件
通过将 ProGuard 配置文件加入到包括其 ProGuard 指令的库,您能够在自己的库上启用代码压缩。构建工具会为库模块将此文件嵌入到生成的 AAR 文件里。在您将库加入到应用模块时,库的 ProGuard 文件将附加至应用模块的 ProGuard 配置文件 (proguard.txt)。
通过将 ProGuard 文件嵌入到您的库模块中。您能够确保依赖于此库的应用模块不必手动更新其 ProGuard 文件就可以使用库。当 ProGuard 在 Android 应用模块上执行时。它会同一时候使用来自应用模块和库的指令,因此您不应当仅仅在库上执行 ProGuard。
要指定您的库的配置文件名,请将其加入到 consumerProguardFiles 方法中,此方法位于您的库的 build.gradle 文件的 defaultConfig 块内。比如,下面片段会将 lib-proguard-rules.txt 设置为库的 ProGuard 配置文件:
android {
defaultConfig {
consumerProguardFiles ‘lib-proguard-rules.txt‘
}
...
}
默认情况下,应用模块会使用库的公布构建,即使在使用应用模块的调试构建类型时亦是如此。要使用库中不同的构建类型,您必须将依赖项加入到应用的 build.gradle 文件的 dependencies 块中。并在库的 build.gradle 文件里将 publishNonDefault 设置为 true。比如。您应用的 build.gradle 文件里的下面代码段会使应用在应用模块于调试模式下构建时使用库的调试构建类型,以及在应用模块于公布模式下构建时使用库的公布构建类型:
dependencies {
debugCompile project(path: ‘:library‘, configuration: ‘debug‘)
releaseCompile project(path: ‘:library‘, configuration: ‘release‘)
}
您还必须在自己库的 build.gradle 文件的 android 块内加入下面代码行。以便将此库的非公布配置展示给使用它的项目:
android {
...
publishNonDefault true
}
只是请注意。设置 publishNonDefault 会添加构建时间。
为了确保您的库的 ProGuard 规则不会将意外的压缩副作用施加到应用模块,请仅包括适当规则,停用不适用于此库的 ProGuard 功能。
尝试协助开发人员的规则可能会与应用模块或它的其它库中的现有代码冲突,因此不应包括这些规则。比如。您的库的 ProGuard 文件能够指定在应用模块的压缩期间须要保留的代码。
注:Jack 工具链仅支持 ProGuard 的部分压缩和模糊选项
參考文档
https://developer.android.com/studio/projects/android-library.html#aar-contents
http://www.jianshu.com/p/59efa895589e
http://tikitoo.github.io/2016/05/26/android-studio-gradle-build-run-faster/