Android 使用 MVVM 架构快速开发

一、项目简介

本文介绍的这个项目,主要是为了解决安卓应用搭建、组件选型繁琐等问题,可以帮助开发者更加快速的开发一款安卓应用。

项目码云地址

项目分为了3个不同的包,每个包针对了不同需要的开发人群:

  • mvvm:只包含 mvvm 架构以及网络请求和数据库访问的能力,不包含任何资源文件。
  • common-ui:在 mvvm 的基础上又提供了部分可以快速开发的组件,同时也集成了一些优秀的第三方包,去帮助开发者解决状态栏/导航栏、分辨率适配等日常开发中需要考虑的问题。
  • fast-ui:在 common-ui 的基础上提供部分自定义的 UI 效果,以达到更快速开发应用的目的。

二、项目依赖

build.gradle

repositories {
    maven { url 'https://jitpack.io' }
}
build.gradle.kts

repositories {
    maven { url = uri("https://jitpack.io") }
}
// mvvm
implementation("com.gitee.superpeter2020.androidbase:mvvm:0.0.4")
// common-ui
implementation("com.gitee.superpeter2020.androidbase:common-ui:0.0.4")
// fast-ui
implementation("com.gitee.superpeter2020.androidbase:fast-ui:0.0.4")

三、项目使用

mvvm

mvvm 库的使用,遵循一个逻辑:页面和数据是分层的,ViewModel 是它们之间衔接的桥梁,页面通过 ViewModel 去获取数据,同时页面作为观察者在监听到数据返回后再去更新页面内容。

下面只给出 kotlin 的使用方式,java 的使用方式可以在项目的码云地址里进行查看。

使用前需要先在 manifest 里配置 Application:

class CustomApplication : BaseApplication()
1、基础使用
// 1. 创建 Activity/Fragment
class MainActivity : BaseLifecycleActivity<MainViewModel>() {
    override fun addDataObserver(viewModel: MainViewModel) {
        // ViewModel 是连接 Activity 和 数据的桥梁
        // 所有和视图相关的数据都应该在这个回调里进行处理
        viewModel.liveData.observe(this) {
            // 更新视图
        }
    }
}
class SubFragment : BaseLifecycleFragment<SubViewModel>() {
    override fun addDataObserver(viewModel: SubViewModel) {
        // 使用方式同 Activity
    }
}

// 2. 创建 ViewModel
class MainViewModel(application: Application) : NulViewModel(application) {
    
    // 创建 LiveData
    val liveData: BaseLiveData<String> = BaseLiveData()
}
2、网络请求

网络请求使用了 Retrofit + OkHttp 的框架。

// 1.创建网络请求 ViewModel
class NetworkViewModel(application: Application) : NetViewModel<NetworkRepository>(application) {
    val getRequestData = RequestLiveData<String>()
    val postRequestData = RequestLiveData<PostResponse>()

    // GET 请求
    fun doGetRequest() {
        mRepository?.doGetRequest(getRequestData)
    }

    // POST 请求
    fun doPostRequest(request: PostRequest) {
        mRepository?.doPostRequest(request, postRequestData)
    }

    // 下载请求
    fun doDownloadRequest(liveData: DownloadLiveData?) {
        mRepository?.doDownloadRequest(liveData)
    }

    // 上传请求
    fun doUploadRequest(liveData: UploadLiveData<String>) {
        mRepository?.doUploadRequest(liveData)
    }

}

// 2. 创建供 ViewModel 使用的网络请求能力类
class NetworkRepository(netImpl: NetRequestInterface) : NetRepository<NetworkServer>(netImpl) {

    // GET 请求
    fun doGetRequest(liveData: RequestLiveData<String>) {
        sendRequest(mServer.doGetRequest(), liveData)
    }

    // POST 请求
    fun doPostRequest(request: PostRequest, liveData: RequestLiveData<PostResponse>?) {
        sendRequest(mServer.doPostRequest(request), liveData)
    }

