【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)
}
相关推荐
天若有情6732 小时前
作为软件专业学生,我眼中新架构实践的‘稳’与‘进’
架构
MasterNeverDown3 小时前
.net 微服务jeager链路跟踪
微服务·架构·.net
萤丰信息5 小时前
智慧工地如何撕掉“高危低效”标签?三大社会效益重构建筑业价值坐标
java·大数据·人工智能·微服务·重构·架构·智慧工地
技术小泽6 小时前
深度解析Netty架构工作原理
java·后端·性能优化·架构·系统架构
其古寺7 小时前
异地多活架构:从“机房炸了”到“用户无感”的逆袭之路
架构·异地多活
掘金-我是哪吒8 小时前
分布式微服务系统架构第168集:不要让“百万用户”直连 Redis
redis·分布式·微服务·架构·系统架构
闲不住的李先森9 小时前
前端渲染模式演进与选型指南:从 CSR 到 Islands
前端·架构
眠りたいです13 小时前
基于脚手架微服务的视频点播系统-界面布局部分(二):用户界面及系统管理界面布局
c++·qt·ui·微服务·云原生·架构·cmake
喂完待续13 小时前
【Big Data】云原生与AI时代的存储基石 Apache Ozone 的技术演进路径
云原生·架构·apache·big data·序列晋升
lssjzmn14 小时前
会话管理巅峰对决:Spring Web中Cookie-Session、JWT、Spring Session + Redis深度秘籍
java·spring·架构