1. 简介
在 Android 开发中,JAR(Java Archive)和 AAR(Android Archive)是两种常见的打包格式,用于封装和复用代码库。
2. JAR 与 AAR 的区别与用途
-
JAR (Java Archive)
-
用途:适用于纯 Java/Kotlin 代码库的打包。
-
内容:包含编译后的
.class
文件和可选的META-INF / MANIFEST.MF
文件。 -
特点:
-
不包含 Android 资源文件(如布局、图片等)。
-
不能包含
AndroidManifes.xml
-
适用于平台无关的通用逻辑库。
-
-
使用场景:
-
封装通用的工具类(如加密、网络请求封装等)。
-
在多个 Java 或 Android 项目中共享通用逻辑。
-
-
-
AAR (Android Archive)
-
用途:专为 Android 平台设计,适用于包含资源和组件的库。
-
内容:包含编译后的
.class
文件、AndroidManifest.xml
、资源文件(如布局、图片—)等。 -
特点:
-
可以包含 Android 资源和清单文件。
-
支持包含本地库(
.so
文件)。 -
适用于封装具有 UI 组件的库。
-
-
使用场景:
-
封装自定义控件、布局、主题等可视化组件。
-
在多个 Android 项目中共享完整的功能模块。
-
-
特性 | JAR包 | AAR包 |
---|---|---|
包含内容 | 仅包含.class 文件 | 包含.class 、资源文件、清单等 |
是否包含资源文件 | 否 | 是 |
是否包含清单文件 | 否 | 是(AndroidManifest.xml ) |
使用场景 | 通用工具类库 | Android UI 组件、资源库 |
适用平台 | Java 平台 | Android 平台 |
简单来说,工具类相关的封装到 JAR 包中,自定义 View、布局相关的封装到的 AAR 包中。
3. 创建 JAR 包
-
创建 Java or Kotlin Library 模块
-
在 Andorid Studio 中,选择
File -> New -> New Module
-
选择
Java or Kotlin Library
,填写模块名称等信息,点击Finish
-
-
编写代码
我们在创建的 utils 模块中创建我们的工具类代码,例如我们写一个对文件处理的通用工具类
import java.io.* import java.nio.charset.Charset /** * 文件操作工具类 */ object FileUtils { // ======================== 文件/目录检查与创建 ======================== /** * 检查文件是否存在 * @param filePath 文件绝对路径 * @return Boolean 是否存在 */ fun isFileExist(filePath: String): Boolean = File(filePath).exists() /** * 创建目录(若不存在) * @param dirPath 目录路径 * @return Boolean 是否成功创建或已存在 */ fun createDirIfNotExists(dirPath: String): Boolean { val dir = File(dirPath) return if (!dir.exists()) dir.mkdirs() else true } /** * 创建空文件(若不存在) * @param filePath 文件路径 * @return Boolean 是否成功创建或已存在 */ fun createFileIfNotExists(filePath: String): Boolean { val file = File(filePath) if (file.exists()) return true return try { file.parentFile?.mkdirs() // 创建父目录 file.createNewFile() } catch (e: IOException) { e.printStackTrace() false } } // ======================== 文件读写 ======================== /** * 将字符串写入文件(覆盖模式) * @param filePath 文件路径 * @param content 字符串内容 * @param charset 字符集(默认UTF-8) * @return Boolean 是否成功 */ @JvmOverloads fun writeStringToFile( filePath: String, content: String, charset: Charset = Charsets.UTF_8 ): Boolean { return try { File(filePath).writeText(content, charset) true } catch (e: IOException) { e.printStackTrace() false } } /** * 从文件读取字符串内容 * @param filePath 文件路径 * @param charset 字符集(默认UTF-8) * @return String? 内容或null */ @JvmOverloads fun readFileToString(filePath: String, charset: Charset = Charsets.UTF_8): String? { return try { File(filePath).readText(charset) } catch (e: IOException) { e.printStackTrace() null } } // ======================== 文件操作(复制、删除、移动) ======================== /** * 复制文件(使用NIO高效复制) * @param srcPath 源文件路径 * @param destPath 目标文件路径 * @return Boolean 是否成功 */ fun copyFile(srcPath: String, destPath: String): Boolean { return try { FileInputStream(File(srcPath)).channel.use { srcChannel -> FileOutputStream(File(destPath)).channel.use { destChannel -> srcChannel.transferTo(0, srcChannel.size(), destChannel) true } } } catch (e: Exception) { e.printStackTrace() false } } /** * 删除文件或目录(递归删除) * @param path 文件/目录路径 * @return Boolean 是否成功 */ fun deleteRecursively(path: String): Boolean { val file = File(path) return if (file.isDirectory) { file.listFiles()?.forEach { deleteRecursively(it.absolutePath) } file.delete() } else { file.delete() } } /** * 重命名文件/目录 * @param srcPath 原路径 * @param newName 新名称(不含路径) * @return Boolean 是否成功 */ fun renameFile(srcPath: String, newName: String): Boolean { val srcFile = File(srcPath) val destFile = File(srcFile.parent, newName) return srcFile.renameTo(destFile) } // ======================== 文件属性获取 ======================== /** * 获取文件大小(字节) * @param filePath 文件路径 * @return Long 文件大小(-1表示失败) */ fun getFileSize(filePath: String): Long { return try { File(filePath).length() } catch (e: SecurityException) { -1 } } }
-
构建 JAR 包
-
我们点击打开右侧的
Gradle
工具栏,打开我们的 JAR 模块,展开Tasks > build
,双击build
任务。注意: 如果我们右侧的
Gradle
中没有 build,我们要在Settings -> Experimental
中开启然后再重新执行
Sync Project with Gradle File
,就会发现我们右侧的Gradle
中就出现了 build。 -
构建完成后,JAR 文件位于:
-
<project_root>/<module_name>/build/libs/
-
-
-
使用 JAR 包
-
我们在自己的 app 模块中,创建一个 libs 文件夹,将我们构建 JAR 包放入其中
-
在项目级的
build.gradle.kts
中添加本地 JAR 包依赖implementation(files("/libs/utils.jar"))
然后我们就可以在这个模块中使用我们 JAR 包提供的工具类了。
-
4. 创建 AAR 包
-
创建 Android Library 模块
-
在 Andorid Studio 中,选择
File -> New -> New Module
-
选择 Android Library,填写模块名称等信息,点击
Finish
-
-
编写代码
我们在创建的 components 模块中创建我们自定义的组件,例如我们写一个圆形
ImageView
-
创建
res -> values -> attrs.xml
<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="CircleImageView"> <attr name="borderWidth" format="dimension"/> <attr name="borderColor" format="color"/> <attr name="forceSquare" format="boolean"/> </declare-styleable> </resources>
-
创建
CircleImageView
import android.content.Context import android.graphics.* import android.util.AttributeSet import androidx.appcompat.widget.AppCompatImageView import androidx.core.graphics.drawable.toBitmap class CircleImageView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : AppCompatImageView(context, attrs, defStyleAttr) { // 画笔配置 private val paint = Paint(Paint.ANTI_ALIAS_FLAG) private val borderPaint = Paint(Paint.ANTI_ALIAS_FLAG) private val shaderMatrix = Matrix() // 绘制区域 private var drawRect = RectF() private var borderRect = RectF() // 属性配置 private var borderWidth = 0f private var borderColor = Color.WHITE private var isSquare = false // 是否强制为正方形 init { // 从XML属性读取配置 val ta = context.obtainStyledAttributes(attrs, R.styleable.CircleImageView) borderWidth = ta.getDimension(R.styleable.CircleImageView_borderWidth, 0f) borderColor = ta.getColor(R.styleable.CircleImageView_borderColor, Color.WHITE) isSquare = ta.getBoolean(R.styleable.CircleImageView_forceSquare, false) ta.recycle() // 初始化画笔 borderPaint.style = Paint.Style.STROKE borderPaint.color = borderColor borderPaint.strokeWidth = borderWidth // 默认缩放类型为居中裁剪 scaleType = ScaleType.CENTER_CROP } override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { super.onSizeChanged(w, h, oldw, oldh) updateDimensions() } private fun updateDimensions() { val width = if (isSquare) width.coerceAtMost(height) else width val height = if (isSquare) height.coerceAtMost(width) else height val size = width.coerceAtMost(height) - borderWidth * 2 val halfBorder = borderWidth / 2 // 图片绘制区域 drawRect.set( halfBorder, halfBorder, size + halfBorder, size + halfBorder ) // 边框绘制区域 borderRect.set( halfBorder, halfBorder, size + halfBorder, size + halfBorder ) // 更新着色器矩阵 updateShaderMatrix() } private fun updateShaderMatrix() { val bitmap = (drawable?.toBitmap()) ?: return val scale: Float var dx = 0f var dy = 0f val bitmapWidth = bitmap.width.toFloat() val bitmapHeight = bitmap.height.toFloat() when (scaleType) { ScaleType.CENTER_CROP -> { val widthScale = drawRect.width() / bitmapWidth val heightScale = drawRect.height() / bitmapHeight scale = widthScale.coerceAtLeast(heightScale) dx = (drawRect.width() - bitmapWidth * scale) * 0.5f dy = (drawRect.height() - bitmapHeight * scale) * 0.5f } // 其他缩放类型可根据需要添加 else -> { scale = 1f } } shaderMatrix.setScale(scale, scale) shaderMatrix.postTranslate(dx + drawRect.left, dy + drawRect.top) val shader = BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP) shader.setLocalMatrix(shaderMatrix) paint.shader = shader } override fun onDraw(canvas: Canvas) { if (drawable == null) { super.onDraw(canvas) return } // 绘制圆形图片 canvas.drawOval(drawRect, paint) // 绘制边框 if (borderWidth > 0) { canvas.drawOval(borderRect, borderPaint) } } // 属性设置方法 fun setBorderWidth(width: Float) { borderWidth = width borderPaint.strokeWidth = width updateDimensions() invalidate() } fun setBorderColor(color: Int) { borderColor = color borderPaint.color = color invalidate() } }
-
-
构建 AAR 包
-
在右侧的
Gradle
面板中,找到 components 模块。 -
展开
Tasks > build
,双击build
任务。 -
构建完成后,AAR 文件位于:
-
<project_root>/<module_name>/build/outputs/aar/
-
-
-
使用 AAR 包
-
将我们构建的 AAR 包放到项目的 libs 下
-
在项目级的
build.gradle.kts
中添加本地 AAR 包依赖implementation(files("/libs/components-debug.aar"))
-
在 xml 中直接使用,效果如下
-
-
将 AAR 包配置在本地 Maven 仓库
-
在 AAR 模块中的 build.gradle.kts 中进行
plugins { alias(libs.plugins.android.library) alias(libs.plugins.kotlin.android) id("maven-publish") // 引入用于发布到 Maven 仓库的插件 } configure<PublishingExtension> { repositories { mavenLocal() // 指定发布目标为本地 Maven 仓库 (~/.m2/repository) } } afterEvaluate { extensions.configure<PublishingExtension>("publishing") { publications { create<MavenPublication>("release") { // 创建名为 release 的发布配置(与 release 构建变体关联) groupId = "com.example" // 组织 ID,发布后的依赖路径一部分 artifactId = "components" // 组件 ID,依赖路径一部分 version = "v1.0.0" // 版本号 from(components["release"]) // 从 release 构建变体中发布 AAR 包 } } } } android { namespace = "com.example.components" compileSdk = 35 defaultConfig { minSdk = 21 testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles("consumer-rules.pro") } buildTypes { release { isMinifyEnabled = false proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" ) } } compileOptions { sourceCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_11 } kotlinOptions { jvmTarget = "11" } } dependencies { implementation(libs.androidx.core.ktx) implementation(libs.androidx.appcompat) implementation(libs.material) testImplementation(libs.junit) androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) }
编译之后,我们在
-
在 macOS 上:/Users/<用户名>/.m2/repository
-
在 Linux 上:/home/<用户名>/.m2/repository
-
在 Windows 上:C:\Users<用户名>.m2\repository
查看是否生成成功,
接着我们在全局的
settings.gradle.kts
中添加mavenLocal()
,然后我们需要使用的项目中添加依赖implementation("com.example:components:v1.0.0")
运行结果和添加 AAR 包一样。
-
-
5. JAR 和 AAR 的面试题
-
JAR 和 AAR 的主要区别是什么?
答:
-
JAR(Java Archive):主要用于打包 Java 类文件(
.class
)、资源文件(如.properties
)和MANIFEST.MF
文件,不包含 Android 资源(如布局、图片等)或AndroidManifest.xml
。 -
AAR(Android Archive):专为 Android 开发设计,除了包含
.class
文件外,还包括资源文件(如.xml
、.png
)、AndroidManifest.xml
、resources.arsc
和可能的本地库(.so
文件)。
总结: JAR 适用于纯 Java 库,而 AAR 适用于包含 Android 组件和资源的库。
-
-
在什么情况下选择使用 JAR 或AAR ?
答:
-
使用 JAR 的情况:
-
库仅包含 Java 代码,无需 Android 资源或组件。
-
需要跨平台使用,如在 Java SE 或其他非 Android 环境中。
-
-
使用 AAR 的情况:
-
库包含 Android 资源(如布局、图片)或组件(如自定义 View、Service)。
-
需要在 Android 项目中完整集成库的功能。
-
-
-
如何在 Android Studio 中生成 JAR 和 AAR 文件?
答:
-
生成 JAR 文件:
-
在
build.gradle
中应用java-library
插件。 -
运行
./gradlew build
,生成的 JAR 文件位于build/libs/
目录下。
-
-
生成 AAR 文件:
-
在
build.gradle
中应用com.android.library
插件。 -
运行
./gradlew assembleRelease
,生成的 AAR 文件位于build/outputs/aar/
目录下。
-
-
-
如何在项目中引入 JAR 和 AAR 文件?
答:
-
引入 JAR 文件:
-
将 JAR 文件放入项目的
libs/
目录。 -
在
build.gradle
中添加依赖
-
-
引入 AAR 文件:
-
将 AAR 文件放入项目的
libs/
目录。 -
在
build.gradle
中添加依赖
-
-
-
AAR 文件包含哪些内容?
答:AAR 文件通常包含:
-
classes.jar
:编译后的 Java 类文件。 -
AndroidManifest.xml
:库的清单文件。 -
资源文件夹(如
res/
):布局、图片等资源。 -
resources.arsc
:编译后的资源索引。 -
本地库(如
jni/
):.so
文件。
-
-
使用 JAR 和 AAR 各有哪些优缺点?
答:
-
JAR 的优点:
-
结构简单,易于管理。
-
适用于纯 Java 项目或跨平台使用。
-
-
JAR 的缺点:
-
不支持 Android 资源和组件。
-
无法包含
AndroidManifest.xml
。
-
-
AAR 的优点:
-
支持完整的 Android 资源和组件。
-
适用于模块化的 Android 项目。
-
-
AAR 的缺点:
-
结构复杂,体积较大。
-
仅适用于 Android 项目。
-
-
-
如何将 AAR 文件发布到 Maven 仓库?
答:可以使用 Gradle 的
maven-publish
插件,将 AAR 文件发布到本地或远程 Maven 仓库。配置示例:plugins { alias(libs.plugins.android.library) alias(libs.plugins.kotlin.android) id("maven-publish") // 引入用于发布到 Maven 仓库的插件 } configure<PublishingExtension> { repositories { mavenLocal() // 指定发布目标为本地 Maven 仓库 (~/.m2/repository) } } afterEvaluate { extensions.configure<PublishingExtension>("publishing") { publications { create<MavenPublication>("release") { // 创建名为 release 的发布配置(与 release 构建变体关联) groupId = "com.example" // 组织 ID,发布后的依赖路径一部分 artifactId = "components" // 组件 ID,依赖路径一部分 version = "v1.0.0" // 版本号 from(components["release"]) // 从 release 构建变体中发布 AAR 包 } } } }
-
如何在多模块项目中管理 JAR 和 AAR 依赖?
答:
-
将公共模块打包为 AAR,供其他模块引用。
-
使用 Gradle 的
implementation
或api
关键字管理依赖关系。 -
通过设置统一的版本号和依赖路径,确保依赖的一致性。
-
-
JAR 和 AAR 的兼容性如何?
答:
-
JAR 文件可在任何支持 Java 的环境中使用,包括 Android。
-
AAR 文件仅适用于 Android 项目,因其包含 Android 特有的资源和组件。
-
-
如何查看 AAR 文件的内容?
答:AAR 文件本质上是一个 ZIP 压缩包,可以通过解压查看其内容:
解压后,可以看到
classes.jar
、AndroidManifest.xml
、res/
等文件夹和文件。