阅读 3585

️🔥️🔥️🔥 最新的kt+jetpack+Coroutine打造的Android MVVM开发框架

AACHulk


AACHulk是以Google的ViewModel+DataBinding+LiveData+Lifecycles框架为基础, 结合Okhttp+Retrofit+BaseRecyclerViewAdapterHelper+SmartRefreshLayout+ARouter打造的一款快速开发框架, 开发语言是Kotlin,再结合AACHulkTemplate模版开发进行开发, 避免一些繁琐的操作,提供开发效率

功能介绍

1.支持服务器地址、成功码、各种超时时间、各种拦截器、Arouter、EventBus等的配置

2.支持自定义各种非正常态View替换

3.支持接口调用出错时重试

4.支持多种Activity、Fragment展示,满足业务需求

5.支持多布局适配器

6.支持通用代码生成AACHulkTemplate模版

第三方库

  1. Okhttp 一个用于Android、Kotlin和Java的HTTP客户端
  2. Retrofit 为Android和Java提供安全的HTTP客户端
  3. BaseRecyclerViewAdapterHelper 功能强大、灵活的万能适配器
  4. SmartRefreshLayout Android智能下拉刷新框架
  5. EventBus Android和Java的事件总线,简化了活动、片段、线程、服务等之间的通信。代码越少,质量越好
  6. ARouter 帮助 Android App 进行组件化改造的路由框架

基础功能

1.主项目启用dataBinding

    dataBinding {
        enabled true
    }
复制代码

2.添加依赖

在project的build.grade加入

allprojects {
    repositories {
        maven { url 'https://jitpack.io' }
        google()
        jcenter()
    }
}
复制代码

在主项目app的build.grade加入

api 'com.madreain:libhulk:1.0.0'
复制代码

3.继承HulkApplication,配置相关配置项

HulkConfig.builder() //这里只需要选择设置一个
            .setRetSuccess(BuildConfig.CODE_SUCCESS)//单一的成功响应码
            .setRetSuccessList(BuildConfig.CODELIST_SUCCESS)////多种请求对应不同成功响应码
            .setBaseUrl(BuildConfig.BASE_URL)//服务器地址
            .setLogOpen(BuildConfig.OPEN_LOG)//网络日志开关
            .setArouterOpen(BuildConfig.OPEN_AROUTER)//Arouter的开关
            .setEventBusOpen(BuildConfig.OPEN_EVENTBUS)//EventBus的开关
            .addOkHttpInterceptor(RequestHeaderInterceptor()) //请求头拦截器
            .addOkHttpInterceptor(
                BuildConfig.OPEN_LOG,
                HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)
            ) //okhttp请求日志开关+消息拦截器
            .addRetCodeInterceptors(SessionInterceptor()) // returnCode非正常态拦截器
            .setRetrofit(
                ApiClient.instance!!.getRetrofit(
                    ApiClient.instance!!.getOkHttpClient(
                        HulkConfig.getOkHttpInterceptors()
                    )
                )
            )//
            .build()
复制代码

上面这些配置项的配置可参考demo进行自身项目的配置

这里还可根据SmartRefreshLayout相关文档配置统一样式,也可单独设置,也可自定义,根据自身项目选择

4.继承IRes,根据自身项目封装统一的数据接受

5.编写ApiService,放接口

6.编写通用的Toolbar(自行选择) 因受kotlin-android-extensions这个插件可能只管自己module的资源文件的影响,没法将通用的toolbar.xml写在libhulk中供app使用,因此只能在app项目中写通用的toolbar.xml

⚠️ 如果大佬们有好的实现方法欢迎指教

️🔥️🔥️🔥 AACHulkTemplate模版,此模版使用得保证ApiService、toolbar.xml已创建,使用者也可根据自身项目进行修改

快速开发

AACHulkTemplate模版用起来是相当香的,接下来讲一下自已手动的步骤,以SingleActivity举例

1.新建SingleActivity继承BaseActivity

class SingleActivity : BaseActivity<BaseViewModel, ViewDataBinding>() {

    override fun getLayoutId(): Int {
        return R.layout.activity_single
    }

    override fun getReplaceView(): View {
        return layout
    }

    override fun init(savedInstanceState: Bundle?) {

    }

