基於谷歌最新AAC架構打造的Android MVVM開發框架,使用該框架可以快速開發一個高質量、易維護的Android應用

AACHulk是以Google的ViewModel+DataBinding+LiveData+Lifecycles框架爲基礎, 結合Okhttp+Retrofit+BaseRecyclerViewAdapterHelper+SmartRefreshLayout+ARouter打造的一款快速開發框架, 開發語言是Kotlin,再結合AACHulkTemplate模版開發進行開發, 避免一些繁瑣的操作,提供開發效率

功能介紹

1.支持多服務器地址、多成功碼、各種超時時間、各種攔截器、Arouter等的配置

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. 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.4'

3.繼承HulkApplication,配置相關配置項

    HulkConfig.builder() //這裏只需要選擇設置一個
//            .setRetSuccess(BuildConfig.CODE_SUCCESS)
            .setRetSuccessList(BuildConfig.CODELIST_SUCCESS)
            //設置多baseurl的retcode
            .addRetSuccess(HulkKey.WANANDROID_DOMAIN_NAME, BuildConfig.WANANDROID_CODELIST_SUCCESS)
            .addRetSuccess(HulkKey.GANK_DOMAIN_NAME, BuildConfig.GANK_CODELIST_SUCCESS)
            .setBaseUrl(BuildConfig.BASE_URL)
            //設置多baseurl
            .addDomain(HulkKey.WANANDROID_DOMAIN_NAME, HulkKey.WANANDROID_API)
            .addDomain(HulkKey.GANK_DOMAIN_NAME, HulkKey.GANK_API)
            .setLogOpen(BuildConfig.OPEN_LOG)
            .setArouterOpen(BuildConfig.OPEN_AROUTER)
            .addOkHttpInterceptor(RequestHeaderInterceptor()) //請求頭攔截器
            .addOkHttpInterceptor(
                BuildConfig.OPEN_LOG,
                HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)
            ) //okhttp請求日誌開關+消息攔截器.md
            .addRetCodeInterceptors(SessionInterceptor()) // returnCode非正常態攔截器
            .setRetrofit(
                ApiClient.getInstance().getRetrofit(
                    ApiClient.getInstance().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?) {

    }

}

3.多BaseUrl以及多狀態碼

3.1 設置多BaseUrl

.addDomain(HulkKey.WANANDROID_DOMAIN_NAME, HulkKey.WANANDROID_API)

設置了多BaseUrl,就要設置對應的狀態碼,否則會報未設置狀態碼異常

3.2 設置對應的狀態碼

.addRetSuccess(HulkKey.WANANDROID_DOMAIN_NAME, BuildConfig.WANANDROID_CODELIST_SUCCESS)

3.3 設置調用接口方法的currentDomainName

 fun getWxArticle() {
        launchOnlyresult(
            //調用接口方法
            block = {
                getApiService().getWxArticle()
            },
            //重試
            reTry = {
                //調用重試的方法
                getWxArticle()
            },
            //成功
            success = {
                //成功回調
            },
            currentDomainName = HulkKey.WANANDROID_DOMAIN_NAME,
            type = RequestDisplay.REPLACE
        )
    }

上面這些配置項的配置可參考demo進行自身項目的配置

多BaseUrl的設計思路參考的RetrofitUrlManager的實現方式

4.消息總線

針對大家提出的問題,這裏採用了LiveEventBus(缺點:不支持線程分發)去替換原先的EventBus,去掉了在HulkConfig設置setEventBusOpen的開關設置,大家可根據自身項目去選擇適合自己的消息總線

LiveEventBus 消息總線,基於LiveData,具有生命週期感知能力,支持Sticky,支持AndroidX,支持跨進程,支持跨APP

具體實現方法參考官方文檔

相關資料

想要學習或領取上圖的相關資料,可以私信我【666】獲取資料地址或者直接掃碼加入粉絲扣扣羣

感謝

感謝本框架所使用到的所有三方庫的作者,以及所有爲開源做無私貢獻的開發者和組織,使我們能更好的工作和學習,本人也會將業餘時間回報給開源社區

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章