Android Jetpack实战

Ai编程前序

现在是Ai的时代,竟然已经知道Android Jetpack的简介和组成,那么我们可以直接问Ai,让它帮我们用Jetpack进行封装一套基础框架出来。我们需要掌握的是方向,不要让Ai给带出轨了。

比如,我是这么问的"帮我用Android Jetpack生成一个基础的框架,需要包含图片、网络、数据库的处理,以及Fragment、Activity、Dialog的声明周期管控,尽量使用封装的方式,增加代码的复用性!"

你需要一个基于 Android Jetpack 的基础项目框架,要求封装图片、网络、数据库模块,同时管控 Activity、Fragment、Dialog 的生命周期,提升代码复用性。下面我会基于 MVVM 架构 搭建这个框架,所有模块均做通用封装,且附带完整的代码结构和示例(见文章末)。

一、框架整体设计

1. 技术栈选型

功能模块 核心技术/库
生命周期管控 Jetpack Lifecycle、ViewModel、SavedStateHandle
网络请求 Retrofit + OkHttp + Kotlin 协程
本地数据库 Jetpack Room(ORM 框架)
图片加载 Glide(封装统一入口)
基础 UI 封装 BaseActivity、BaseFragment、BaseDialogFragment
架构模式 MVVM(ViewModel + Repository + LiveData/Flow)

2. 项目目录结构(清晰分层,便于扩展)

复制代码
├── app
│   ├── src
│   │   ├── main
│   │   │   ├── java/com/your/package/
│   │   │   │   ├── App.kt                  // 应用入口,初始化全局组件
│   │   │   │   ├── core/                   // 核心封装(全局复用)
│   │   │   │   │   ├── base/               // 基础组件封装(生命周期管控核心)
│   │   │   │   │   │   ├── BaseActivity.kt
│   │   │   │   │   │   ├── BaseFragment.kt
│   │   │   │   │   │   ├── BaseDialogFragment.kt
│   │   │   │   │   │   └── BaseViewModel.kt
│   │   │   │   │   ├── constant/           // 全局常量
│   │   │   │   │   └── utils/              // 工具类(网络、图片、生命周期等)
│   │   │   │   ├── network/                // 网络模块封装
│   │   │   │   │   ├── ApiService.kt       // 接口定义
│   │   │   │   │   ├── RetrofitClient.kt   // Retrofit 单例封装
│   │   │   │   │   ├── OkHttpConfig.kt     // OkHttp 配置(拦截器、超时等)
│   │   │   │   │   └── model/              // 网络返回数据模型
│   │   │   │   ├── database/               // 数据库模块封装
│   │   │   │   │   ├── AppDatabase.kt      // Room 数据库单例
│   │   │   │   │   ├── dao/                // 数据访问接口
│   │   │   │   │   └── entity/             // 数据库实体类
│   │   │   │   ├── image/                  // 图片加载模块封装
│   │   │   │   │   └── ImageLoader.kt      // Glide 封装入口
│   │   │   │   ├── repository/             // 数据仓库(统一管理网络/本地数据)
│   │   │   │   └── ui/                     // 业务页面(示例)
│   │   │   │       ├── main/               // 主页面示例
│   │   │   │       └── demo/               // 功能演示页面
│   │   │   └── res/                        // 资源文件
│   │   └── test/                           // 单元测试
│   └── build.gradle                        // 模块依赖配置

二、第一步:项目依赖配置

先在 Module 级 build.gradle(app 目录下)添加所需依赖,确保 Jetpack 组件和第三方库版本兼容:

gradle 复制代码
plugins {
    id 'com.android.application'
    id 'org.jetbrains.kotlin.android'
    id 'kotlin-kapt' // Room 注解处理器需要
}

android {
    namespace "com.example.jetpackbase"
    compileSdk 34

    defaultConfig {
        applicationId "com.example.jetpackbase"
        minSdk 21
        targetSdk 34
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = '1.8'
    }
    viewBinding {
        enabled = true // 启用 ViewBinding,替代 findViewById,提升 UI 操作安全性
    }
}