    /**
     * 设置SmartRefreshLayout
     */
    override fun getSmartRefreshLayout(): SmartRefreshLayout? {
        return null
    }

    override fun refreshData() {

    }

}
复制代码

ViewDataBinding将会用在activity_single.xml中关联ActivitySingleBinding替换掉 BaseViewModel将会用新建的SingleViewModel继承BaseViewModel替换掉

2.创建对应的对象

@Keep
class SingleData {
    var code: String? = null
    var name: String? = null
}
复制代码

3.关联ViewDataBing

在activity_single.xml中关联ActivitySingleBinding

<?xml version="1.0" encoding="utf-8"?>
<layout 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">

    <data>

        <import type="java.util.List" />

        <import type="com.madreain.aachulk.module.single.SingleData" />

        <variable
            name="singleDataS"
            type="List&lt;SingleData>" />

        <variable
            name="singleData"
            type="SingleData" />

    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/single_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context="com.madreain.aachulk.module.main.MainActivity">

        <include
            android:id="@+id/tbar"
            layout="@layout/toolbar" />

        <androidx.constraintlayout.widget.ConstraintLayout
            android:id="@+id/layout"
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <TextView
                android:id="@+id/tv"
                android:layout_width="@dimen/dp60"
                android:layout_height="wrap_content"
                android:background="@color/colorPrimary"
                android:text="@{singleData.code,default=`接口调用之前`}"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintLeft_toLeftOf="parent"
                app:layout_constraintRight_toRightOf="parent"
                app:layout_constraintTop_toTopOf="parent"
                tools:text="接口调用结果" />

        </androidx.constraintlayout.widget.ConstraintLayout>


    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>
复制代码

4.新建SingleViewModel继承BaseViewModel

class SingleViewModel : BaseViewModel<ApiService>() {

    public override fun onStart() {
        cityList()
    }

    //这里举例的是相关接口的调用,具体可参考demo
    var result = MutableLiveData<List<SingleData>>()
    private fun cityList() {
        launchOnlyresult(
            //调用接口方法
            block = {
                getApiService().getCityList()
            },
            //重试
            reTry = {
                //调用重试的方法
                cityList()
            },
            //成功
            success = {
                //成功回调
                result.value = it
            }, type = RequestDisplay.REPLACE
        )
    }
}

复制代码

5.替换ViewDataBinding、BaseViewModel ActivitySingleBinding替换掉ViewDataBinding SingleViewModel替换掉BaseViewModel

6.调用接口

        //请求接口
        mViewModel.onStart()
        //接口请求的数据变化
        mViewModel.result.observe(this, Observer {
            mBinding!!.singleDataS = it
            mBinding!!.singleData = it[0]
        })
复制代码

7.ARoute的配置

根据自身项目需求来决定是否配置ARoute来进行路由控制

@Route(path = "/aachulk/ui/SingleActivity")
复制代码

到此为止,简单的一个接口调用到数据展示就完成了

⚠️⚠️⚠️ 带适配器的demo参考ListActivity

用法进阶

1.自定义各种非正常态View替换

以demo中的MyVaryViewHelperController举例,只是修改了showLoading,其他的都可根据自身项目需求进行修改

