Fork me on GitHub

从零开始写一个 ARouter - service 服务

  • ARouter
    • router-annotation 路由注解模块

      • src.main.java
        • com.alibaba.android.arouter
          • facade 提供注解和枚举的包
            • annotation 存放各种注解类的包
              • Autowired.java 自动注入的注解
              • Interceptor.java 拦截器的注解
              • Param.java(废弃) 参数注解: 被 Autowired 淘汰
              • Route.java 路由信息注解
            • enums 包含各种枚举类型的包
              • RouteType.java 路由类型的枚举
              • TypeKind.java 类型种类的枚举
            • model 包含模型类的包
              • RouteMeta.java 路由元信息的模型类
              • TypeWrapper.java 存储目标对象的泛型类型信息的类
    • arouter-api ARouter框架的API模块

      • src.main.java
        • com.alibaba.android.arouter
          • base 基础功能相关的包
            • UniqueKeyTreeMap.java 唯一键树形映射的类
          • core 核心功能相关的包
            • AutowiredLifecyleCallback.java(废弃) 自动注入生命周期回调的类
            • AutowiredServiceImpl.java 自动注入服务的实现类
            • InstrumentationHook.java(废弃) 仪表钩子的类
            • InterceptorServiceImpl.java 拦截器服务的实现类
            • LogisticsCenter.java 物流中心的类
            • Warehouse.java 仓库的类
          • exception 异常相关的包
            • HandlerException.java 处理异常的类
            • InitException.java 初始化异常的类
            • NoRouteFoundException.java 未找到路由的异常类
          • facade 提供各种服务和回调的包
            • callback 回调相关的包
              • InterceptorCallback.java 拦截器回调的接口
              • NavigationCallback.java 导航回调的接口
              • NoRouteFoundException.java 未找到路由的异常接口
          • service 服务相关的包
          • template 模板相关的包
            • IInterceptor.java 拦截器接口
            • IInterceptorGroup.java 拦截器分组接口
            • Ilogger.java 日志记录器接口
            • IPolicy.java 策略接口
            • IProvider.java 提供者接口
            • IProviderGroup.java 提供者分组接口
            • IRouteGroup.java 路由分组接口
            • IRouteRoot.java 路由根接口
            • Isyringe.java 注射器接口
            • Postcard.java 路由信息封装类
          • launcher 启动器: 包含一些用于启动ARouter框架的类和线程管理相关的类。
            • _Arouter.java ARouter框架的内部启动类,用于初始化ARouter。
            • Arouter.java ARouter框架的启动类,用于初始化ARouter。
          • thread (线程)包含了与线程管理相关的类。
            • CancelableCountDownLatch.java 可取消的倒计时计数器,用于线程同步。
            • DefaultPoolExecutor.java 默认的线程池执行器,用于执行异步任务。
            • DefaultThreadFactory.java 默认线程工厂,用于创建线程。
          • utils 工具类。
            • ClassUtils.java 用于操作类的实用工具类。
            • Consts.java 包含一些常量值的类。
            • DefaultLogger.java 默认的日志记录器类。
            • MapUtils.java 用于操作地图数据的实用工具类。
            • PackageUtils.java 用于操作包信息的实用工具类。
            • TextUtils.java 用于操作文本数据的实用工具类。
    • arouter-compiler

      • src.main.java
        • com.alibaba.android.arouter
          • compiler 编译相关的包
            • entity 实体类相关的包
              • RouteDoc.java 路由文档实体类
            • processor 处理器相关的包
              • AutowiredProcessor.java 自动注入处理器
              • BaseProcessor.java 基础处理器
              • InterceptorProcessor.java 拦截器处理器
              • RouteProcessor.java 路由处理器
            • utils 工具类相关的包
              • Consts.java 常量类
              • Logger.java 日志记录器类
              • TypeUtils.java 类型工具类

从零开始写一个 ARouter - template 模板

  • ARouter
    • router-annotation 路由注解模块

      • src.main.java
        • com.alibaba.android.arouter
          • facade 提供注解和枚举的包
            • annotation 存放各种注解类的包
              • Autowired.java 自动注入的注解
              • Interceptor.java 拦截器的注解
              • Param.java(废弃) 参数注解: 被 Autowired 淘汰
              • Route.java 路由信息注解
            • enums 包含各种枚举类型的包
              • RouteType.java 路由类型的枚举
              • TypeKind.java 类型种类的枚举
            • model 包含模型类的包
              • RouteMeta.java 路由元信息的模型类
              • TypeWrapper.java 存储目标对象的泛型类型信息的类
    • arouter-api ARouter框架的API模块

      • src.main.java
        • com.alibaba.android.arouter
          • base 基础功能相关的包
            • UniqueKeyTreeMap.java 唯一键树形映射的类
          • core 核心功能相关的包
            • AutowiredLifecyleCallback.java(废弃) 自动注入生命周期回调的类
            • AutowiredServiceImpl.java 自动注入服务的实现类
            • InstrumentationHook.java(废弃) 仪表钩子的类
            • InterceptorServiceImpl.java 拦截器服务的实现类
            • LogisticsCenter.java 物流中心的类
            • Warehouse.java 仓库的类
          • exception 异常相关的包
            • HandlerException.java 处理异常的类
            • InitException.java 初始化异常的类
            • NoRouteFoundException.java 未找到路由的异常类
          • facade 提供各种服务和回调的包
            • callback 回调相关的包
              • InterceptorCallback.java 拦截器回调的接口
              • NavigationCallback.java 导航回调的接口
              • NoRouteFoundException.java 未找到路由的异常接口
          • service 服务相关的包
            • AutowiredService.java 自动注入服务的接口
            • ClassLoaderService.java 类加载器服务的接口
            • DegradeService.java 降级服务的接口
            • InterceptorService.java 拦截器服务的接口
            • PathReplaceService.java 路径替换服务的接口
            • PretreatmentService.java 预处理服务的接口
            • SerializationService.java 序列化服务的接口
          • template 模板相关的包
          • launcher 启动器: 包含一些用于启动ARouter框架的类和线程管理相关的类。
            • _Arouter.java ARouter框架的内部启动类,用于初始化ARouter。
            • Arouter.java ARouter框架的启动类,用于初始化ARouter。
          • thread (线程)包含了与线程管理相关的类。
            • CancelableCountDownLatch.java 可取消的倒计时计数器,用于线程同步。
            • DefaultPoolExecutor.java 默认的线程池执行器,用于执行异步任务。
            • DefaultThreadFactory.java 默认线程工厂,用于创建线程。
          • utils 工具类。
            • ClassUtils.java 用于操作类的实用工具类。
            • Consts.java 包含一些常量值的类。
            • DefaultLogger.java 默认的日志记录器类。
            • MapUtils.java 用于操作地图数据的实用工具类。
            • PackageUtils.java 用于操作包信息的实用工具类。
            • TextUtils.java 用于操作文本数据的实用工具类。
    • arouter-compiler

      • src.main.java
        • com.alibaba.android.arouter
          • compiler 编译相关的包
            • entity 实体类相关的包
              • RouteDoc.java 路由文档实体类
            • processor 处理器相关的包
              • AutowiredProcessor.java 自动注入处理器
              • BaseProcessor.java 基础处理器
              • InterceptorProcessor.java 拦截器处理器
              • RouteProcessor.java 路由处理器
            • utils 工具类相关的包
              • Consts.java 常量类
              • Logger.java 日志记录器类
              • TypeUtils.java 类型工具类

