美文网首页
TMS-PDA的架构组成

TMS-PDA的架构组成

作者: 我要离开浪浪山 | 来源:发表于2023-12-04 11:51 被阅读0次

    一、项目架构

    • 基于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()
    
    

    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、功能点单独抽出来,比如“拍照”和“蓝牙打印”;

    相关文章

      网友评论

          本文标题:TMS-PDA的架构组成

          本文链接:https://www.haomeiwen.com/subject/pqfzzrtx.html