class MyVaryViewHelperController private constructor(private val helper: VaryViewHelper) :
    IVaryViewHelperController {

    //是否已经调用过restore方法
    private var hasRestore: Boolean = false

    constructor(replaceView: View) : this(VaryViewHelper(replaceView)) {}

    override fun showNetworkError(onClickListener: View.OnClickListener?) {
        showNetworkError("网络状态异常,请刷新重试", onClickListener)
    }

    override fun showNetworkError(
        msg: String?,
        onClickListener: View.OnClickListener?
    ) {
        hasRestore = false
        val layout = helper.inflate(R.layout.hulk_page_error)
        val againBtn =
            layout.findViewById<Button>(R.id.pager_error_loadingAgain)
        val tv_title = layout.findViewById<TextView>(R.id.tv_title)
        tv_title.visibility = View.GONE
        val tv_msg = layout.findViewById<TextView>(R.id.tv_msg)
        tv_msg.text = msg
        if (null != onClickListener) {
            againBtn.setOnClickListener(onClickListener)
        }
        helper.showView(layout)
    }

    override fun showCustomView(
        drawableInt: Int,
        title: String?,
        msg: String?,
        btnText: String?,
        listener: View.OnClickListener?
    ) {
        hasRestore = false
        val layout = helper.inflate(R.layout.hulk_page_error)
        val iv_flag =
            layout.findViewById<ImageView>(R.id.iv_flag)
        val tv_title = layout.findViewById<TextView>(R.id.tv_title)
        val tv_msg = layout.findViewById<TextView>(R.id.tv_msg)
        val againBtn =
            layout.findViewById<Button>(R.id.pager_error_loadingAgain)
        iv_flag.setImageResource(drawableInt)
        if (TextUtils.isEmpty(title)) {
            tv_title.visibility = View.GONE
        } else {
            tv_title.visibility = View.VISIBLE
            tv_title.text = title
        }
        if (TextUtils.isEmpty(msg)) {
            tv_msg.visibility = View.GONE
        } else {
            tv_msg.visibility = View.VISIBLE
            tv_msg.text = msg
        }
        if (TextUtils.isEmpty(btnText)) {
            againBtn.visibility = View.GONE
        } else {
            againBtn.text = btnText
            if (null != listener) {
                againBtn.setOnClickListener(listener)
            }
        }
        helper.showView(layout)
    }

    override fun showEmpty(emptyMsg: String?) {
        hasRestore = false
        val layout = helper.inflate(R.layout.hulk_page_no_data)
        val textView = layout.findViewById<TextView>(R.id.tv_no_data)
        if (!TextUtils.isEmpty(emptyMsg)) {
            textView.text = emptyMsg
        }
        helper.showView(layout)
    }

    override fun showEmpty(
        emptyMsg: String?,
        onClickListener: View.OnClickListener?
    ) {
        hasRestore = false
        val layout = helper.inflate(R.layout.hulk_page_no_data_click)
        val againBtn =
            layout.findViewById<Button>(R.id.pager_error_loadingAgain)
        val textView = layout.findViewById<TextView>(R.id.tv_no_data)
        if (!TextUtils.isEmpty(emptyMsg)) {
            textView.text = emptyMsg
        }
        if (null != onClickListener) {
            againBtn.setOnClickListener(onClickListener)
            //            againBtn.setVisibility(View.VISIBLE);
            againBtn.visibility = View.GONE //按钮都隐藏,空页面没有刷新 2018.9.5
        } else {
            againBtn.visibility = View.GONE
        }
        helper.showView(layout)
    }

    override fun showLoading() {
        hasRestore = false
        val layout = helper.inflate(R.layout.view_page_loading)
        helper.showView(layout)
    }

    override fun showLoading(msg: String?) {
        hasRestore = false
        val layout = helper.inflate(R.layout.view_page_loading)
        val tv_msg = layout.findViewById<TextView>(R.id.tv_msg)
        tv_msg.text = msg
        helper.showView(layout)
    }

    override fun restore() {
        hasRestore = true
        helper.restoreView()
    }

    override val isHasRestore: Boolean
        get() = hasRestore

}
复制代码

2.拦截器

2.1 请求头拦截器

class RequestHeaderInterceptor : Interceptor {
    //统一请求头的封装根据自身项目添加
    @Throws(IOException::class)
    override fun intercept(chain: Interceptor.Chain): Response {
        val request = chain.request()
        val authorised: Request
        val headers = Headers.Builder()
            .add("app_id", "wpkxpsejggapivjf")
            .add("app_secret", "R3FzaHhSSXh4L2tqRzcxWFBmKzBvZz09")
            .build()
        authorised = request.newBuilder().headers(headers).build()
        return chain.proceed(authorised)
    }
}
复制代码

2.2 非正常态响应码拦截器

实际应用:可应用于App中用户的互踢

class SessionInterceptor : IReturnCodeErrorInterceptor {
    //和接口定义互踢的相关参数返回,然后在doWork方法进行跳转
    override fun intercept(returnCode: String?): Boolean {
        return "-100" == returnCode
    }

    override fun doWork(returnCode: String?, msg: String?) {

    }

}
复制代码

🌟🌟🌟 推荐Carson_Ho大佬的Kotlin:这是一份全面 & 详细的 类使用 的语法学习指南

项目地址,如果觉得不错欢迎star,欢迎指教,不断学习,不断进步,你的支持是我前进的动力