从零开始写一个 ARouter - launcher 启动器

  • ARouter
    • router-annotation 路由注解模块

      • src.main.java
        • com.alibaba.android.arouter
          • facade 提供注解和枚举的包
            • annotation 存放各种注解类的包
              • Autowired.java 自动注入的注解
              • Interceptor.java 拦截器的注解
              • Param.java(废弃) 参数注解: 被 Autowired 淘汰
              • Route.java 路由信息注解
            • enums 包含各种枚举类型的包
              • RouteType.java 路由类型的枚举
              • TypeKind.java 类型种类的枚举
            • model 包含模型类的包
              • RouteMeta.java 路由元信息的模型类
              • TypeWrapper.java 存储目标对象的泛型类型信息的类
    • arouter-api ARouter框架的API模块

      • src.main.java
        • com.alibaba.android.arouter
          • base 基础功能相关的包
            • UniqueKeyTreeMap.java 唯一键树形映射的类
          • core 核心功能相关的包
            • AutowiredLifecyleCallback.java(废弃) 自动注入生命周期回调的类
            • AutowiredServiceImpl.java 自动注入服务的实现类
            • InstrumentationHook.java(废弃) 仪表钩子的类
            • InterceptorServiceImpl.java 拦截器服务的实现类
            • LogisticsCenter.java 物流中心的类
            • Warehouse.java 仓库的类
          • exception 异常相关的包
            • HandlerException.java 处理异常的类
            • InitException.java 初始化异常的类
            • NoRouteFoundException.java 未找到路由的异常类
          • facade 提供各种服务和回调的包
            • callback 回调相关的包
              • InterceptorCallback.java 拦截器回调的接口
              • NavigationCallback.java 导航回调的接口
              • NoRouteFoundException.java 未找到路由的异常接口
          • service 服务相关的包
            • AutowiredService.java 自动注入服务的接口
            • ClassLoaderService.java 类加载器服务的接口
            • DegradeService.java 降级服务的接口
            • InterceptorService.java 拦截器服务的接口
            • PathReplaceService.java 路径替换服务的接口
            • PretreatmentService.java 预处理服务的接口
            • SerializationService.java 序列化服务的接口
          • template 模板相关的包
            • IInterceptor.java 拦截器接口
            • IInterceptorGroup.java 拦截器分组接口
            • Ilogger.java 日志记录器接口
            • IPolicy.java 策略接口
            • IProvider.java 提供者接口
            • IProviderGroup.java 提供者分组接口
            • IRouteGroup.java 路由分组接口
            • IRouteRoot.java 路由根接口
            • Isyringe.java 注射器接口
            • Postcard.java 路由信息封装类
          • launcher 启动器: 包含一些用于启动ARouter框架的类和线程管理相关的类。
            • _Arouter.java ARouter框架的内部启动类,用于初始化ARouter。
            • Arouter.java ARouter框架的启动类,用于初始化ARouter。
          • thread (线程)包含了与线程管理相关的类。
          • utils 工具类。
            • ClassUtils.java 用于操作类的实用工具类。
            • Consts.java 包含一些常量值的类。
            • DefaultLogger.java 默认的日志记录器类。
            • MapUtils.java 用于操作地图数据的实用工具类。
            • PackageUtils.java 用于操作包信息的实用工具类。
            • TextUtils.java 用于操作文本数据的实用工具类。
    • arouter-compiler

      • src.main.java
        • com.alibaba.android.arouter
          • compiler 编译相关的包
            • entity 实体类相关的包
              • RouteDoc.java 路由文档实体类
            • processor 处理器相关的包
              • AutowiredProcessor.java 自动注入处理器
              • BaseProcessor.java 基础处理器
              • InterceptorProcessor.java 拦截器处理器
              • RouteProcessor.java 路由处理器
            • utils 工具类相关的包
              • Consts.java 常量类
              • Logger.java 日志记录器类
              • TypeUtils.java 类型工具类

从零开始写一个 ARouter - utils 工具类

  • ARouter
    • router-annotation 路由注解模块

      • src.main.java
        • com.alibaba.android.arouter
          • facade 提供注解和枚举的包
            • annotation 存放各种注解类的包
              • Autowired.java 自动注入的注解
              • Interceptor.java 拦截器的注解
              • Param.java(废弃) 参数注解: 被 Autowired 淘汰
              • Route.java 路由信息注解
            • enums 包含各种枚举类型的包
              • RouteType.java 路由类型的枚举
              • TypeKind.java 类型种类的枚举
            • model 包含模型类的包
              • RouteMeta.java 路由元信息的模型类
              • TypeWrapper.java 存储目标对象的泛型类型信息的类
    • arouter-api ARouter框架的API模块

      • src.main.java
        • com.alibaba.android.arouter
          • base 基础功能相关的包
            • UniqueKeyTreeMap.java 唯一键树形映射的类
          • core 核心功能相关的包
            • AutowiredLifecyleCallback.java(废弃) 自动注入生命周期回调的类
            • AutowiredServiceImpl.java 自动注入服务的实现类
            • InstrumentationHook.java(废弃) 仪表钩子的类
            • InterceptorServiceImpl.java 拦截器服务的实现类
            • LogisticsCenter.java 物流中心的类
            • Warehouse.java 仓库的类
          • exception 异常相关的包
            • HandlerException.java 处理异常的类
            • InitException.java 初始化异常的类
            • NoRouteFoundException.java 未找到路由的异常类
          • facade 提供各种服务和回调的包
            • callback 回调相关的包
              • InterceptorCallback.java 拦截器回调的接口
              • NavigationCallback.java 导航回调的接口
              • NoRouteFoundException.java 未找到路由的异常接口
          • service 服务相关的包
            • AutowiredService.java 自动注入服务的接口
            • ClassLoaderService.java 类加载器服务的接口
            • DegradeService.java 降级服务的接口
            • InterceptorService.java 拦截器服务的接口
            • PathReplaceService.java 路径替换服务的接口
            • PretreatmentService.java 预处理服务的接口
            • SerializationService.java 序列化服务的接口
          • template 模板相关的包
            • IInterceptor.java 拦截器接口
            • IInterceptorGroup.java 拦截器分组接口
            • Ilogger.java 日志记录器接口
            • IPolicy.java 策略接口
            • IProvider.java 提供者接口
            • IProviderGroup.java 提供者分组接口
            • IRouteGroup.java 路由分组接口
            • IRouteRoot.java 路由根接口
            • Isyringe.java 注射器接口
            • Postcard.java 路由信息封装类
          • launcher 启动器: 包含一些用于启动ARouter框架的类和线程管理相关的类。
            • _Arouter.java ARouter框架的内部启动类,用于初始化ARouter。
            • Arouter.java ARouter框架的启动类,用于初始化ARouter。
          • thread (线程)包含了与线程管理相关的类。
            • CancelableCountDownLatch.java 可取消的倒计时计数器,用于线程同步。
            • DefaultPoolExecutor.java 默认的线程池执行器,用于执行异步任务。
            • DefaultThreadFactory.java 默认线程工厂,用于创建线程。
          • utils 工具类。
    • arouter-compiler

      • src.main.java
        • com.alibaba.android.arouter
          • compiler 编译相关的包
            • entity 实体类相关的包
              • RouteDoc.java 路由文档实体类
            • processor 处理器相关的包
              • AutowiredProcessor.java 自动注入处理器
              • BaseProcessor.java 基础处理器
              • InterceptorProcessor.java 拦截器处理器
              • RouteProcessor.java 路由处理器
            • utils 工具类相关的包
              • Consts.java 常量类
              • Logger.java 日志记录器类
              • TypeUtils.java 类型工具类