dependencies {
    // ===== Jetpack 核心(生命周期 + 架构)=====
    implementation 'androidx.core:core-ktx:1.12.0'
    implementation 'androidx.appcompat:appcompat:1.6.1'
    implementation 'com.google.android.material:material:1.11.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.4'

    // Lifecycle(生命周期管控)
    implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0'
    implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.7.0'
    implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.7.0'
    implementation 'androidx.lifecycle:lifecycle-fragment-ktx:2.7.0'

    // ViewModel + SavedState(进程回收数据恢复)
    implementation 'androidx.lifecycle:lifecycle-viewmodel-savedstate:2.7.0'

    // Room(数据库)
    implementation 'androidx.room:room-runtime:2.6.1'
    kapt 'androidx.room:room-compiler:2.6.1'
    implementation 'androidx.room:room-ktx:2.6.1' // Room 对 Kotlin 协程的支持

    // ===== 网络相关 =====
    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.9.0' // Gson 解析
    implementation 'com.squareup.okhttp3:okhttp:4.12.0'
    implementation 'com.squareup.okhttp3:logging-interceptor:4.12.0' // 网络日志拦截器

    // ===== 图片加载 =====
    implementation 'com.github.bumptech.glide:glide:4.16.0'
    kapt 'com.github.bumptech.glide:compiler:4.16.0'

    // ===== 测试 =====
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.5'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
}

三、第二步:核心封装(生命周期管控 + 基础 UI)

这是框架的核心,通过封装 Base 系列类,统一管控生命周期,抽离通用逻辑,减少重复代码。

3.1 封装 BaseViewModel(统一管理数据状态 + 生命周期)

所有业务 ViewModel 继承此类,统一处理加载状态、错误信息、数据回调,避免冗余。

kotlin 复制代码
package com.example.jetpackbase.core.base

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel

/**
 * 基础 ViewModel,封装通用逻辑:加载状态、错误信息、SavedState 数据恢复
 * @param savedStateHandle 用于进程回收后的数据恢复
 */
open class BaseViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() {
    // 加载状态(可观察,用于 UI 显示加载框)
    private val _loadingState = MutableLiveData<Boolean>(false)
    val loadingState: LiveData<Boolean> = _loadingState

    // 错误信息(可观察,用于 UI 显示错误提示)
    private val _errorMsg = MutableLiveData<String>("")
    val errorMsg: LiveData<String> = _errorMsg

    // ===== 通用方法封装 =====
    /**
     * 更新加载状态
     */
    protected fun updateLoadingState(isLoading: Boolean) {
        _loadingState.postValue(isLoading)
    }

    /**
     * 更新错误信息
     */
    protected fun updateErrorMsg(msg: String) {
        _errorMsg.postValue(msg)
    }

    /**
     * 保存数据到 SavedState(进程回收后可恢复)
     */
    protected fun <T> saveData(key: String, value: T) {
        savedStateHandle.set(key, value)
    }

    /**
     * 从 SavedState 读取数据
     */
    protected fun <T> getData(key: String, defaultValue: T): T {
        return savedStateHandle.get(key) ?: defaultValue
    }

    /**
     * ViewModel 销毁时的清理工作(子类可重写)
     */
    override fun onCleared() {
        super.onCleared()
        // 统一清理资源,避免内存泄漏(如取消协程、关闭流等)
    }
}

3.2 封装 BaseActivity(管控 Activity 生命周期 + 通用 UI 逻辑)

统一处理布局加载、ViewModel 初始化、加载状态、生命周期回调,所有业务 Activity 继承此类。

kotlin 复制代码
package com.example.jetpackbase.core.base

import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.ViewModelProvider
import androidx.viewbinding.ViewBinding
import com.example.jetpackbase.core.utils.ToastUtils

/**
 * 基础 Activity,封装通用逻辑:
 * 1. ViewBinding 初始化
 * 2. ViewModel 统一获取
 * 3. 生命周期管控
 * 4. 通用加载状态、错误提示
 */
abstract class BaseActivity<VB : ViewBinding, VM : BaseViewModel> : AppCompatActivity() {
    // 视图绑定(子类无需重复编写 findViewById)
    protected lateinit var binding: VB

    // 业务 ViewModel
    protected lateinit var viewModel: VM

    override fun onCreate(savedInstanceState: android.os.Bundle?) {
        super.onCreate(savedInstanceState)
        // 1. 初始化 ViewBinding
        binding = getViewBinding()
        setContentView(binding.root)

        // 2. 初始化 ViewModel
        viewModel = createViewModel()

        // 3. 订阅通用数据(加载状态、错误信息)
        subscribeCommonData()

        // 4. 子类初始化逻辑(页面数据、事件绑定等)
        initView()
        initData()
        initEvent()
    }

