【Android架构底层逻辑拆解】Google官方项目NowInAndroid研究(3)数据层的设计和实现之network

前文分析了data层用database的实现,除了数据库,常用的数据源还有网络,因此这里继续分析network模块。

:core:network模块总览

整个network模块的内容相对较少,各个package的功能如下:

package 功能
demo 用assets中的json文件模拟服务器行为的实现,暂不关注
di 依赖注入network组件
model 数据在网络层的呈现
retrofit 网络层数据仓库
NiaNetworkDataSource 网络层向上暴露的接口,封装具体实现

本次分析真实业务逻辑,先跳过单元测试、模拟数据的过程,待完成主要功能拆解分析后,再补齐这些部分。

model包------网络层数据表现形式

仍然离不开TopicNewsResource这两个基本数据类,在model包中提供了网络层的对应实现。

kotlin 复制代码
@Serializable // 默认使用JSON进行序列化,可见NetworkModule类
data class NetworkTopic(
    val id: String,
    val name: String = "",
    val shortDescription: String = "",
    val longDescription: String = "",
    val url: String = "",
    val imageUrl: String = "",
    val followed: Boolean = false,
)

fun NetworkTopic.asExternalModel(): Topic = // 转换为基本数据类型
    Topic(
        id = id,
        name = name,
        shortDescription = shortDescription,
        longDescription = longDescription,
        url = url,
        imageUrl = imageUrl,
    )

为进行版本管理,提供了NetworkChangeList数据类,其中封装了数据id版本号删除标记

kotlin 复制代码
@Serializable
data class NetworkChangeList(
    /**
     * The id of the model that was changed
     */
    val id: String,
    /**
     * Unique consecutive, monotonically increasing version number in the collection describing
     * the relative point of change between models in the collection
     */
    val changeListVersion: Int, // model在服务器的递增版本号
    /**
     * Summarizes the update to the model; whether it was deleted or updated.
     * Updates include creations.
     */
    val isDelete: Boolean,
)

di包------网络模块单例

di包只含有NetworkModule一个类,它提供了网络模块的OkHttp单例实现。

kotlin 复制代码
@Module
@InstallIn(SingletonComponent::class) // 很好理解,作用域应用全生命周期的单例对象
internal object NetworkModule {

    @Provides // 创建单例,供全局使用
    @Singleton
    fun providesNetworkJson(): Json = Json { // 提供Json单例,在使用处调用@Inject进行注入
        ignoreUnknownKeys = true
    }

    @Provides
    @Singleton
    fun okHttpCallFactory(): Call.Factory = trace("NiaOkHttpClient") { // Systrace埋点
        OkHttpClient.Builder()
            .addInterceptor(
                HttpLoggingInterceptor() // 打印日志拦截器
                    .apply {
                        if (BuildConfig.DEBUG) {
                            setLevel(HttpLoggingInterceptor.Level.BODY)
                        }
                    },
            )
            .build()
    }

    @Provides
    @Singleton
    fun imageLoader( // 全局使用的单例图片加载器,同样用trace进行埋点
        // We specifically request dagger.Lazy here, so that it's not instantiated from Dagger.
        okHttpCallFactory: dagger.Lazy<Call.Factory>,
        @ApplicationContext application: Context,
    ): ImageLoader = trace("NiaImageLoader") {
        ImageLoader.Builder(application)
            .callFactory { okHttpCallFactory.get() }
            .components { add(SvgDecoder.Factory()) }
            // Assume most content images are versioned urls
            // but some problematic images are fetching each time
            .respectCacheHeaders(false)
            .apply {
                if (BuildConfig.DEBUG) {
                    logger(DebugLogger())
                }
            }
            .build()
    }
}

知识点1:本项目中使用的单例模式

到现在,已经开始对Hilt的单例注解慢慢熟悉起来了,@Module表示这是一个Hilt模块,其内部提供了全局 使用的各种单例、@InstallIn(SingletonComonent::class) 表示模块的作用域为应用级单例 ,与应用生命周期一致。

在模块内部的方法上@Provides表示对外提供的注入对象,@Singleton则表示对象是全局唯一的单例,不会在每次调用时创建一个全新的。

知识点2:Systrace埋点

在创建OkHttpClientImageLoader时,都使用trace("xxxx"){}的代码块,包装了网络请求、图片加载的过程。其作用在于监控 关键(耗时) 操作,这种埋点收集的数据会写入system trace buffer中,可以使用Systrace工具进行分析,定位耗时操作。

知识点3:coil图片加载器