从零开始写一个 ARouter - compiler 编译模块

  • ARouter

    • router-annotation 路由注解模块

      • src.main.java
        • com.alibaba.android.arouter
          • facade 提供注解和枚举的包
            • annotation 存放各种注解类的包
              • Autowired.java 自动注入的注解
              • Interceptor.java 拦截器的注解
              • Param.java(废弃) 参数注解: 被 Autowired 淘汰
              • Route.java 路由信息注解
            • enums 包含各种枚举类型的包
              • RouteType.java 路由类型的枚举
              • TypeKind.java 类型种类的枚举
            • model 包含模型类的包
              • RouteMeta.java 路由元信息的模型类
              • TypeWrapper.java 存储目标对象的泛型类型信息的类
    • arouter-api ARouter框架的API模块

      • src.main.java
        • com.alibaba.android.arouter
          • base 基础功能相关的包
            • UniqueKeyTreeMap.java 唯一键树形映射的类
          • core 核心功能相关的包
            • AutowiredLifecyleCallback.java(废弃) 自动注入生命周期回调的类
            • AutowiredServiceImpl.java 自动注入服务的实现类
            • InstrumentationHook.java(废弃) 仪表钩子的类
            • InterceptorServiceImpl.java 拦截器服务的实现类
            • LogisticsCenter.java 物流中心的类
            • Warehouse.java 仓库的类
          • exception 异常相关的包
            • HandlerException.java 处理异常的类
            • InitException.java 初始化异常的类
            • NoRouteFoundException.java 未找到路由的异常类
          • facade 提供各种服务和回调的包
            • callback 回调相关的包
              • InterceptorCallback.java 拦截器回调的接口
              • NavigationCallback.java 导航回调的接口
              • NoRouteFoundException.java 未找到路由的异常接口
          • service 服务相关的包
            • AutowiredService.java 自动注入服务的接口
            • ClassLoaderService.java 类加载器服务的接口
            • DegradeService.java 降级服务的接口
            • InterceptorService.java 拦截器服务的接口
            • PathReplaceService.java 路径替换服务的接口
            • PretreatmentService.java 预处理服务的接口
            • SerializationService.java 序列化服务的接口
          • template 模板相关的包
            • IInterceptor.java 拦截器接口
            • IInterceptorGroup.java 拦截器分组接口
            • Ilogger.java 日志记录器接口
            • IPolicy.java 策略接口
            • IProvider.java 提供者接口
            • IProviderGroup.java 提供者分组接口
            • IRouteGroup.java 路由分组接口
            • IRouteRoot.java 路由根接口
            • Isyringe.java 注射器接口
            • Postcard.java 路由信息封装类
          • launcher 启动器: 包含一些用于启动ARouter框架的类和线程管理相关的类。
            • _Arouter.java ARouter框架的内部启动类,用于初始化ARouter。
            • Arouter.java ARouter框架的启动类,用于初始化ARouter。
          • thread (线程)包含了与线程管理相关的类。
            • CancelableCountDownLatch.java 可取消的倒计时计数器,用于线程同步。
            • DefaultPoolExecutor.java 默认的线程池执行器,用于执行异步任务。
            • DefaultThreadFactory.java 默认线程工厂,用于创建线程。
          • utils 工具类。
            • ClassUtils.java 用于操作类的实用工具类。
            • Consts.java 包含一些常量值的类。
            • DefaultLogger.java 默认的日志记录器类。
            • MapUtils.java 用于操作地图数据的实用工具类。
            • PackageUtils.java 用于操作包信息的实用工具类。
            • TextUtils.java 用于操作文本数据的实用工具类。
  • arouter-compiler

Android MVI GPT4 快问快答

MVI 层级架构

  • App 用于依赖注入 —— 可以访问所有层
  • Data 负责内部和外部存储 —— 可以访问 Domain
  • Domain 负责仓库和用例 —— 不访问任何层
  • Presentation 负责UI逻辑 —— 可以访问 Domain

GPT 4 块问快答:究竟什么是 MVI

为什么 Data 和 Presentation 可以访问领域层,举例子怎么访问的

在一个典型的分层架构中,每一层都有其独特的职责,而且这种架构通常允许某些层访问其他特定的层。在你提到的结构中,AppDataDomainPresentation层各自担负不同的角色,其中DataPresentation层被允许访问Domain层,主要是因为Domain层包含了应用程序的核心业务逻辑和规则,是其他层依赖的基础。

Data 层访问 Domain 层

  • 职责Data层负责处理数据的存取,无论是来自内部存储(如SQLite数据库、内存缓存等)还是外部存储(如网络API、远程数据库等)。
  • 访问方式Data层访问Domain层通常是通过实现Domain层定义的仓库接口(Repository Interfaces)。这些仓库接口定义了应用程序需要的数据操作(如获取用户数据、保存订单等),而Data层实现这些接口,具体处理数据的存取逻辑。

示例
假设在Domain层有一个UserRepository接口,它定义了获取用户信息的方法。Data层将实现这个接口,通过网络API或本地数据库获取用户信息。

Presentation 层访问 Domain 层

  • 职责Presentation层负责处理所有与用户界面(UI)相关的逻辑,如显示数据、响应用户操作等。
  • 访问方式Presentation层通过使用Domain层中定义的用例(Use Cases)或服务(Services)来获取所需的数据或执行特定的业务逻辑。用例封装了特定的业务逻辑操作,为Presentation层提供了清晰、简洁的API。

示例
如果有一个显示用户个人信息的界面,Presentation层的一个ViewModel或Presenter可能会调用一个GetUserProfile用例(从Domain层获取),该用例封装了获取用户个人信息的业务逻辑。

总结