    // ===== 抽象方法(子类必须实现)=====
    /**
     * 获取 ViewBinding 实例(如:ActivityMainBinding.inflate(layoutInflater))
     */
    protected abstract fun getViewBinding(): VB

    /**
     * 创建 ViewModel 实例
     */
    protected abstract fun createViewModel(): VM

    /**
     * 初始化视图(如:设置标题、隐藏导航栏等)
     */
    protected open fun initView() {}

    /**
     * 初始化数据(如:加载网络数据、本地数据等)
     */
    protected open fun initData() {}

    /**
     * 初始化事件(如:按钮点击、列表条目点击等)
     */
    protected open fun initEvent() {}

    // ===== 通用逻辑封装 =====
    /**
     * 订阅 ViewModel 通用数据(加载状态、错误信息)
     */
    private fun subscribeCommonData() {
        // 监听加载状态(子类可重写 onLoadingChanged 自定义逻辑)
        viewModel.loadingState.observe(this) { isLoading ->
            onLoadingChanged(isLoading)
        }

        // 监听错误信息(子类可重写 onError 自定义逻辑,默认 Toast 提示)
        viewModel.errorMsg.observe(this) { msg ->
            if (msg.isNotBlank()) {
                onError(msg)
                viewModel.updateErrorMsg("") // 消费错误信息,避免重复展示
            }
        }
    }

    /**
     * 加载状态变化回调(子类可重写,如:显示/隐藏加载框)
     */
    protected open fun onLoadingChanged(isLoading: Boolean) {
        // 示例:默认空实现,子类可自定义加载框展示
    }

    /**
     * 错误信息回调(子类可重写,如:显示错误页面、弹窗提示等)
     */
    protected open fun onError(msg: String) {
        ToastUtils.showShort(msg)
    }

    // ===== 生命周期管控(子类可重写,增加统一逻辑)=====
    override fun onStart() {
        super.onStart()
        // 统一处理:页面可见时的逻辑(如:开启定位、刷新数据等)
    }

    override fun onPause() {
        super.onPause()
        // 统一处理:页面暂停时的逻辑(如:暂停视频播放、停止定位等)
    }

    override fun onDestroy() {
        super.onDestroy()
        // 统一清理资源,避免内存泄漏
    }
}

3.3 封装 BaseFragment(管控 Fragment 生命周期 + 与 Activity 协同)

Fragment 生命周期比 Activity 更复杂,封装后统一处理视图绑定、ViewModel 初始化、生命周期回调,避免常见坑(如:视图销毁后数据回调导致的空指针)。

kotlin 复制代码
package com.example.jetpackbase.core.base

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.viewbinding.ViewBinding
import com.example.jetpackbase.core.utils.ToastUtils

/**
 * 基础 Fragment,封装通用逻辑:
 * 1. ViewBinding 初始化(避免视图销毁后空指针)
 * 2. ViewModel 统一获取
 * 3. 生命周期管控(处理 Fragment 重建问题)
 * 4. 通用加载状态、错误提示
 */
abstract class BaseFragment<VB : ViewBinding, VM : BaseViewModel> : Fragment() {
    // 视图绑定(使用 nullable 类型,避免视图销毁后内存泄漏)
    private var _binding: VB? = null
    protected val binding: VB get() = _binding!!

    // 业务 ViewModel
    protected lateinit var viewModel: VM

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // 1. 初始化 ViewBinding
        _binding = getViewBinding(inflater, container)
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        // 2. 初始化 ViewModel
        viewModel = createViewModel()

        // 3. 订阅通用数据(加载状态、错误信息)
        subscribeCommonData()