    // 下载请求
    fun doDownloadRequest(liveData: DownloadLiveData?) {
        downloadFile(mServer.doDownloadRequest(), liveData)
    }

    // 上传请求
    fun doUploadRequest(liveData: UploadLiveData<String>) {
        uploadFile(
            mServer.doUploadRequest(
                createUploadParams(liveData, keyFileName = "file", null, null)
            ),
            liveData
        )
    }
}

// 3. 创建接口服务
interface NetworkServer {

    // GET 请求
    @GET("test/getRequest")
    fun doGetRequest(): Observable<String>

    // POST 请求
    @POST("test/postRequest")
    fun doPostRequest(@Body request: PostRequest): Observable<PostResponse>

    // 下载请求
    @GET("test/downloadRequest")
    @Streaming
    fun doDownloadRequest(): Observable<ResponseBody>

    // 上传请求
    @POST("test/uploadRequest")
    @Multipart
    fun doUploadRequest(@Part parts: List<MultipartBody.Part>?): Observable<String>
}


// 4. 监听请求数据/上传下载调用
override fun addDataObserver(viewModel: NetworkViewModel) {
    viewModel.getRequestData.observeRequest(this,
        object : RequestObserver<String>() {
            override fun onRequestResult(result: String) {
                // 更新页面
            }
    })
    viewModel.postRequestData.observeRequest(this,
        object : RequestObserver<PostResponse>() {
            override fun onRequestResult(result: PostResponse) {
                // 更新页面
            }
    })
}
// 文件下载
private fun downloadFile() {
    val downloadFile = File("文件下载路径")
    val liveData = DownloadLiveData(downloadFile)
    liveData.observeDownload(this,
        object : DownloadObserver() {
            override fun onDownloadFile(file: File) {
                // 处理文件
            }
        })
    mViewModel.doDownloadRequest(liveData)
}
// 文件上传
private fun uploadFile() {
    val file = File("上传文件路径")
    val liveData = UploadLiveData<String>(file)
    liveData.observeUpload(this, object : UploadObserver<String>() {
        override fun onUploadResult(result: String) {
            // 处理文件上传结果
        }
    })
    mViewModel.doUploadRequest(liveData)
}


// 5. 初始化网络请求配置
class CustomApplication : BaseApplication() {

    override fun onCreate() {
        super.onCreate()
        initHttpConfig()
    }

    private fun initHttpConfig() {
        RetrofitManager.getInstance().init(
            HttpConfig.Builder()
                .setBaseUrl("服务地址")
                .build())
    }
}
3、数据库访问

数据库使用的是 Room 框架,因为 Room 使用了注解处理工具,所以在使用数据库的时候需要配置 kapt:

plugins {
    id("org.jetbrains.kotlin.kapt")
}

kapt("androidx.room:room-compiler:xxx")

更高版本的 Room 更推荐使用编译速度更快的 ksp:

plugins {
    id("id("com.google.devtools.ksp")")
}

ksp("androidx.room:room-compiler:xxx")

 

// 1. 创建数据库 ViewModel
class DBViewModel(application: Application) : DatabaseViewModel<DBRepository>(application) {
    val insertData = DatabaseLiveData<Long>()
    val deleteData = DatabaseLiveData<Int>()
    val updateData = DatabaseLiveData<Int>()
    val queryData = DatabaseLiveData<List<User>>()

    fun insertUser(user: User) {
        mRepository?.insertUser(user, insertData)
    }

    fun deleteUser(user: User) {
        mRepository?.deleteUser(user, deleteData)
    }

    fun updateUser(user: User) {
        mRepository?.updateUser(user, updateData)
    }

    fun queryUser() {
        mRepository?.queryUser(queryData)
    }

}

