一、项目简介
本文介绍的这个项目,主要是为了解决安卓应用搭建、组件选型繁琐等问题,可以帮助开发者更加快速的开发一款安卓应用。
项目分为了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))
1502

被折叠的 条评论
为什么被折叠?