通过允许DataPresentation层访问Domain层,可以确保应用程序的核心业务逻辑集中管理,并且易于测试和维护。同时,这种结构还有助于解耦,使得数据源的更改或UI的修改不会直接影响到业务逻辑的实现,从而提高了应用程序的灵活性和可维护性。

给出 GetUserProfile 代码例子

为了提供一个GetUserProfile的示例,我们将遵循典型的分层架构中的DomainDataPresentation层的交互方式。这个例子将使用Kotlin语言,因为它是在Android开发中常用的语言,同时也适用于Kotlin多平台移动(KMM)项目。

Domain 层

Domain层,我们定义一个用例(Use Case)GetUserProfile,它是业务逻辑的一部分。这个用例会从仓库(Repository)中获取用户的个人信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Domain层 - 定义用例
class GetUserProfile(private val userRepository: UserRepository) {
suspend fun execute(userId: String): UserProfile {
return userRepository.getUserProfile(userId)
}
}

// Domain层 - 定义仓库接口
interface UserRepository {
suspend fun getUserProfile(userId: String): UserProfile
}

// 用户个人信息的数据模型
data class UserProfile(val userId: String, val name: String, val email: String)

Data 层

Data层,我们实现了Domain层定义的UserRepository接口,提供了获取用户个人信息的实际逻辑。

1
2
3
4
5
6
7
// Data层 - 仓库实现
class UserRepositoryImpl : UserRepository {
override suspend fun getUserProfile(userId: String): UserProfile {
// 假设这里是从网络或本地数据库获取数据
return UserProfile(userId, "John Doe", "johndoe@example.com")
}
}

Presentation 层

Presentation层,我们使用GetUserProfile用例来获取用户个人信息,并展示到UI上。这里以一个简单的ViewModel为例。

1
2
3
4
5
6
7
8
9
10
11
12
// Presentation层 - ViewModel
class UserProfileViewModel(private val getUserProfile: GetUserProfile) : ViewModel() {
// LiveData用于观察数据变化,更新UI
private val _userProfile = MutableLiveData<UserProfile>()
val userProfile: LiveData<UserProfile> = _userProfile

fun loadUserProfile(userId: String) {
viewModelScope.launch {
_userProfile.value = getUserProfile.execute(userId)
}
}
}

小结

这个例子演示了在一个分层架构中,如何通过Domain层的用例来协调Data层和Presentation层的交互。Domain层定义了业务逻辑和接口,Data层实现了数据获取的逻辑,而Presentation层负责调用这些逻辑并更新UI。这种分层的方式有助于保持代码的清晰和可维护性。

描述一下,数据在几个层之间的传递,从起始到结束

数据在分层架构中的传递通常遵循一个定义良好的流程,确保应用的各个组成部分职责明确、解耦合,同时促进模块间的协作。以下是一个典型的从起始到结束的数据传递流程,以一个简化的用户个人信息获取功能为例,该功能遵循PresentationDomainData层的架构模式:

1. 用户界面触发

  • 起始点:用户在应用的界面上执行一个动作(比如点击一个按钮),请求查看个人信息。
  • 处理:这个动作被Presentation层的一个组件(例如,一个ViewModel或Activity)捕获。

2. Presentation 层处理

  • Presentation层的组件调用Domain层定义的用例(Use Case),比如GetUserProfile,来获取用户的个人信息。这个调用通常是异步的,以避免阻塞UI线程。
  • ViewModel通过用例接口,向Domain层表达获取数据的需求。

3. Domain 层中转

  • Domain层的GetUserProfile用例接收到请求,并将其转发给相应的UserRepository接口。此时,GetUserProfile仅知道需要调用仓库接口来获取数据,但不关心数据的具体来源。
  • Domain层起到了定义业务规则和操作流程的角色,但本身不处理数据的存取。

4. Data 层响应

  • Data层中,具体实现了UserRepository接口的类(如UserRepositoryImpl)负责处理数据的实际获取操作。这可能涉及访问网络API、查询本地数据库或读取缓存等操作。
  • 一旦数据被成功获取,它会被封装成一个模型(如UserProfile),并返回给调用者——在这个例子中是Domain层的GetUserProfile用例。

5. 数据回流到Presentation层

  • GetUserProfile用例收到Data层提供的用户信息后,将这些信息返回给最初请求它的Presentation层的组件。
  • Presentation层的ViewModel接收到这些数据后,更新UI绑定的数据模型,通常是通过LiveData或类似机制。

6. UI更新显示数据

  • 最后,随着ViewModel中LiveData(或其他观察者模式实现)的更新,UI监听这些变化,并据此刷新显示,向用户展示最新获取的用户个人信息。

总结

这个过程展示了数据从用户触发获取请求,通过应用的各层传递,并最终显示回用户界面的完整流程。每一层都有其特定的职责:Presentation层管理UI逻辑和用户交互,Domain层定义业务规则和数据获取的抽象接口,而Data层负责具体的数据存取逻辑。这种分层架构不仅有助于保持代码的组织性和可维护性,还促进了各模块间的清晰分离和互操作性。

解释 Domain 层如何定义业务规则

Domain层在分层架构中扮演着核心角色,它定义了应用程序的业务规则和逻辑。这一层是独立于用户界面(UI)和数据存储机制的,意味着它不应该依赖于任何特定的框架或库,以便于业务逻辑能够在不同的环境中重用和测试。Domain层的设计主要关注于应用程序的“什么”和“为什么”,而不是“怎么做”。

业务规则的定义

业务规则是指那些定义企业如何运作、决策应如何做出、业务流程应如何管理的规则。在软件开发中,这些规则转化为可以执行的代码,用于指导应用程序的行为。业务规则包括但不限于:

  • 实体(Entities):代表业务概念的对象,如用户、订单等,以及这些对象的属性和行为。
  • 用例(Use Cases)或服务(Services):封装了应用程序的核心功能,每个用例代表一个或多个业务操作,如“获取用户信息”、“创建订单”等。
  • 值对象(Value Objects):描述了业务中的值和它们的属性,但没有唯一标识符(与实体不同)。
  • 仓库接口(Repository Interfaces):定义了如何访问外部数据源(如数据库或远程服务)的抽象接口,但不实现具体的数据访问逻辑。

Domain层的职责

  • 封装业务逻辑Domain层包含应用程序的业务逻辑,确保业务操作的正确性、有效性和连贯性。
  • 定义业务模型:通过实体和值对象定义业务领域内的数据模型和它们之间的关系。
  • 业务决策的实现:实现业务决策逻辑,如验证规则、计算和数据转换等。
  • 保持独立性Domain层的设计应确保它不依赖于特定的UI实现或数据存储策略,使得业务逻辑可以在不同的应用场景下重用。

举例

假设有一个在线商店应用,其Domain层可能包含如下定义:

  • 实体Product(产品)、Customer(客户)、Order(订单)。
  • 用例PlaceOrder(下订单)、CancelOrder(取消订单)、UpdateCustomerProfile(更新客户资料)。
  • 值对象Address(地址)、Money(货币)。
  • 仓库接口OrderRepository(提供对订单数据的访问)、CustomerRepository(提供对客户数据的访问)。