// 2. 创建供 ViewModel 使用的数据库能力类
class DBRepository(impl: DatabaseInterface,
                   application: Application
) : DatabaseRepository<AppDatabase>(impl, application) {

    override fun addMigrations(builder: RoomDatabase.Builder<AppDatabase>) {
        builder.addMigrations(object : Migration(1, 2) {
            override fun migrate(database: SupportSQLiteDatabase) {
                // 处理数据库升降版本时的数据迁移或者表结构变更
            }
        })
    }

    override fun getDbName(): String {
        return "定义数据库DB文件名称"
    }

    // 增
    fun insertUser(user: User, liveData: DatabaseLiveData<Long>) {
        execute(mDb.userDao().insert(user), liveData)
    }

    // 删
    fun deleteUser(user: User, liveData: DatabaseLiveData<Int>) {
        execute(mDb.userDao().delete(user), liveData)
    }

    // 改
    fun updateUser(user: User, liveData: DatabaseLiveData<Int>) {
        execute(mDb.userDao().update(user), liveData)
    }

    // 查
    fun queryUser(liveData: DatabaseLiveData<List<User>>) {
        execute(mDb.userDao().query(), liveData)
    }

}


// 3. 自定义数据库
@Database(
    entities = [ User::class ], // 表结构
    version = Config.VERSION,   // 数据库的版本
    exportSchema = false        // 是否需要生成 Schema 文件
)
abstract class AppDatabase :  RoomDatabase() {

    abstract fun userDao(): UserDao // 访问和操作表的对象

}


// 4. 自定义表结构以及访问表的对象
@Entity
data class User(

    @PrimaryKey(autoGenerate = true)
    val id: Long?,

    /**
     * 姓名
     */
    @ColumnInfo(name = "nick_name") // 实体对象的名称可以和对应表的列名不同
    val name: String,

    /**
     * 年龄
     */
    val age: Int

)
@Dao
interface UserDao {
    /**
     * 插入用户
     */
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun insert(user: User): Single<Long>

    /**
     * 删除用户
     */
    @Delete
    fun delete(user: User): Single<Int>

    /**
     * 修改用户
     */
    @Update
    fun update(user: User): Single<Int>

    /**
     * 查询用户
     */
    @Query("SELECT * FROM User where name = 'Peter'")
    fun query(): Single<List<User>>
}

// 5. 操作数据库并监听结果
class DBFragment : BaseLifecycleFragment<DBViewModel>() {
    override fun addDataObserver(viewModel: DBViewModel) {
        viewModel.insertData.observeExecute(this, object : DatabaseObserver<Long>() {
            override fun onExecuteResult(result: Long) {
                // 若 result > 0,插入新数据成功
            }
        })
        viewModel.deleteData.observeExecute(this, object : DatabaseObserver<Int>() {
            override fun onExecuteResult(result: Int) {
                // 若 result > 0, 则删除数据成功
            }
        })
        viewModel.updateData.observeExecute(this, object : DatabaseObserver<Int>() {
            override fun onExecuteResult(result: Int) {
                // 若 result > 0, 则更新数据成功
            }
        })
        viewModel.queryData.observeExecute(this, object : DatabaseObserver<List<User>>() {
            override fun onExecuteResult(result: List<User>) {
                // 处理查询到的数据
            }
        })
        viewModel.insertUser() // 新增数据
        viewModel.deleteUser() // 删除数据
        viewModel.updateUser() // 更改数据
        viewModel.queryUser()  // 查询数据
    }
}

common-ui

在 mvvm 的基础上又封装了一些可以快速开发的组件,由于使用了 ViewBinding 绑定布局文件,所以项目里必须开启以下配置:

buildFeatures {
    viewBinding = true
}

初始化 common-ui 配置:

class CustomApplication : BaseApplication() {
    
    override fun onCreate() {
        Cu.init(CuConfig.Builder()
            .setOpenConsole(true) // 是否打开控制台
            .setHttpConfig(HttpConfig.Builder()
                .setBaseUrl("服务地址")
                .build())
            .setRefreshConfig(CommonRefreshUiConfig.Builder()
                .setHeadBgColorId(id) // 下拉头背景颜色
                .setHeadEnableLastTime(false) // 下拉头是否需要显示上次刷新时间
                .build())
            .setAutoSizeConfig(CommonAutoSizeConfig.Builder()
                .setBaseOnWidth(true) // 是否根据宽度适配
                .setAutoSizeWidth(DESIGN_WIDTH) // 设计稿宽度, 默认 375
                .setAutoSizeHeight(DESIGN_HEIGHT) // 设计稿高度, 默认 667
                .build())
            .build())
    }
}