        // 4. 子类初始化逻辑
        initView()
        initData()
        initEvent()
    }

    // ===== 抽象方法(子类必须实现)=====
    /**
     * 获取 ViewBinding 实例(如:FragmentHomeBinding.inflate(inflater, container, false))
     */
    protected abstract fun getViewBinding(inflater: LayoutInflater, container: ViewGroup?): VB

    /**
     * 创建 ViewModel 实例
     */
    protected abstract fun createViewModel(): VM

    /**
     * 初始化视图
     */
    protected open fun initView() {}

    /**
     * 初始化数据
     */
    protected open fun initData() {}

    /**
     * 初始化事件
     */
    protected open fun initEvent() {}

    // ===== 通用逻辑封装 =====
    private fun subscribeCommonData() {
        // 监听加载状态(与 Activity 生命周期协同,仅在视图可见时响应)
        viewModel.loadingState.observe(viewLifecycleOwner) { isLoading ->
            onLoadingChanged(isLoading)
        }

        // 监听错误信息
        viewModel.errorMsg.observe(viewLifecycleOwner) { msg ->
            if (msg.isNotBlank()) {
                onError(msg)
                viewModel.updateErrorMsg("")
            }
        }
    }

    /**
     * 加载状态变化回调
     */
    protected open fun onLoadingChanged(isLoading: Boolean) {}

    /**
     * 错误信息回调
     */
    protected open fun onError(msg: String) {
        ToastUtils.showShort(msg)
    }

    // ===== 生命周期管控(关键:处理 Fragment 重建)=====
    override fun onDestroyView() {
        super.onDestroyView()
        // 销毁 ViewBinding,避免内存泄漏(Fragment 视图销毁后,Binding 应置空)
        _binding = null
    }

    override fun onStart() {
        super.onStart()
        // 统一处理:Fragment 可见时的逻辑
    }

    override fun onPause() {
        super.onPause()
        // 统一处理:Fragment 暂停时的逻辑
    }
}

3.4 封装 BaseDialogFragment(管控 Dialog 生命周期 + 通用弹窗逻辑)

使用 DialogFragment 封装弹窗(自带 Lifecycle 支持,避免传统 Dialog 的生命周期问题),统一处理弹窗样式、数据传递、生命周期回调。

kotlin 复制代码
package com.example.jetpackbase.core.base

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.DialogFragment
import androidx.lifecycle.ViewModelProvider
import androidx.viewbinding.ViewBinding
import com.example.jetpackbase.core.utils.ToastUtils

/**
 * 基础 DialogFragment,封装通用逻辑:
 * 1. ViewBinding 初始化
 * 2. ViewModel 统一获取
 * 3. 生命周期管控(避免弹窗泄漏)
 * 4. 通用弹窗样式、数据传递
 */
abstract class BaseDialogFragment<VB : ViewBinding, VM : BaseViewModel> : DialogFragment() {
    private var _binding: VB? = null
    protected val binding: VB get() = _binding!!

    protected lateinit var viewModel: VM

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        _binding = getViewBinding(inflater, container)
        // 初始化弹窗样式(子类可重写)
        initDialogStyle()
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        viewModel = createViewModel()
        subscribeCommonData()
        initView()
        initData()
        initEvent()
    }

    // ===== 抽象方法 =====
    protected abstract fun getViewBinding(inflater: LayoutInflater, container: ViewGroup?): VB

    protected abstract fun createViewModel(): VM

    protected open fun initView() {}

    protected open fun initData() {}

    protected open fun initEvent() {}

    // ===== 通用逻辑封装 =====
    /**
     * 初始化弹窗样式(如:宽高、背景、动画等)
     */
    protected open fun initDialogStyle() {
        dialog?.window?.apply {
            // 默认样式:宽度填充屏幕80%,无标题
            setLayout(
                (resources.displayMetrics.widthPixels * 0.8).toInt(),
                ViewGroup.LayoutParams.WRAP_CONTENT
            )
            setTitle(null)
        }
    }

    private fun subscribeCommonData() {
        viewModel.loadingState.observe(viewLifecycleOwner) { isLoading ->
            onLoadingChanged(isLoading)
        }

        viewModel.errorMsg.observe(viewLifecycleOwner) { msg ->
            if (msg.isNotBlank()) {
                onError(msg)
                viewModel.updateErrorMsg("")
            }
        }
    }

    protected open fun onLoadingChanged(isLoading: Boolean) {}

    protected open fun onError(msg: String) {
        ToastUtils.showShort(msg)
    }

    // ===== 生命周期管控 =====
    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }

    /**
     * 关闭弹窗(统一入口,方便子类调用)
     */
    protected fun dismissDialog() {
        if (isAdded && !isRemoving) {
            dismiss()
        }
    }
}