在这个例子中,Domain层定义了在线商店运作的基本规则和逻辑,如何处理订单、如何管理客户信息,以及如何执行业务决策,所有这些都与特定的UI实现或数据持久化机制无关。

总结,Domain层的设计是为了确保应用程序的核心业务逻辑得到清晰定义、良好封装,从而易于维护和测试,同时提高了代码的可重用性。

解释 Domain 层定义了基本规则和逻辑,与特定的 UI 实现或数据持久化机制无关

Domain层在软件架构中的作用是封装和实现应用程序的核心业务逻辑,独立于用户界面(UI)和数据持久化(数据库等)的具体实现。这种设计方法使得业务逻辑更加模块化、清晰,并且易于测试和维护。在在线商店的例子中,Domain层定义的业务规则和逻辑涉及以下几个关键方面:

如何处理订单

Domain层中,处理订单的逻辑包括订单的创建、验证、支付、取消等步骤。这些操作反映了订单生命周期中的各个阶段,每个阶段都基于业务规则来执行。例如,订单支付前需要验证订单详情和客户支付信息的有效性;订单一旦支付成功,就不能随意取消,除非满足特定的退款条件。

如何管理客户信息

客户信息管理包括客户注册、信息更新、历史订单查询等功能。Domain层通过定义客户实体(Customer)和相关的业务操作(如UpdateCustomerProfile)来实现这一点。这些操作确保了客户信息的一致性和安全性,例如,客户密码需要加密存储,客户信息更新需要验证客户的身份。

如何执行业务决策

业务决策涉及到基于特定条件执行的逻辑选择,比如根据库存量决定是否接受订单、根据客户购买历史提供个性化推荐等。这些决策通常通过Domain层中的业务规则来实现,业务规则可能包括各种验证逻辑、计算公式和决策树。

与UI和数据持久化机制的独立性

Domain层的设计目的是保持对UI层和数据持久化层的独立性。这意味着,不管UI如何变化(无论是移动应用、Web页面还是桌面应用),Domain层的业务逻辑不需要做出改变。同样,数据存储的细节(是使用SQL数据库、NoSQL数据库还是云服务)对Domain层也是透明的。这一层只通过抽象的接口(如Repository接口)与数据交互,具体的实现细节由Data层负责。

通过将业务逻辑集中在Domain层,应用程序的核心功能和规则得到了清晰的定义和封装,这不仅有助于减少不同层之间的耦合,还使得业务逻辑更容易理解、测试和维护。此外,这种架构设计还增加了应用程序的灵活性,使得未来对应用程序的扩展和修改变得更加容易。

抽出 domain 层的意义是什么

抽出Domain层在软件架构中的意义主要体现在以下几个方面:

1. 业务逻辑集中化

将业务逻辑集中在Domain层,有助于明确应用程序的核心功能和规则,使得这些逻辑独立于其他层(如表示层和数据持久化层)的实现。这种集中化有助于保持业务规则的一致性,确保业务操作遵循同一套逻辑,无论是通过何种途径执行。

2. 提高可维护性

通过将业务逻辑与数据访问代码(Data层)和用户界面代码(Presentation层)分离,开发人员可以更容易地理解、修改和扩展业务规则。当业务需求发生变化时,通常只需要修改Domain层的代码,而无需触及到其他层的实现,这大大降低了维护成本和复杂性。

3. 增强可测试性

独立的Domain层使得业务逻辑的单元测试变得更加直接和简单。因为业务逻辑与外部的界面显示和数据存取逻辑解耦,测试可以集中在业务规则的正确性上,而不必担心UI或数据库的具体实现。这有助于提高测试覆盖率和发现潜在的业务逻辑错误。

4. 促进技术栈灵活性

由于Domain层独立于具体的技术实现,因此可以在不影响业务逻辑的前提下更换表示层框架或数据持久化技术。例如,可以从一个数据库系统迁移到另一个,或者在不同的前端技术(如Web、移动应用)之间共享相同的业务逻辑。

5. 便于多团队协作

在大型项目中,不同的团队可以负责不同的应用层,例如一个团队专注于Domain层的业务逻辑开发,而另一个团队专注于用户界面的实现。这种分工有助于提高开发效率,因为团队可以并行工作,而且减少了彼此工作的直接依赖。

6. 支持多种应用场景

Domain层的业务逻辑可以被不同的应用场景重用,比如同一个业务逻辑既能服务于Web应用,也能服务于移动应用或桌面应用。这种重用性可以显著降低开发和维护新平台应用的成本和时间。

总之,抽出Domain层是实现软件架构清晰分层、提高代码质量和维护性的重要策略,它有助于构建更加健壯、灵活和可持续发展的软件系统。

这样做为什么可以只需要修改Domain层的代码,而无需触及到其他层的实现

当业务需求发生变化时,通常只需要修改Domain层的代码而无需触及到其他层的实现,这个原理主要是基于软件设计中的分层架构和关注点分离的原则。让我们来详细解释一下为什么这样做是可行的:

分层架构

在一个典型的分层架构中,软件被划分为几个层次,每个层次负责处理不同的任务。这些层次通常包括:

  • 表示层(Presentation Layer):负责用户界面和用户交云的处理。
  • 领域层(Domain Layer):包含业务逻辑和业务规则。
  • 数据访问层(Data Access Layer):负责数据持久化和数据检索。

关注点分离

分层架构实现了关注点分离(Separation of Concerns, SoC)的原则,即不同的层次关注不同的方面。通过这种方式,每一层都可以独立于其他层变化,只要它们之间的接口保持不变。

对于业务逻辑的变化

  • 业务逻辑变化通常发生在Domain层:因为Domain层封装了应用程序的核心业务规则和逻辑。当业务需求变化时,这些变化几乎总是直接影响到业务规则和逻辑,而这些都是Domain层的职责范畴。
  • 表示层和数据访问层的独立性:由于表示层主要负责界面显示和用户交互,数据访问层主要负责数据的持久化和检索,这两层的工作与业务逻辑的具体内容相对独立。它们通过定义良好的接口与Domain层交互,只要这些接口不变,业务逻辑的变更就不会直接影响到它们。
  • 接口作为契约Domain层通过接口与其他层进行交互。当业务需求变化导致Domain层的逻辑变化时,只要接口保持不变,表示层和数据访问层就无需变化。即使接口需要变化,现代编程语言和框架提供的抽象和封装机制也能最小化这种变化的影响。

实例

假设一个电子商务应用,其中有一个计算订单总价的业务逻辑。如果业务规定更改了订单的折扣计算方式,这个变化直接关系到订单总价的计算规则,这是Domain层的一部分。只需修改Domain层中计算总价的代码即可。只要Domain层对外提供的计算总价的接口不变,表示层(可能显示总价的界面)和数据访问层(可能存储订单详情的数据库操作)就不需要任何改动。

结论

