基本架构:Android 官方的推荐架构指南https://developer.android.google.cn/jetpack/guide#recommended-app-arch
请注意,每个组件仅依赖于其下一级的组件。
例如,Activity 和 Fragment 仅依赖于视图模型。存储区是唯一依赖于其他多个类的类;在本例中,存储区依赖于持久性数据模型和远程后端数据源。
这种设计打造了一致且愉快的用户体验。无论用户上次使用应用是在几分钟前还是几天之前,现在回到应用时都会立即看到应用在本地保留的用户信息。如果此数据已过时,则应用的存储区模块将开始在后台更新数据
Activity持有ViewModel,
ViewModel持有Repository,
Repository持有Dao,
Dao定义具体的增删改查方法
Activity持有ViewModel,
ViewModel持有Repository,
Repository持有Service,
Service定义具体的get,post请求方法
下面是参考官方的sunflower项目的案例
先来俩按钮:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<Button
android:id="@+id/tv_add"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="添加本地数据" />
<Button
android:id="@+id/tv_getList"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="获取电影列表" />
</LinearLayout>
然后设置各自的点击事件
既然是参照sunflower,我们先看本地数据操作的流程:
简单来说:Activity持有ViewModel,ViewModel持有Repository,Repository持有Dao,Dao定义具体的增删改查方法
1.初始化数据库和定义Dao
//entities是个数组,可以存放多张表
@Database(entities = [User::class], version = 1, exportSchema = false)
abstract class AppDataBase : RoomDatabase() {
//把你的Dao都在这里定义为抽象方法即可
abstract fun getUserDao(): UserDao
companion object {
const val DATABASE_NAME = "zx-db"
@Volatile
private var instance: AppDataBase? = null
fun getInstance(context: Context): AppDataBase {
return instance ?: synchronized(this) {
instance ?: buildDatabase(context).also { instance = it }
}
}
private fun buildDatabase(context: Context): AppDataBase {
return Room.databaseBuilder(context, AppDataBase::class.java, DATABASE_NAME).build()
}
}
}
@Entity
data class User (
@PrimaryKey
val id: Int,
val name:String,
val age: Int
)
@Dao
interface UserDao {
@Query("select * from User ORDER BY id")
fun getUsers(): LiveData<List<User>>
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertUsers(users: List<User>)
}
2.创建Repository
class UserRepository constructor(private val userDao: UserDao) {
fun getUser() = userDao.getUsers()
suspend fun insertUser(users: List<User>) = userDao.insertUsers(users)
companion object {
@Volatile
private var instance: UserRepository? = null
fun getInstance(userDao: UserDao) =
instance ?: synchronized(this) {
instance
?: UserRepository(userDao)
.also { instance = it }
}
}
}
3.创建ViewModel(使用ViewModelProvider.Factory 创建)
class UserListViewModel constructor(val userRepository: UserRepository) : ViewModel() {
val users: LiveData<List<User>> = userRepository.getUser()
suspend fun insertUser(users: List<User>) = userRepository.insertUser(users)
}
class UserListViewModelFactory constructor(val repository: UserRepository) :
ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return UserListViewModel(repository) as T
}
}
4.最后在MainActivity中得到ViewModel并调用相应方法
class MainActivity : AppCompatActivity() {
//初始化viewmodel
private val userViewModel: UserListViewModel by viewModels {
val appDataBase = AppDataBase.getInstance(this)
val repository = UserRepository.getInstance(appDataBase.getUserDao())
UserListViewModelFactory(repository)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//观察users
userViewModel.users.observe(this, Observer {
for (a in it) {
println("${a.name},${a.age},${a.id}")
}
})
tv_add.setOnClickListener {
val user1: User = User(1, "张飞", 22)
val user2: User = User(2, "诸葛亮", 23)
val user3: User = User(3, "张对对对", 27)
val user4: User = User(4, "张对对对22", 27)
val users = listOf<User>(user1, user2, user3, user4)
lifecycleScope.launch {
userViewModel.insertUser(users)
}
}
}
}
这样操作本地数据的方法就完成了,下面我们看看使用Retrofit网络请求的处理,也是一样
Activity持有ViewModel,ViewModel持有Repository,Repository持有Service,Service定义具体的get,post请求方法
1.先定义好一个Service
interface MsgService {
@GET("videoHomeTab")
suspend fun getVideoTab(): HttpResponse<List<Video>>
}
创建一个kt文件,所有的data都写在这一个里面
data class Video(
val nameType: Int,
val apiUrl: String,
val name: String,
val tabType: Int,
val id: Int
)
data class HttpResponse<out T>(val code: Int, val msg: String, val result: T)
2.创建Repository
class MsgRepository constructor(
private val msgService: MsgService
) {
suspend fun getVideoTab(): HttpResult<List<Video>> {
return apiCall { msgService.getVideoTab() }
}
companion object {
@Volatile
private var instance: MsgRepository? = null
fun getInstance(msgService: MsgService) =
instance ?: synchronized(this) {
instance
?: MsgRepository(msgService)
.also { instance = it }
}
}
}
sealed class HttpResult<out T> {
data class Success<out T>(val data: T) : HttpResult<T>()
data class Error(val e: String) : HttpResult<Nothing>()
override fun toString(): String {
return when (this) {
is Success<*> -> "Success[data=$data]"
is Error -> "Error[exception=$e]"
}
}
fun data(): T {
return (this as Success<T>).data
}
fun isSucceed(): Boolean {
return this is Success
}
fun error(): String {
return (this as Error).e
}
companion object {
fun handleException(e: Exception): String {
val context: Context = IApplication.CONTEXT
val result = when (e) {
null -> context.getString(R.string.basic_error_unknown)
is CertificateException, is SSLHandshakeException
-> context.getString(R.string.basic_error_certificate)
is MalformedURLException -> context.getString(R.string.basic_error_service_domain)
is HttpException -> context.getString(R.string.basic_error_service)
is InterruptedIOException, is SocketException, is TimeoutException, is UnknownHostException
-> context.getString(R.string.basic_error_network)
is JsonSyntaxException -> context.getString(R.string.basic_error_response_parse)
is IOException -> context.getString(R.string.basic_error_request)
is ClassCastException -> context.getString(R.string.basic_error_data_structure)
else -> e.toString()
}
return result
}
}
}
创建一个kt文件
suspend fun <T> apiCall(call: suspend () -> HttpResponse<T>): HttpResult<T> {
return try {
call().let {
if (it.code == 0 || it.code == 200) {
HttpResult.Success(it.result)
} else {
HttpResult.Error(it.msg)
}
}
} catch (e: Exception) {
HttpResult.Error(HttpResult.handleException(e))
}
}
3.创建ViewModel(使用ViewModelProvider.Factory 创建)
class MsgViewModel constructor(val repository: MsgRepository) : ViewModel() {
private val _getVideoTab = MutableLiveData<HttpResult<List<Video>>>()
val getVideoTab: LiveData<HttpResult<List<Video>>>
get() = _getVideoTab
fun getVideoTab() {
viewModelScope.launch {
_getVideoTab.value = repository.getVideoTab()
}
}
}
class MsgViewModelFactory constructor(val repository: MsgRepository) : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return MsgViewModel(repository) as T
}
}
4.最后在MainActivity中得到ViewModel并调用相应方法
class MainActivity : AppCompatActivity() {
private val msgViewModel: MsgViewModel by viewModels {
val retrofit = retrofitClient.createRetrofit("https://api.apiopen.top")
val msgService = retrofit.create(MsgService::class.java)
val repository = MsgRepository.getInstance(msgService)
MsgViewModelFactory(repository)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
msgViewModel.getVideoTab.observe(this, Observer {
if (it.isSucceed()) {
for (a in it.data()) {
println("${a.apiUrl},${a.name}")
}
} else {
Toast.makeText(this, it.error(), Toast.LENGTH_LONG).show()
}
})
}
}
retrofit的配置:
class RetrofitClient {
fun createRetrofit(baseUrl: String): Retrofit {
return Retrofit.Builder()
.baseUrl(baseUrl)
.client(provideClient(OkHttpClient.Builder()))
.addConverterFactory(GsonConverterFactory.create())
.build()
}
private fun provideClient(builder: OkHttpClient.Builder): OkHttpClient {
if (BuildConfig.DEBUG) {
builder.addNetworkInterceptor(initLogInterceptor())
}
builder.addInterceptor(initHeader())
builder.connectTimeout(30, TimeUnit.SECONDS)
builder.readTimeout(30, TimeUnit.SECONDS)
builder.writeTimeout(30, TimeUnit.SECONDS)
return builder.build()
}
//----------------------------------------具体配置---------------------------------------------
private fun initHeader(): Interceptor {
return Interceptor { chain ->
val originalRequest = chain.request()
val requestBuilder = originalRequest.newBuilder()
.header("Content-Type", "application/json")
.header("Accept", "application/json")
.method(originalRequest.method, originalRequest.body)
val request = requestBuilder.build()
chain.proceed(request)
}
}
private fun initLogInterceptor(): HttpLoggingInterceptor {
val loggingInterceptor = HttpLoggingInterceptor()
loggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
return loggingInterceptor
}
}
class IApplication : Application() {
companion object {
var CONTEXT: Context by Delegates.notNull()
}
override fun onCreate() {
super.onCreate()
CONTEXT = applicationContext
}
}
上述基本就实现了获取本地和网络数据
但是viewmodle的初始化不是很完美,我这边是koin来简化一下,并且把dao和service合并到一个Repository里面来
改动的地方:
1.MsgRepository持有dao和service
class MsgRepository constructor(
private val msgService: MsgService, private val userDao: UserDao
) {
suspend fun getVideoTab(): HttpResult<List<Video>> {
return apiCall { msgService.getVideoTab() }
}
fun getUser() = userDao.getUsers()
suspend fun insertUser(users: List<User>) = userDao.insertUsers(users)
companion object {
@Volatile
private var instance: MsgRepository? = null
fun getInstance(msgService: MsgService, userDao: UserDao) =
instance ?: synchronized(this) {
instance
?: MsgRepository(msgService, userDao)
.also { instance = it }
}
}
}
这里就可以就同一个Repository直接调用了
class MsgViewModel constructor(val repository: MsgRepository) : ViewModel() {
private val _getVideoTab = MutableLiveData<HttpResult<List<Video>>>()
val getVideoTab: LiveData<HttpResult<List<Video>>>
get() = _getVideoTab
fun getVideoTab() {
viewModelScope.launch {
_getVideoTab.value = repository.getVideoTab()
}
}
val users: LiveData<List<User>> = repository.getUser()
suspend fun insertUser(users: List<User>) = repository.insertUser(users)
}
2.创建AppModule.kt文件
val viewModelModule = module {
factory { MsgViewModel(get()) }
}
val repositoryModule = module {
single { MsgRepository(get(), get()) }
}
val dataModule = module {
single {
RetrofitClient().createRetrofit("https://api.apiopen.top").create(MsgService::class.java)
}
single {
AppDataBase.getInstance(IApplication.CONTEXT).getUserDao()
}
}
val appModule = listOf(viewModelModule, repositoryModule, dataModule)
3.Application中初始化
class IApplication : Application() {
companion object {
var CONTEXT: Context by Delegates.notNull()
}
override fun onCreate() {
super.onCreate()
CONTEXT = applicationContext
//初始化koin
startKoin {
androidContext(this@IApplication)
modules(appModule)
}
}
}
4.最后Mainactiviy就可以这样调用了
class MainActivity : AppCompatActivity() {
//通过koin获取ViewModel
val msgViewModel: MsgViewModel by inject()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
observers()
tv_add.setOnClickListener {
val user1: User = User(1, "张飞", 22)
val user2: User = User(2, "诸葛亮", 23)
val user3: User = User(3, "张对对对", 27)
val user4: User = User(4, "张对对对22", 27)
val users = listOf<User>(user1, user2, user3, user4)
lifecycleScope.launch {
msgViewModel.insertUser(users)
}
}
tv_getList.setOnClickListener {
msgViewModel.getVideoTab()
}
}
private fun observers() {
//通过msgViewModel获取网络数据
msgViewModel.getVideoTab.observe(this, Observer {
if (it.isSucceed()) {
for (a in it.data()) {
println("${a.apiUrl},${a.name}")
}
} else {
Toast.makeText(this, it.error(), Toast.LENGTH_LONG).show()
}
})
//通过msgViewModel获取本地数据
msgViewModel.users.observe(this, Observer {
for (a in it) {
println("${a.name},${a.age},${a.id}")
}
})
}
}
本案例涉及到的技术框架:LiveData,Lifecycles,ViewModel,Room,Koin,Retrofit
官方文档:https://developer.android.google.cn/jetpack