四、第三步:功能模块封装(图片 + 网络 + 数据库)

4.1 图片加载模块封装(基于 Glide)

封装 ImageLoader 统一图片加载入口,屏蔽 Glide 底层细节,方便后续替换图片加载库(如:换成 Coil),提升复用性。

kotlin 复制代码
package com.example.jetpackbase.image

import android.content.Context
import android.widget.ImageView
import com.bumptech.glide.Glide
import com.bumptech.glide.load.resource.bitmap.CircleCrop
import com.bumptech.glide.request.RequestOptions
import com.example.jetpackbase.R

/**
 * 图片加载工具类(基于 Glide 封装)
 * 提供通用图片加载、圆形图片、占位图/错误图等功能
 */
object ImageLoader {
    // ===== 通用配置 =====
    private val defaultOptions: RequestOptions by lazy {
        RequestOptions()
            .placeholder(R.mipmap.ic_image_placeholder) // 默认占位图
            .error(R.mipmap.ic_image_error) // 默认错误图
            .centerCrop()
    }

    // ===== 公开方法(统一入口)=====
    /**
     * 加载普通图片
     */
    fun loadImage(
        context: Context,
        imageView: ImageView,
        url: String?,
        placeholderRes: Int = R.mipmap.ic_image_placeholder,
        errorRes: Int = R.mipmap.ic_image_error
    ) {
        val options = defaultOptions
            .placeholder(placeholderRes)
            .error(errorRes)

        Glide.with(context)
            .load(url)
            .apply(options)
            .into(imageView)
    }

    /**
     * 加载圆形图片(如:用户头像)
     */
    fun loadCircleImage(
        context: Context,
        imageView: ImageView,
        url: String?,
        placeholderRes: Int = R.mipmap.ic_avatar_placeholder,
        errorRes: Int = R.mipmap.ic_avatar_error
    ) {
        val options = defaultOptions
            .placeholder(placeholderRes)
            .error(errorRes)
            .transform(CircleCrop()) // 圆形裁剪

        Glide.with(context)
            .load(url)
            .apply(options)
            .into(imageView)
    }

    /**
     * 清除图片缓存(内存 + 磁盘)
     */
    fun clearCache(context: Context) {
        // 清除内存缓存(主线程)
        Glide.get(context).clearMemory()
        // 清除磁盘缓存(子线程)
        Thread {
            Glide.get(context).clearDiskCache()
        }.start()
    }
}

4.2 网络模块封装(Retrofit + OkHttp)

4.2.1 配置 OkHttp(拦截器 + 超时)
kotlin 复制代码
package com.example.jetpackbase.network

import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import java.util.concurrent.TimeUnit

/**
 * OkHttp 配置类
 * 封装超时设置、日志拦截器、请求头拦截器等
 */
object OkHttpConfig {
    // 超时时间(10 秒)
    private const val TIMEOUT = 10L

    // 构建 OkHttpClient 实例
    fun createOkHttpClient(): OkHttpClient {
        // 日志拦截器(仅在调试模式下打印网络日志)
        val loggingInterceptor = HttpLoggingInterceptor().apply {
            level = HttpLoggingInterceptor.Level.BODY
        }

        return OkHttpClient.Builder()
            .connectTimeout(TIMEOUT, TimeUnit.SECONDS) // 连接超时
            .readTimeout(TIMEOUT, TimeUnit.SECONDS) // 读取超时
            .writeTimeout(TIMEOUT, TimeUnit.SECONDS) // 写入超时
            .addInterceptor(loggingInterceptor) // 添加日志拦截器
            // 可添加其他拦截器(如:请求头拦截器、Token 拦截器、错误拦截器等)
//            .addInterceptor(HeaderInterceptor())
            .build()
    }
}
4.2.2 封装 Retrofit 单例
kotlin 复制代码
package com.example.jetpackbase.network

import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory

/**
 * Retrofit 单例类
 * 统一配置 BaseUrl、转换器、OkHttpClient 等
 */
object RetrofitClient {
    // 基础 Url(替换为你的实际接口地址)
    private const val BASE_URL = "https://api.example.com/"

    // Retrofit 实例(懒加载,仅初始化一次)
    private val retrofit: Retrofit by lazy {
        Retrofit.Builder()
            .baseUrl(BASE_URL)
            .client(OkHttpConfig.createOkHttpClient())
            .addConverterFactory(GsonConverterFactory.create()) // Gson 解析
            .build()
    }