因此,将业务逻辑封装在Domain层,确保了应用程序在面对业务需求变化时,可以局部化修改影响,减少了需要修改的代码量,从而降低了维护成本和复杂性。这种设计使得软件系统更加灵活和可维护,更容易适应和响应业务变化。

给出代码例子

让我们通过一个简化的代码示例来说明当业务需求发生变化时,通常只需要修改Domain层的代码而无需触及到其他层的实现。考虑一个电子商务应用,其中包含一个功能:计算订单总价。我们将展示Domain层、Data层和Presentation层的简化代码,并展示当业务规则(例如,订单折扣规则)变化时,如何只修改Domain层代码来应对这一变化。

初始实现

Domain层

1
2
3
4
5
6
7
8
9
10
11
12
13
// Domain层 - 定义业务逻辑接口
interface OrderService {
fun calculateTotalPrice(items: List<OrderItem>): Double
}

// Domain层 - 实现
class OrderServiceImpl : OrderService {
override fun calculateTotalPrice(items: List<OrderItem>): Double {
return items.sumOf { it.price * it.quantity }
}
}

data class OrderItem(val price: Double, val quantity: Int)

Data层

假设数据层处理订单数据的存储和检索,但与计算总价无关,因此在此示例中略过。

Presentation层

1
2
3
4
5
6
7
// Presentation层 - 使用Domain层的服务展示总价
class OrderViewModel(private val orderService: OrderService) {
fun displayTotalPrice(items: List<OrderItem>) {
val totalPrice = orderService.calculateTotalPrice(items)
println("Total Price: $totalPrice")
}
}

业务需求变化

假设业务需求变化,现在需要对订单总价应用10%的折扣。

修改Domain层

我们只需要在Domain层中修改OrderServiceImpl的实现来反映这一变化:

1
2
3
4
5
6
7
8
// Domain层 - 修改后的实现
class OrderServiceImpl : OrderService {
override fun calculateTotalPrice(items: List<OrderItem>): Double {
val originalTotal = items.sumOf { it.price * it.quantity }
val discount = originalTotal * 0.1 // 应用10%的折扣
return originalTotal - discount
}
}

在这个修改中,我们没有触及Presentation层或Data层的代码。Presentation层仍然以相同的方式调用Domain层的calculateTotalPrice方法,而Data层(假设它负责数据的持久化)也不需要变化,因为它与订单价格的计算逻辑无关。

结论

通过这个简化的例子,我们可以看到,当业务规则变化(如订单折扣规则)时,只需修改Domain层中的业务逻辑实现。Presentation层和Data层不需要修改,因为它们与Domain层通过抽象的接口进行交互,这个接口(在本例中是OrderService.calculateTotalPrice)保持不变。这种设计使得应用程序能够灵活应对业务需求的变化,同时最小化了修改的影响范围,提高了代码的可维护性。

Android 音视频屏幕旋转方案

正文

在Android平台上,实现音视频播放过程中的屏幕旋转涉及到以下几个方面:界面布局的调整、Activity的生命周期管理、保存和恢复播放状态、以及处理屏幕旋转时可能引发的音视频播放问题。以下是一个基本的音视频屏幕旋转方案的步骤:

布局调整

res文件夹中创建不同方向的布局文件,例如layout/activity_main.xmllayout-land/activity_main.xml分别表示竖屏和横屏时的布局。在这些布局文件中,你可以调整控件的摆放位置以适应不同方向的屏幕。

Activity的生命周期管理

当屏幕发生旋转时,Activity会经历重新创建的过程。确保你的Activity正确处理生命周期事件,特别是onCreateonSaveInstanceStateonRestoreInstanceState等方法。

保存和恢复播放状态

onSaveInstanceState方法中保存当前的播放状态,例如播放位置、播放状态(播放、暂停等),以及其他相关信息。然后在onCreateonRestoreInstanceState方法中恢复这些状态。

处理屏幕旋转可能引发的问题

屏幕旋转可能会导致播放器重新创建或重置,从而影响播放状态。你需要在Activity重新创建时恢复播放状态,并确保播放器不会中断或重置播放进度。

使用ViewModel来保存播放状态(可选)

使用ViewModel可以更方便地在屏幕旋转时保持数据。ViewModel会在Activity重新创建时保持其状态,因此你可以将播放状态等数据存储在ViewModel中,以确保不会丢失。

锁定屏幕方向(可选)

如果你希望在播放音视频时固定屏幕方向,可以在AndroidManifest.xml中的相应Activity中使用android:screenOrientation属性来指定所需的屏幕方向(如portraitlandscape)。

总结

总之,处理音视频播放过程中的屏幕旋转需要综合考虑界面布局、生命周期管理、数据保存和恢复等方面的问题。合理使用布局文件、Activity生命周期方法、ViewModel等工具,可以帮助你实现稳定且用户友好的音视频播放体验。

Android 音视频屏幕旋转方案

Q:我应该去哪里获取不同版本的存储策略

A:要获取不同版本的 Android 存储策略(Storage Policies),您可以访问以下资源,这些资源提供了详细的版本特定存储策略和变更的信息:

官方 Android 文档

Android 官方文档是了解不同版本存储策略最可靠的来源。以下是一些重要的链接:

  1. **Android Developers Storage Documentation**:
    这是 Android 官方的存储文档,涵盖了各种存储选项和最佳实践。

  2. **Android Platform Releases**:
    这个页面提供了每个 Android 版本的详细变更,包括存储策略的变更。

  3. **Scoped Storage**:
    针对 Android 10(API Level 29)及更高版本的分区存储(Scoped Storage),是存储策略的重要变更点。

官方博客和公告

Android 官方博客和公告也是了解存储策略变更的好资源:

  1. **Android Developers Blog**:
    在这个博客上,Google 发布了关于新功能和存储策略变更的详细信息和指导。

GitHub 和社区资源

开源社区和开发者论坛也提供了大量关于存储策略的信息:

  1. **Google Samples on GitHub**:
    Google 提供的示例项目,可以查看不同版本的存储策略实现。

  2. **Stack Overflow**:
    搜索关于 Android 存储策略的问题和答案,可以获取社区的解决方案和建议。

具体版本的存储策略变更

要了解具体版本的存储策略变更,可以参考以下步骤:

  1. 访问 Android 开发者文档中的版本变更日志
    每个 Android 版本的变更日志会详细列出该版本的新特性和变更,包括存储策略。

  2. 查找特定版本的存储策略文档
    在 Android Developers 网站上,您可以找到特定版本的 API 参考和变更日志。

直接搜索具体问题

如果您有特定的存储策略问题,可以直接在 Google 搜索:

  • 例如:Android 10 Scoped Storage
  • 例如:Android 11 Storage Policy Changes

通过这些资源,您应该能够找到详细的 Android 存储策略信息,并理解每个版本的特定变更。

Android 6 以下的文件存储和读取策略

在Android 6.0(API Level 23)及以下版本中,文件存储和读取的策略主要涉及以下几个方面:

1. 存储位置