common-ui 模块总体包含了以下功能:

  • Console:控制台(包含网络请求/通用日志)
  • CommonActivity:ViewBinding 布局绑定、沉浸式状态栏/导航栏、autoSize 分辨率适配等实现
  • CommonFragment:实现 ViewBinding 布局绑定
  • CommonQuickAdapter:实现快速 RecyclerView 适配器
  • CommonRefreshActivity:实现下拉刷新/上拉加载
  • CommonRefreshFragment:实现下拉刷新/上拉加载
  • CommonPagingFragment: 实现分页
Console

如果想使用控制台,请先确保初始化配置的时候已开启控制台。

因为控制台是以悬浮框的形式存在,所以需配置以下权限,并确保设置应用可以显示在其他应用的上层。

<manifest>
    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
    <uses-permission android:name="android.permission.SYSTEM_OVERLAY_WINDOW"/>
</manifest>
Console.log("输出日志到控制台");
CommonActivity

CommonActivity 泛型的第一个入参对应页面布局,第二个入参对应  ViewModel。

class MainActivity : CommonActivity<ActivityMainBinding, MainViewModel>() {
    
    override fun initView(savedInstanceState: Bundle?) {
        mBinding.tvTest.text = "测试"
    }
    
}

 继承 CommonActivity 可以实现沉浸式状态栏/导航栏。

class MainActivity : CommonActivity<ActivityMainBinding, MainViewModel>() {
    // 设置状态栏文字深色(默认为 false)
    @Override
    protected boolean isStatusDark() {
        return true;
    }

    // 设置内容是否在状态栏区域下(默认为 true)
    @Override
    protected boolean isStatusFitWindow() {
        return false;
    }

    // 设置状态栏背景色(默认为透明色)
    @Override
    protected int getStatusBgColor() {
        return ResourcesCompat.getColor(getResources(), R.color.title_bg_color, null);
    }

    // 设置导航栏浅色(默认为 true)
    @Override
    protected boolean isNavigationLight() {
        return false;
    }

    // 设置内容是否在导航栏区域上(默认为 false)
    @Override
    protected boolean isNavigationFitWindow() {
        return true;
    }

    // 设置导航栏背景色(默认透明)
    @Override
    protected int getNavigationBgColor() {
        return ResourcesCompat.getColor(getResources(), R.color.content_bg_color, null);
    }
}

分辨率适配的目的是为了保证在各手机分辨率上都可以还原设计稿,这里用的适配方式类似于 rpx 的概念。
Cu.init 里可以设置设计稿的宽和高,默认为 375 x 667,这里的单位是设备独立像素,也就是安卓里的dp。

下面先给出不开启分辨率适配的方式(默认是开启的):

// 继承 CommonActivityCancelAdapt 则不开启分辨率适配
class MainActivity : CommonActivityCancelAdapt<ActivityMainBinding, MainViewModel>()
class MainActivity : CommonActivity<ActivityMainBinding, MainViewModel>() {
    // 同时可以对单个页面进行定制化适配,比如有的页面只希望在屏幕高度区域内展示,那则可以适配设计稿高度
    override fun isBaseOnWidth(): Boolean {
        return false // 适配高度
    }

    override fun getSizeInDp(): Float {
        return 1024f // 返回当前页面的设计稿尺寸(根据适配宽度/高度返回具体数值)
    }
}
 CommonFragment

CommonFragment 的用法基本和 CommonActivity