    /**
     * 获取 ApiService 实例(通用方法,支持所有接口定义)
     */
    fun <T> createService(serviceClass: Class<T>): T {
        return retrofit.create(serviceClass)
    }

    // 示例:获取默认 ApiService 实例(可根据业务拆分多个 ApiService)
    val apiService: ApiService by lazy {
        createService(ApiService::class.java)
    }
}
4.2.3 定义接口与统一返回模型
kotlin 复制代码
package com.example.jetpackbase.network

import com.example.jetpackbase.network.model.ImageResponse
import retrofit2.http.GET
import retrofit2.http.Query

/**
 * 接口定义类(所有网络接口统一在此声明)
 */
interface ApiService {
    /**
     * 示例:获取图片列表
     */
    @GET("images")
    suspend fun getImageList(
        @Query("page") page: Int,
        @Query("size") size: Int
    ): BaseResponse<List<ImageResponse>>
}

// ===== 统一返回数据模型 =====
/**
 * 所有接口的统一返回格式
 * @param code 状态码(200 成功,其他失败)
 * @param msg 提示信息
 * @param data 返回数据
 */
data class BaseResponse<T>(
    val code: Int,
    val msg: String,
    val data: T?
)

/**
 * 图片数据模型(示例)
 */
data class ImageResponse(
    val id: String,
    val url: String,
    val title: String,
    val createTime: String
)

4.3 数据库模块封装(Jetpack Room)

4.3.1 定义实体类(Entity)
kotlin 复制代码
package com.example.jetpackbase.database.entity

import androidx.room.Entity
import androidx.room.PrimaryKey

/**
 * 图片缓存实体类(示例)
 * @Entity 注解:标记为 Room 数据库表,tableName 为表名
 */
@Entity(tableName = "image_cache")
data class ImageCacheEntity(
    @PrimaryKey val id: String, // 主键(唯一标识)
    val url: String, // 图片地址
    val title: String, // 图片标题
    val createTime: String, // 创建时间
    val cacheTime: Long // 缓存时间(用于清理过期缓存)
)
4.3.2 定义数据访问接口(Dao)
kotlin 复制代码
package com.example.jetpackbase.database.dao

import androidx.room.Dao
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Update
import com.example.jetpackbase.database.entity.ImageCacheEntity
import kotlinx.coroutines.flow.Flow

/**
 * 图片缓存 Dao(数据访问接口,定义增删改查方法)
 * @Dao 注解:标记为 Room 数据访问接口
 */
@Dao
interface ImageCacheDao {
    /**
     * 插入单条图片缓存(如果主键已存在,替换)
     */
    @Insert(onConflict = androidx.room.OnConflictStrategy.REPLACE)
    suspend fun insertImageCache(imageCache: ImageCacheEntity)

    /**
     * 批量插入图片缓存
     */
    @Insert(onConflict = androidx.room.OnConflictStrategy.REPLACE)
    suspend fun insertImageCacheList(imageCacheList: List<ImageCacheEntity>)

    /**
     * 根据 ID 查询图片缓存
     */
    @Query("SELECT * FROM image_cache WHERE id = :id")
    suspend fun getImageCacheById(id: String): ImageCacheEntity?

    /**
     * 查询所有图片缓存(返回 Flow,支持数据变化监听)
     */
    @Query("SELECT * FROM image_cache ORDER BY cacheTime DESC")
    fun getAllImageCacheFlow(): Flow<List<ImageCacheEntity>>

    /**
     * 删除过期缓存(根据缓存时间)
     */
    @Query("DELETE FROM image_cache WHERE cacheTime < :expireTime")
    suspend fun deleteExpireImageCache(expireTime: Long)
}
4.3.3 封装数据库单例(AppDatabase)
kotlin 复制代码
package com.example.jetpackbase.database

import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import com.example.jetpackbase.App
import com.example.jetpackbase.database.dao.ImageCacheDao
import com.example.jetpackbase.database.entity.ImageCacheEntity

/**
 * Room 数据库单例类
 * @Database 注解:定义数据库版本、包含的实体类
 */