Android系统提供了两种主要的存储位置:内部存储和外部存储。

内部存储

  • 内部存储是应用的私有存储区域,只有该应用可以访问。
  • 存储在内部存储中的文件默认在应用卸载时会被删除。
  • 使用Context对象的方法可以在内部存储中读写文件,例如:
    • openFileOutput(String name, int mode):写文件
    • openFileInput(String name):读文件

外部存储

  • 外部存储可以是设备的外部存储卡或者内置存储分区。
  • 分为公共外部存储和应用私有外部存储。
  • 公共外部存储可以被所有应用访问,例如图片、音频文件等。
  • 应用私有外部存储只能被创建该文件的应用访问,在应用卸载时会被删除。
  • 使用Environment类和Context对象的方法可以在外部存储中读写文件,例如:
    • getExternalFilesDir(String type):获取应用私有外部存储目录
    • Environment.getExternalStoragePublicDirectory(String type):获取公共外部存储目录

2. 权限管理

在Android 6.0及以下版本中,应用需要在AndroidManifest.xml中声明对外部存储的读写权限:

  • 读权限:<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
  • 写权限:<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

这些权限在应用安装时由用户批准。

3. 文件操作代码示例

内部存储

写文件:

1
2
3
4
5
String filename = "myfile.txt";
String fileContents = "Hello, world!";
FileOutputStream fos = openFileOutput(filename, Context.MODE_PRIVATE);
fos.write(fileContents.getBytes());
fos.close();

读文件:

1
2
3
4
5
6
7
8
9
10
FileInputStream fis = openFileInput(filename);
InputStreamReader isr = new InputStreamReader(fis);
BufferedReader bufferedReader = new BufferedReader(isr);
StringBuilder sb = new StringBuilder();
String line;
while ((line = bufferedReader.readLine()) != null) {
sb.append(line);
}
String fileContents = sb.toString();
fis.close();

外部存储

检查存储状态:

1
2
3
4
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state)) {
// 可以读写外部存储
}

写文件:

1
2
3
4
File file = new File(getExternalFilesDir(null), "myfile.txt");
FileOutputStream fos = new FileOutputStream(file);
fos.write(fileContents.getBytes());
fos.close();

读文件:

1
2
3
4
5
6
7
8
9
10
11
File file = new File(getExternalFilesDir(null), "myfile.txt");
FileInputStream fis = new FileInputStream(file);
InputStreamReader isr = new InputStreamReader(fis);
BufferedReader bufferedReader = new BufferedReader(isr);
StringBuilder sb = new StringBuilder();
String line;
while ((line = bufferedReader.readLine()) != null) {
sb.append(line);
}
String fileContents = sb.toString();
fis.close();

这些代码示例展示了在Android 6.0及以下版本中如何进行基本的文件读写操作。随着Android版本的升级,文件存储和权限管理策略也发生了显著变化,因此在开发过程中需要根据目标API版本进行适配。

Android 6 以上文件存储和读取策略

在Android 6.0(API Level 23)及以上版本中,文件存储和读取策略发生了一些变化,特别是在权限管理和存储位置的使用方面。以下是各版本的策略变化和相应的文件存储读写方法。

1. Android 6.0 (API Level 23)

动态权限

  • 从Android 6.0开始,权限管理采用动态权限模式。用户可以在运行时授予或拒绝权限。
  • 应用在使用外部存储时,需要在AndroidManifest.xml中声明权限,并在运行时请求权限。

权限请求代码示例:

1
2
3
4
if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_CODE);
}

2. Android 7.0 (API Level 24)

文件URI策略

  • 从Android 7.0开始,File URI不再被允许在应用之间共享。必须使用FileProvider来共享文件URI。

FileProvider配置示例:
AndroidManifest.xml中:

1
2
3
4
5
6
7
8
9
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>

res/xml/file_paths.xml中:

1
2
3
<paths>
<external-path name="external_files" path="." />
</paths>

3. Android 8.0 (API Level 26)

安全性增强

  • 在后台执行文件操作受到限制,必须在前台任务或通过服务进行文件操作。

4. Android 9.0 (API Level 28)

共享存储访问

  • 在Android 9.0中引入了新的API,用于访问共享存储,推荐使用ACTION_OPEN_DOCUMENTACTION_CREATE_DOCUMENT来访问和创建文件。

使用示例:

1
2
3
4
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("*/*");
startActivityForResult(intent, REQUEST_CODE_OPEN_DOCUMENT);

5. Android 10 (API Level 29)

分区存储 (Scoped Storage)

  • 引入了分区存储,应用只能访问其私有的外部存储空间,除非使用特定的API访问公共存储。
  • 可以通过请求Legacy存储模式来继续使用传统的存储访问方式(直到Android 11)。

分区存储代码示例:

1
2
3
4
File file = new File(getExternalFilesDir(null), "myfile.txt");
FileOutputStream fos = new FileOutputStream(file);
fos.write(fileContents.getBytes());
fos.close();

6. Android 11 (API Level 30)

强制分区存储

  • 强制启用分区存储,应用无法再使用requestLegacyExternalStorage标志。
  • 提供了新的访问存储的API,如MediaStore

MediaStore使用示例:

1
2
3
4
5
6
7
8
9
10
ContentValues values = new ContentValues();
values.put(MediaStore.MediaColumns.DISPLAY_NAME, "myfile.txt");
values.put(MediaStore.MediaColumns.MIME_TYPE, "text/plain");
values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOCUMENTS);
Uri uri = getContentResolver().insert(MediaStore.Files.getContentUri("external"), values);
if (uri != null) {
OutputStream outputStream = getContentResolver().openOutputStream(uri);
outputStream.write(fileContents.getBytes());
outputStream.close();
}

7. Android 12 (API Level 31) 及更高版本

进一步限制外部存储访问

  • 在Android 12中,对特定类型的文件访问进行了更多的限制,需要使用特定的API来访问不同类型的文件,如音频、视频和图片等。

总结:

  • Android 6.0及以上版本在权限管理上采用动态权限,需要在运行时请求权限。
  • Android 7.0开始引入FileProvider来共享文件URI。
  • Android 10引入分区存储,Android 11强制启用分区存储,进一步限制了应用对外部存储的访问。
  • 使用MediaStore等新的API来进行文件的读写操作,以适应最新的存储策略。

了解并适应这些变化,可以确保应用在不同Android版本上的文件存储和读取操作安全且有效。

Android aar 文件到底是什么

作者: 朕小猫与GPT4

问题起源:怎么打包发布一个含有 C++ 文件的 Library

起因是 KOOM 2.2.1 Release 存在后台分析进程在部分机型失效,想自己发布一个适合自己项目的版本。

.so文件可以被打包到最终的AAR文件中吗

是的,.so文件可以并且通常被打包到最终的AAR(Android Archive)文件中。AAR文件是Android开发中用于分发Android库的标准格式,它包含了库所需的所有资源、编译后的代码以及其他文件,比如.so文件(本地库文件)。

