一、项目架构
- 基于MVVM模式集成谷歌官方推荐的JetPack组件库:LiveData、ViewModel、Lifecycle、Navigation组件;
- 使用kotlin语言,添加大量拓展函数,简化代码;
- 加入Retrofit网络请求,协程,帮你简化各种操作,让你快速请求网络;
二、项目引入
1、引入
//项目核心框架
implementation project(path: ':JetpackMvvm')
2、在app's build.gradle中,android 模块下按需开启DataBinding与ViewBinding
AndroidStudio 4.0 以下版本------>
android {
...
dataBinding {
enabled = true
}
viewBinding {
enabled = true
}
}
AndroidStudio 4.0及以上版本 ------>
android {
...
buildFeatures {
dataBinding = true
viewBinding = true
}
}
三、Activity或者Fragment的使用
1、HomeActivity 继承BaseActivity
class HomeActivity : BaseActivity<MainViewModel, ActivityMainBinding>(){
}
2、MainViewModel 继承BaseViewModel
class MainViewModel : BaseViewModel() {}
3、frgment也是同样的操作;
class LoginViewModel : BaseViewModel() {
}
创建LoginFragment 继承基类传入相关泛型,第一个泛型为你创建的LoginViewModel,第二个泛型为ViewDataBind,保存fragment_login.xml后databinding会生成一个FragmentLoginBinding类。(如果没有生成,试着点击Build->Clean Project)
class LoginFragment : BaseFragment<LoginViewModel, FragmentLoginBinding>() {
/**
* 初始化操作
*/
override fun initView(savedInstanceState: Bundle?) {
...
}
/**
* fragment 懒加载
*/
override fun lazyLoadData() {
...
}
}
四、网络请求(Retrofit+协程)
1、新建请求配置类继承 BaseNetworkApi 示例:
class NetworkApi : BaseNetworkApi() {
companion object {
val instance: NetworkApi by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { NetworkApi() }
//双重校验锁式-单例 封装NetApiService 方便直接快速调用
val service: ApiService by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
instance.getApi(ApiService::class.java, ApiService.SERVER_URL)
}
}
/**
* 实现重写父类的setHttpClientBuilder方法,
* 在这里可以添加拦截器,可以对 OkHttpClient.Builder 做任意你想要做的骚操作
*/
override fun setHttpClientBuilder(builder: OkHttpClient.Builder): OkHttpClient.Builder {
builder.apply {
//示例:添加公共heads,可以存放token,公共参数等, 注意要设置在日志拦截器之前,不然Log中会不显示head信息
addInterceptor(MyHeadInterceptor())
// 日志拦截器
addInterceptor(LogInterceptor())
//超时时间 连接、读、写
connectTimeout(10, TimeUnit.SECONDS)
readTimeout(5, TimeUnit.SECONDS)
writeTimeout(5, TimeUnit.SECONDS)
}
return builder
}
/**
* 实现重写父类的setRetrofitBuilder方法,
* 在这里可以对Retrofit.Builder做任意骚操作,比如添加GSON解析器,protobuf等
*/
override fun setRetrofitBuilder(builder: Retrofit.Builder): Retrofit.Builder {
return builder.apply {
addConverterFactory(GsonConverterFactory.create(GsonBuilder().create()))
addCallAdapterFactory(CoroutineCallAdapterFactory())
}
}
}
2、MyHeadInterceptor请求拦截器
class MyHeadInterceptor : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val builder = chain.request().newBuilder()
// builder.addHeader("token", "token123456").build()
builder.addHeader("device", "Android").build()
// builder.addHeader("isLogin", CacheUtil.isLogin().toString()).build()
return chain.proceed(builder.build())
}
}
3、如果你请求服务器返回的数据有基类(没有可忽略这一步)例如
{
"data": ...,
"errorCode": 0,
"errorMsg": ""
}
该示例格式是 标准的返回的数据格式,如果errorCode等于0 请求成功,否则请求失败 作为开发者的角度来说,我们主要是想得到脱壳数据-data,且不想每次都判断errorCode==0请求是否成功或失败 这时我们可以在服务器返回数据基类中继承BaseResponse,
实现相关方法:
data class ApiResponse<T>(var errorCode: Int, var errorMsg: String, var data: T) : BaseResponse<T>() {
// 这里是示例,wanandroid 网站返回的 错误码为 0 就代表请求成功,请你根据自己的业务需求来编写
override fun isSucces() = errorCode == 0
override fun getResponseCode() = errorCode
override fun getResponseData() = data
override fun getResponseMsg() = errorMsg
}
4、在ViewModel中发起请求,所有请求都是在viewModelScope中启动,请求会发生在IO线程,最终回调在主线程上,当页面销毁的时候,请求会统一取消,不用担心内存泄露的风险,框架做了2种请求使用方式
class RequestLoginViewModel: BaseViewModel {
//自动脱壳过滤处理请求结果,自动判断结果是否成功
var loginResult = MutableLiveData<ResultState<UserInfo>>()
//不用框架帮脱壳
var loginResult2 = MutableLiveData<ResultState<ApiResponse<UserInfo>>>()
fun login(username: String, password: String){
//1.在 Activity/Fragment的监听回调中拿到已脱壳的数据(项目有基类的可以用)
request(
{ HttpRequestCoroutine.login(username, password) }, //请求体
loginResult,//请求的结果接收者,请求成功与否都会改变该值,在Activity或fragment中监听回调结果,具体可看loginActivity中的回调
true,//是否显示等待框,,默认false不显示 可以默认不传
"正在登录中..."//等待框内容,可以默认不填请求网络中...
)
//2.在Activity/Fragment中的监听拿到未脱壳的数据,你可以自己根据code做业务需求操作(项目没有基类的可以用)
requestNoCheck(
{HttpRequestCoroutine.login(username,password)},
loginResult2,
true,
"正在登录中...")
}
class LoginFragment : BaseFragment<LoginViewModel, FragmentLoginBinding>() {
private val requestLoginRegisterViewModel: RequestLoginRegisterViewModel by viewModels()
/**
* 初始化操作
*/
override fun initView(savedInstanceState: Bundle?) {
...
}
/**
* fragment 懒加载
*/
override fun lazyLoadData() {
...
}
override fun createObserver(){
//脱壳
requestLoginRegisterViewModel.loginResult.observe(viewLifecycleOwner,
Observer { resultState ->
parseState(resultState, {
//登录成功 打印用户
it.username.logd()
}, {
//登录失败(网络连接问题,服务器的结果码不正确...异常都会走在这里)
showMessage(it.errorMsg)
})
})
//不脱壳
requestLoginRegisterViewModel.loginResult2.observe(viewLifecycleOwner, Observer {resultState ->
parseState(resultState,{
if(it.errorCode==0){
//登录成功 打印用户名
it.data.username.logd()
}else{
//登录失败
showMessage(it.errorMsg)
}
},{
//请求发生了异常
showMessage(it.errorMsg)
})
})
}
}
直接在当前ViewModel中拿到请求结果
class RequestLoginViewModel : BaseViewModel() {
fun login(username: String, password: String){
//1.拿到已脱壳的数据(项目有基类的可以用)
request({HttpRequestCoroutine.login(username,password)},{
//请求成功 已自动处理了 请求结果是否正常
it.username.logd()
},{
//请求失败 网络异常,或者请求结果码错误都会回调在这里
it.errorMsg.logd()
},true,"正在登录中...")
//2.拿到未脱壳的数据,你可以自己根据code做业务需求操作(项目没有基类或者不想框架帮忙脱壳的可以用)
requestNoCheck({HttpRequestCoroutine.login(username,password)},{
//请求成功 自己拿到数据做业务需求操作
if(it.errorCode==0){
//结果正确
it.data.username.logd()
}else{
//结果错误
it.errorMsg.logd()
}
},{
//请求失败 网络异常回调在这里
it.errorMsg.logd()
},true,"正在登录中...")
}
五、其它小问题
1、配置APP环境
打开BaseConfig类中设置AppCurrentEnvironment的值;
2、设置日志全局开关
//自动在BaseConfig中设置了
LogUtils.setLogOpen(isOpen)
方式1: "请求参数:$json".logd("mvvm")
方式2:LogUtils.debugInfo("mvvm", "请求参数:$json")
3、扩展函数
class ExtendUtils {
}
/**
* 正常编码中一般只会用到 [dp]/[sp] ;
* 其中[dp]/[sp] 会根据系统分辨率将输入的dp/sp值转换为对应的px
*/
val Float.dp: Float // [xxhdpi](360 -> 1080)
get() = android.util.TypedValue.applyDimension(
android.util.TypedValue.COMPLEX_UNIT_DIP, this, Resources.getSystem().displayMetrics
)
val Int.dp: Int
get() = android.util.TypedValue.applyDimension(
android.util.TypedValue.COMPLEX_UNIT_DIP,
this.toFloat(),
Resources.getSystem().displayMetrics
).toInt()
val Float.sp: Float // [xxhdpi](360 -> 1080)
get() = android.util.TypedValue.applyDimension(
android.util.TypedValue.COMPLEX_UNIT_SP, this, Resources.getSystem().displayMetrics
)
val Int.sp: Int
get() = android.util.TypedValue.applyDimension(
android.util.TypedValue.COMPLEX_UNIT_SP,
this.toFloat(),
Resources.getSystem().displayMetrics
).toInt()
网友评论