@Database(
    entities = [ImageCacheEntity::class], // 包含的实体类
    version = 1, // 数据库版本(升级时需修改)
    exportSchema = false // 关闭 schema 导出(生产环境建议开启)
)
abstract class AppDatabase : RoomDatabase() {
    // 获取 Dao 实例(Room 自动实现)
    abstract fun imageCacheDao(): ImageCacheDao

    // 单例实现
    companion object {
        // 双重校验锁单例(避免多线程初始化问题)
        @Volatile
        private var INSTANCE: AppDatabase? = null

        fun getInstance(): AppDatabase {
            return INSTANCE ?: synchronized(this) {
                val instance = Room.databaseBuilder(
                    App.context, // 全局上下文(在 App 中初始化)
                    AppDatabase::class.java,
                    "jetpack_base_db" // 数据库文件名
                )
                    .fallbackToDestructiveMigration() // 版本升级时销毁旧数据库(开发环境,生产环境需写迁移逻辑)
                    .build()
                INSTANCE = instance
                instance
            }
        }
    }
}
4.3.4 封装 Repository(统一管理网络/本地数据)

遵循 MVVM 架构,Repository 作为数据层统一入口,屏蔽数据来源(网络/本地),ViewModel 仅与 Repository 交互,提升代码解耦和复用性。

kotlin 复制代码
package com.example.jetpackbase.repository

import com.example.jetpackbase.database.AppDatabase
import com.example.jetpackbase.database.entity.ImageCacheEntity
import com.example.jetpackbase.network.RetrofitClient
import com.example.jetpackbase.network.model.ImageResponse

/**
 * 图片数据仓库(统一管理网络图片数据和本地缓存数据)
 */
class ImageRepository {
    // 数据库 Dao 实例
    private val imageCacheDao = AppDatabase.getInstance().imageCacheDao()

    // ===== 网络数据操作 =====
    /**
     * 从网络获取图片列表
     */
    suspend fun getImageListFromNetwork(page: Int, size: Int): Result<List<ImageResponse>> {
        return try {
            val response = RetrofitClient.apiService.getImageList(page, size)
            if (response.code == 200 && response.data != null) {
                // 网络请求成功,缓存到本地数据库
                val cacheList = response.data!!.map {
                    ImageCacheEntity(
                        id = it.id,
                        url = it.url,
                        title = it.title,
                        createTime = it.createTime,
                        cacheTime = System.currentTimeMillis()
                    )
                }
                imageCacheDao.insertImageCacheList(cacheList)

                Result.success(response.data!!)
            } else {
                Result.failure(Throwable(response.msg ?: "网络请求失败"))
            }
        } catch (e: Exception) {
            Result.failure(e)
        }
    }

    // ===== 本地数据操作 =====
    /**
     * 从本地数据库获取图片缓存列表(支持数据变化监听)
     */
    fun getImageListFromLocal() = imageCacheDao.getAllImageCacheFlow()

    /**
     * 根据 ID 获取本地图片缓存
     */
    suspend fun getImageCacheById(id: String) = imageCacheDao.getImageCacheById(id)

    /**
     * 清理过期图片缓存
     */
    suspend fun clearExpireImageCache(expireTime: Long) = imageCacheDao.deleteExpireImageCache(expireTime)
}

五、第四步:全局初始化与示例使用

5.1 应用入口(App.kt)

kotlin 复制代码
package com.example.jetpackbase

import android.app.Application
import android.content.Context

/**
 * 应用入口类,初始化全局组件
 */
class App : Application() {
    companion object {
        // 全局上下文(避免内存泄漏,仅用于非 UI 场景)
        lateinit var context: Context
    }

    override fun onCreate() {
        super.onCreate()
        context = this.applicationContext

        // 初始化全局组件(如:数据库、网络、图片缓存等)
        // 1. 初始化 Room 数据库(预加载,可选)
        AppDatabase.getInstance()

        // 2. 初始化 Glide(可选,Glide 会自动初始化)
        // Glide.init(context, GlideBuilder())
    }
}

5.2 示例:使用框架实现图片加载与缓存

kotlin 复制代码
// 1. 业务 ViewModel
package com.example.jetpackbase.ui.demo

import androidx.lifecycle.SavedStateHandle
import com.example.jetpackbase.core.base.BaseViewModel
import com.example.jetpackbase.repository.ImageRepository
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch

class ImageDemoViewModel(savedStateHandle: SavedStateHandle) : BaseViewModel(savedStateHandle) {
    private val imageRepository = ImageRepository()

    // 加载网络图片列表
    fun loadImageList(page: Int = 1, size: Int = 20) {
        updateLoadingState(true)
        GlobalScope.launch(Dispatchers.IO) {
            val result = imageRepository.getImageListFromNetwork(page, size)
            updateLoadingState(false)
            result.onSuccess {
                // 数据加载成功,通知 UI 更新
            }.onFailure {
                // 数据加载失败,更新错误信息
                updateErrorMsg(it.message ?: "加载图片列表失败")
            }
        }
    }
}

// 2. 业务 Activity
package com.example.jetpackbase.ui.demo

import androidx.lifecycle.ViewModelProvider
import com.example.jetpackbase.core.base.BaseActivity
import com.example.jetpackbase.databinding.ActivityImageDemoBinding
import com.example.jetpackbase.image.ImageLoader

class ImageDemoActivity : BaseActivity<ActivityImageDemoBinding, ImageDemoViewModel>() {
    override fun getViewBinding() = ActivityImageDemoBinding.inflate(layoutInflater)

    override fun createViewModel() = ViewModelProvider(this)[ImageDemoViewModel::class.java]

    override fun initView() {
        // 加载示例图片
        ImageLoader.loadImage(this, binding.ivDemo, "https://example.com/demo.jpg")
        // 加载圆形头像
        ImageLoader.loadCircleImage(this, binding.ivAvatar, "https://example.com/avatar.jpg")
    }

    override fun initData() {
        // 加载图片列表
        viewModel.loadImageList()
    }
}

六、框架总结与扩展建议

总结

  1. 生命周期管控 :通过 BaseActivity/BaseFragment/BaseDialogFragment 统一封装,结合 ViewModel/LiveData,避免内存泄漏,简化生命周期逻辑处理。
  2. 模块封装:图片、网络、数据库均提供统一入口,屏蔽底层细节,提升代码复用性和可维护性,后续替换第三方库无需修改业务代码。
  3. 架构规范 :遵循 MVVM 架构,通过 Repository 统一管理数据来源,实现 UI 与数据层解耦,便于单元测试和功能扩展。
    这个简易框架是基础版本,足够满足日常开发的大部分需求,你可以根据实际业务场景进行扩展和优化。

以上为学习资料,下面是代码案例,跟上面资料有差异

https://github.com/HunterHR/AndroidJetpackFirst

扩展建议

  1. 添加协程封装 :统一处理协程的异常和生命周期,避免使用 GlobalScope,推荐使用 viewModelScope/lifecycleScope
  2. 添加错误处理:封装全局错误拦截器,统一处理网络错误、数据库错误、业务错误。
  3. 添加缓存策略:完善图片缓存和数据缓存的过期策略,提升应用离线体验。
  4. 添加组件化支持:如果项目规模扩大,可基于此框架进行组件化拆分,提升团队协作效率。
  5. 添加 UI 组件封装:封装通用的列表、弹窗、下拉刷新等 UI 组件,进一步提升复用性。
相关推荐
等风来不如迎风去2 小时前
【android】oppo手机拷贝视频文件
android·windows·智能手机
悠哉清闲2 小时前
android studio中怎么引用SVG
android·android studio
TheNextByte13 小时前
在小米上检索照片/视频的5种方法
android
我命由我123453 小时前
JUnit - 自定义 Rule
android·java·开发语言·数据库·junit·java-ee·android-studio
2501_915921433 小时前
在没有源码的前提下,怎么对 Swift 做混淆,IPA 混淆
android·开发语言·ios·小程序·uni-app·iphone·swift
00后程序员张16 小时前
对比 Ipa Guard 与 Swift Shield 在 iOS 应用安全处理中的使用差异
android·开发语言·ios·小程序·uni-app·iphone·swift
悠哉清闲18 小时前
不同车型drawable不同
android·开发语言
00后程序员张21 小时前
在 iOS 设备上同时监控 CPU、GPU 与内存的方法
android·ios·小程序·https·uni-app·iphone·webview
测试_AI_一辰21 小时前
项目实践笔记 9:打卡/日报Agent项目Bug 修改与稳定性收口(v1.0)
android·开发语言·人工智能·功能测试·ai编程·ab测试