打包.so文件到AAR中的步骤

  1. 放置.so文件: 将你的.so文件放置在项目的src/main/jniLibs/目录下。按照不同的CPU架构,你可能需要创建相应的子目录,例如armeabi-v7aarm64-v8ax86等,然后将相应架构的.so文件放置在这些目录下。

  2. 配置Gradle构建脚本: 确保你的build.gradle文件正确配置,这样Gradle在构建过程中就会自动将jniLibs目录下的.so文件包括到AAR包中。大多数情况下,这个过程是自动完成的,无需额外配置。

示例目录结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
YourLibraryProject/
├── src/
│ └── main/
│ ├── java/
│ │ └── ... (your Java or Kotlin source files)
│ ├── res/
│ │ └── ... (your resources)
│ └── jniLibs/
│ ├── armeabi-v7a/
│ │ └── libyourlibrary.so
│ ├── arm64-v8a/
│ │ └── libyourlibrary.so
│ ├── x86/
│ │ └── libyourlibrary.so
│ └── x86_64/
│ └── libyourlibrary.so

构建AAR包

当你构建你的库时(例如,通过运行./gradlew assembleRelease),Gradle会自动将jniLibs目录下的所有.so文件包括到生成的AAR文件中。这样,当其他开发者将你的AAR文件作为依赖项添加到他们的项目中时,这些.so文件就会被自动解压到正确的位置,供他们的应用使用。

确保在发布你的库之前测试它,以确保.so文件被正确包含且可以按预期工作。这通常涉及到在包含不同CPU架构设备的模拟器或真实设备上运行和测试你的库。

.aar 里可以有哪些文件

一个AAR(Android Archive)文件是Android库项目的发布包格式,用于在开发者之间共享Android库。AAR文件包含了库所需要的所有内容,使其可以被其他的Android应用项目引用。以下是AAR文件通常包含的文件和目录:

必需的内容

  • classes.jar:包含库的所有编译后的Java类(.class文件)。
  • res/:包含库所使用的所有资源文件,如布局(layouts)、字符串(strings)、图片(drawables)等。
  • AndroidManifest.xml:库的Android清单文件,描述了库的包名、所需权限、活动(activities)、服务(services)等。

可选的内容

  • R.txt:包含库中所有资源的ID名称和类型的列表,这是为了在库被引用时帮助生成最终应用的R类。
  • aidl/:包含库所使用的所有AIDL(Android Interface Definition Language)文件,用于定义跨进程通信接口。
  • assets/:包含可以被库访问的原始文件,如文本、音频或视频文件。
  • libs/*.jar:包含库依赖的任何Java库的JAR文件。
  • jni/jniLibs/:包含库依赖的本地库(如.so文件),这些文件根据不同的CPU架构可能存放在不同的子目录下,如armeabi-v7a/arm64-v8a/等。
  • proguard.txt:包含用于代码混淆的ProGuard规则。
  • lint.jar:包含自定义lint检查规则的JAR,这些规则可以在使用库的项目中被lint程序使用。
  • public.txt:列出了库公开的资源ID,这些资源ID在库的使用者中是可见的。
  • annotations.zip:包含用于工具支持的注解,比如nullability注解等。

如何查看AAR文件内容

你可以通过解压AAR文件(它本质上是一个ZIP文件)来查看里面的内容。使用任何标准的压缩文件管理器,如WinRAR、7-Zip等,都可以打开AAR文件并浏览其中的内容。

使用AAR文件

当你将AAR文件作为依赖项添加到Android项目中时,项目构建系统(如Gradle)会自动处理并引入这些内容,包括解析资源、合并清单文件等,从而使库中的功能可用于应用程序。
aar文件分析.png

Android aar 文件到底是什么

作者: 朕小猫与GPT4

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
task downloadApk {
doLast {
def remoteApkPath = "/home/jason/AndroidStudioProjects/ncmusic/app/build/outputs/apk/debug/app-debug.apk"
def localApkDir = System.getProperty("user.home") + "/Downloads/"
def localApkPath = localApkDir + "app-debug.apk"

// 创建本地目录
new File(localApkDir).mkdirs()

// 使用 scp 下载远程 APK 文件到本地
def command = "sshpass -p 'password' scp jason@192.168.1.100:${remoteApkPath} ${localApkPath}"
println "Executing command: ${command}"

exec {
commandLine 'bash', '-c', command
}
}
}

task installAndStart {
dependsOn downloadApk
doLast {
def localApkPath = System.getProperty("user.home") + "/Downloads/app-debug.apk"

// 确保 adb 可用
def adbPath = '/Users/jason/Library/Android/sdk/platform-tools/adb'
if (!new File(adbPath).exists()) {
throw new GradleException("adb command not found at ${adbPath}! Please ensure Android SDK is properly installed.")
}

// 安装 APK
def installCommand = "${adbPath} install -r ${localApkPath}"

try {
exec {
commandLine 'bash', '-c', installCommand
}
} catch (Exception e) {
throw new GradleException("Failed to install APK: ${e.message}")
}

// 提取包名
def manifestFile = file("${rootProject.projectDir}/app/src/main/AndroidManifest.xml")
def packageName = ""
if (manifestFile.exists()) {
manifestFile.eachLine { line ->
if (line.contains("package=")) {
packageName = line.split('package=')[1].split('"')[1]
}
}
if (packageName == "") {
throw new GradleException("Failed to extract package name from AndroidManifest.xml")
}
} else {
throw new GradleException("AndroidManifest.xml file not found!")
}

// 启动应用
def launchCommand = "${adbPath} shell monkey -p ${packageName} -c android.intent.category.LAUNCHER 1"
println "Executing command: ${launchCommand}"

try {
exec {
commandLine 'bash', '-c', launchCommand
}
} catch (Exception e) {
throw new GradleException("Failed to launch application: ${e.message}")
}
}
}

task downloadBuild {
doLast {
subprojects.each { subproject ->
def remoteRootPath = "/home/jason/AndroidStudioProjects/"
def remoteDirPath = remoteRootPath + "${rootProject.name}/${subproject.name}/build"
def localDirPath = "${subproject.projectDir}"

// 创建本地目录
new File(localDirPath).mkdirs()

// 使用 rsync 同步远程文件到本地
def command = "sshpass -p 'password' rsync -avz --delete jason@192.168.1.100:${remoteDirPath} ${localDirPath}"
println "Executing command: ${command}"

exec {
commandLine 'bash', '-c', command
}
}
}
}

task remoteBuild {
doLast {
// 远程构建命令
def command = "sshpass -p 'password' ssh jason@192.168.1.100 'cd /home/jason/AndroidStudioProjects/ncmusic/ && ./gradlew assembleDebug'"
exec {
commandLine 'bash', '-c', command
}
}
}

// 定义一个任务来执行远程构建、下载 APK、安装并启动应用,然后同步构建产物
task buildDownloadInstallAndStart {
dependsOn remoteBuild
finalizedBy installAndStart, downloadBuild
}