TMS-PDA的架构组成

2023-12-04  本文已影响0人  我要离开浪浪山

一、项目架构

二、项目引入

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()

4、LiveDataBus代替eventBus传值;

5、MmkvUtils代替SP存储本地数据;

6、StatusBar扩展函数处理沉浸式状态栏;

7、自定义ToastUtils提示;

8、DateTimeHelper时间格式转化;

9、ConstraintLayout代替LinearLayout和RelativeLayout布局;

10、Kotlin 的扩展函数,高阶函数(apply、also、let、run和with),接口回调、判空处理,for循环等;

11、子线程全部使用协程;

12、布局大小和颜色统一布局引入dimens和colors;

13、布局文本统一用strings;

六、项目重构的思考

1、按照功能模块创建文件夹

721beb89e4a4396056c921151ec571a.png

2、每个页面单独的功能和UI,除非功能完全一致,否则不复用;

3、功能点单独抽出来,比如“拍照”和“蓝牙打印”;

上一篇下一篇

猜你喜欢

热点阅读