CommonQuickAdapter
class MainActivity : CommonActivity<ActivityMainBinding, MainViewModel>() {
    override fun initView(savedInstanceState: Bundle?) {
        // 1. mBinding.rvContent 为需要使用快速适配器的 RecyclerView
        // 2. ItemViewBinding 为列表 item 对应的布局
        // 3. ItemData 为列表 item 对应的数据项
        // 4. dataList 为整个数据列表
        mBinding.rvContent.adapter = object : CommonQuickAdapter<ItemViewBinding, ItemData>(dataList) {
            override fun convert(binding: ItemViewBinding, data: ItemData, position: Int) {
                // 处理 item 布局以及对应 item 数据
            }

            override fun onItemClick(data: ItemData, position: Int) {
                // 处理 item 点击的回调
            }
        }
    }
}

同时,我们可以使用 CommonQuickAdapter 内部的方法:

  • addData:添加数据
  • removeData:删除数据
  • clearData:清除数据
  • setEmptyData :设置空数据
CommonRefreshActivity

用法同 CommonRefreshFragment。

CommonRefreshFragment

1、使用内置布局

class RefreshFragment : CommonRefreshFragment<LayoutCuRefreshBinding, NulViewModel>() {

    override fun onRefreshConfig(build: CommonRefreshConfig.Builder) {
        build.apply {
            setEnableRefresh(true) // 开启上拉刷新
            setEnableLoadMore(true) // 开启下拉加载
        }
    }

    override fun onRefresh(refreshLayout: RefreshLayout) {
        // 上拉刷新触发回调
    }

    override fun onLoadMore(refreshLayout: RefreshLayout) {
        // 下拉加载触发回调
    }

}

2、自定义布局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <!-- 自定义布局必须包含 -->
    <include
        layout="@layout/layout_cu_refresh_merge">
    </include>

</LinearLayout>
CommonPagingFragment
// CommonPagingFragment 泛型的4个参数含义分别为:
// 1. 页面布局,使用方式同 CommonRefreshFragment
// 2. 页面对应 ViewModel
// 3. 列表项布局
// 4. 列表项数据类型
class RefreshLoadPagingFragment : CommonPagingFragment<LayoutCuRefreshBinding, NulViewModel, ItemQuickAdapterBinding, Int>() {

    /**
     * 获取每页的数据回调
     */
    override fun getPageData(page: Int) {
        // 获取到数据后调用该方法
        doPagingResult(list)
    }

    /**
     * 获取总页数
     */
    override fun getTotalPage(): Int {
        return PAGE_COUNT
    }

    /**
     * 对recycleView进行自定义
     */
    override fun setRecyclerView(recyclerView: RecyclerView) {

    }

    /**
     * 渲染列表项
     */
    override fun convert(viewBinding: ItemQuickAdapterBinding, data: Int, position: Int) {

    }
}

fast-ui

fast-ui 的目的是为了更加快速的开发应用,我们在日常开发应用的过程中,有些项目可能产品会设计业务上的效果图,但是并不会去设计一些细节, 比如网络请求弹窗、空页面提示、app 升级页面等,fast-ui 的目的就是为了预先设计固定的一套样式,所以它和 common-ui 相比会增加资源文件。

fast-ui 目前的功能并不多,后续会持续更新。

common-ui 模块包含了以下功能:

  • 网络请求/上传下载弹窗
  • RecyclerView 空数据/无网络等提示页面
  • 应用升级弹窗提示
网络请求/上传下载弹窗

上述有网络请求的具体用法,我们只需要在创建观察器的时候传入对应参数就可以显示弹窗:

// RequestObserver 第一个参数传入 this 则显示弹窗
// 第二个参数可以自定义弹窗文字
viewModel.getRequestData.observeRequest(this, object : RequestObserver<String>(this, R.string.tips)

// UploadObserver/DownloadObserver 传入 this 则显示弹窗
liveData.observeUpload(this, object : UploadObserver<String>(this) {})
应用快速升级
val builder =  FuUpdateConfig.Builder()
builder.setTitle("配置升级标题")
       .setVersion("配置升级版本")
       .setContent("配置升级具体内容")

// 最后在显示升级框前还需要配置获取升级包的接口(文件流下载)
Fu.showUpdateDialog(builder.build(mViewModel.getUpdateServer(), this))

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值