这里没有使用耳熟能详的Glide进行图片加载,而是使用了Coil。Coil是JetBrains开发的用于图片加载的开源框架,其与老牌的Glide对比如下:

比较项 Coil Glide
开发语言 纯 Kotlin(协程优先) Java(兼容 Kotlin)
包体积 ≈200 KB(无额外依赖) ≈500 KB(含解码库)
API 风格 函数式 DSL,Jetpack Compose 原生支持 链式调用,需要配置 RequestOptions
默认内存管理 自动复用 Bitmap,协程轻量化 Bitmap 池 + 四级缓存
GIF 支持 需依赖 coil-gif 原生支持
学习曲线 简单(Kotlin 友好) 中等(复杂定制需深入 API)

retrofit包------构建单例服务器数据仓库

retrofit包里面只有RetrofitNiaNetwork一个类,它提供初始化Retrofit单例对象、访问服务器接口的功能。

kotlin 复制代码
private interface RetrofitNiaNetworkApi { // Retrofit自动关联网络请求调用
    @GET(value = "topics")
    suspend fun getTopics(
        @Query("id") ids: List<String>?,
    ): NetworkResponse<List<NetworkTopic>>

    @GET(value = "newsresources")
    suspend fun getNewsResources(
        @Query("id") ids: List<String>?,
    ): NetworkResponse<List<NetworkNewsResource>>

    @GET(value = "changelists/topics")
    suspend fun getTopicChangeList(
        @Query("after") after: Int?,
    ): List<NetworkChangeList>

    @GET(value = "changelists/newsresources")
    suspend fun getNewsResourcesChangeList(
        @Query("after") after: Int?,
    ): List<NetworkChangeList>
}

// 泛型,封装网络请求结果
@Serializable
private data class NetworkResponse<T>(
    val data: T,
)

以下是Retrofit数据源的构建过程,与database相同,它也是一个单例。

kotlin 复制代码
@Singleton
internal class RetrofitNiaNetwork @Inject constructor(
    networkJson: Json,
    okhttpCallFactory: dagger.Lazy<Call.Factory>,
) : NiaNetworkDataSource {

    private val networkApi = trace("RetrofitNiaNetwork") { // Systrace跟踪
        Retrofit.Builder()
            .baseUrl(NIA_BASE_URL)
            // We use callFactory lambda here with dagger.Lazy<Call.Factory>
            // to prevent initializing OkHttp on the main thread.
            .callFactory { okhttpCallFactory.get().newCall(it) } // 防止在主线程初始化
            .addConverterFactory(
                networkJson.asConverterFactory("application/json".toMediaType()),
            )
            .build()
            .create(RetrofitNiaNetworkApi::class.java)
    }

    override suspend fun getTopics(ids: List<String>?): List<NetworkTopic> =
        networkApi.getTopics(ids = ids).data

    override suspend fun getNewsResources(ids: List<String>?): List<NetworkNewsResource> =
        networkApi.getNewsResources(ids = ids).data

    override suspend fun getTopicChangeList(after: Int?): List<NetworkChangeList> =
        networkApi.getTopicChangeList(after = after)

    override suspend fun getNewsResourceChangeList(after: Int?): List<NetworkChangeList> =
        networkApi.getNewsResourcesChangeList(after = after)
}
相关推荐
brzhang8 小时前
代码即图表:dbdiagram.io让数据库建模变得简单高效
前端·后端·架构
MaCa .BaKa8 小时前
35-疫苗预约管理系统(微服务)
spring boot·redis·微服务·云原生·架构·springcloud
三原10 小时前
2025 乾坤(qiankun)和 Vue3 最佳实践(提供模版)
vue.js·架构·前端框架
高桐@BILL11 小时前
1.4 大模型应用产品与技术架构
人工智能·架构·agent
zizisuo11 小时前
6.1.多级缓存架构
缓存·架构
超人强12 小时前
运营弹窗管理
架构
Wgllss13 小时前
按需下载!!全动态插件化框架WXDynamicPlugin解析怎么支持的
android·架构·android jetpack
国际云,接待13 小时前
[特殊字符]服务器性能优化:从硬件到AI的全栈调优指南
运维·服务器·人工智能·阿里云·性能优化·架构·云计算
前端太佬14 小时前
从零到一实现扫码登录:一个前端菜鸟的踩坑实录
前端·javascript·架构
karatttt15 小时前
用go从零构建写一个RPC(仿gRPC,tRPC)--- 版本1
后端·qt·rpc·架构·golang