AACHulk


AACHulk是以Google的ViewModel+DataBinding+LiveData+Lifecycles框架为基础,
结合Okhttp+Retrofit+BaseRecyclerViewAdapterHelper+SmartRefreshLayoua b X Vt+ARouter打造的一款快速开8 ` 0 s q = F发框架,
开发语言是Kotlin,再结合AACHulkTemplate模版开发进行开发 @ ~ ( p @ 4 d
避免一些繁琐的操作,供) 0 – _ N Y Y #给开发功率

Hex.pm

功用介绍

1.支撑多服务器地址、多成功码、各种超时时刻、各种拦截器、Au b X 9 E = Z &router等的装备

2.支撑自界说各种非正常态View替换

3.支撑接口调用出错` t 7 $ k N J b *时重I M r Z L P

4.支撑多种Activity、– 0 O X v 5 z 4 SFragment展现,满足业务需求

5.y G ) k t 0 m ?支撑多布局适配器

6.支撑通用代码生成AAa q 2 JCHulkTemplate模版

第三方库

  1. Okhttp 一个用于Android、Kotlin和Java的HTTP客户端
  2. Retrofit 为Android和Java供给安全的HTTP客户端
  3. BaseRecyclerViewAdapterHelper 功用强大、灵活的全能适配器
  4. SmartRefreshLayout Android智能下拉A G j改写框架
  5. ARouter 协助 Android App 进行组件化改造z T E # & z [ –J R ] & , ^ p x路由框架f L ^ { | : ?

基础功用

1.主项目启用dataBinding

    dataBinding {
enabled true
}

2.添加依赖

在project的build.grade加入

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

在主项目/ o m ; a R Q U aapp的build.grade加入

api 'com.madrK Q * Neain:libhulk:1.0.4'

3.承继HulkApplication,装备相关装备项

    HulkConfig.builder() //这儿只需要挑选设置一个5 t # c H (
//            .setRetSuccess(BuildConfig.COD@ j @ J % Y R (E_SUCCESS)
.setRetSuccessList(BuildConfig.CODELIST_SUCCESS)
//设置多baseurl的retcode
.addRetSuccess(HulkKey.WANAND7 b o j ] 9 BROID_DOMAIN_NAME, BuildConfig.WANANDROID_CODELIST_SUCCESS)
.addRetSuccess(HulkKey.GANK_DOMAIN_NAME, BuildConfig.GANK_CODELIST_SUCCESS)
.setBaseUrl(BuildConfig.BASE_U( / NRL)
//设置多baseurl
.addDomain(Hm N $ dulkKey.WANANDROID_DOMAIN_NAME, HulkKey.WANAN^ 5 %DROID_API)
.~ M @ U : k u |addDomain(HulkKey.GANK_DOMAIN_NAME, HulkKey.GANK_API)
.set3 W V X f ( ; V HLogOpen(BuildConfig.OPEN_LOG)
.setArouterOpen(BuildConfig.OPEN! x 7 x 9 l ^ j_AROUTER)
.addOkHl b w d 0 - - : #ttpInterceptor(RequestHeaderInterceptor()) //恳求头拦截器
.addOkHttpInterceptor(
BuildConfig.OPEN_LOG,
HttpLoggingInterceptor().setLevel(HttpG + C a LLoggingInterceptor.Level.BODY)
) //okhttp恳求日志开关+音讯拦截器.md
.addRetCodeInterceptors(SessiN # i w [ Y JonInterceptor()) // returnCode非正常态拦截器
.setRetrofit(
ApiClient.getInsta( I g  ? 5 i Ynce().getRetrofit(
ApiClient.getInstance().getOkHttpClient(
Ho # : dulkConfig.getOkHttpInterceptors()
)
)
)
.build()

上面这些装备项的装备可参阅demo进行本身项目的装备

这儿还可依据SmartRefreshLayout相关文档装备一致款式,也可独自设置,也可P S )自界说,依据本身项目挑选

4.承继IRes,依据本身项目封装一致的数据承受

5.编写ApiService,放接口

6.编] 3 :写通用的Toolbar(自行挑选)
因受r , ikotlin-an& + Q ; d u L Udroid-eJ 5 Uxtensions这个插件可能只管自己mo9 z Odule的资源文件的影响,无法将通用的toolbar.xml写在libh G l % Z | T =ulk中供app运用,因此只能在app 1 p项目中写通用的toolbar.xml

⚠️ 如果大佬们有好的完成办法欢c 0 g M a 迎指导

️️️ AACHuT { }lkTemplab , =te模版,此模版运用得确保ApiService、toolbar.xml已创立,运用者也可依据本身项目进行修正

快速开发

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

1.新建SingleActivity承继BaseActivity

class S= m g x 7 qingleActivity : BaseActivity<BaseViewModel, ViewDataBinding>() {
override fun getLayoutId(): Int {
return R.layout.activity_single
}
override fI M E O *un getReplaceView(): VL H  b &iew {
return layout
}
override fun init(savedInstanceState: Bundle?) {
}
/**
* 设置SmartRefreshLayout
*/
override fun getSmartRefreshLayout(): SmartRefreshLayout? {
return null
}
override fun refreshData() {_ 4 F 1 r h e h
}
}

Vin p ]ewDataBinding将会用在activity_single.xml中关联ActivitySingleBinding8 v & [ J替换掉
Based 7 FViewModel将会用新建的SingleViewModel承继BaseViewModel替换掉

2.创立对应的对象

@Keep
class SingleData {
var code: String? = null
var name: String? = null
}

3.关联ViewDataBing

在activity_single.xml中关联ActivitySinv 9 1 + ` o F 4gleBinding

<?xml version="1.0" encoding="utf-8"?>
<lal K ) S Y Vyout xmlns:aX ? = Und+ i . ~ C troid="http:/@ , ~ 6 ^ | k I/schemas7 5 7 2 ) v n N.android.com/apk/res/android"
xmlns:app="http://scn K { } T ohemas.android.com/! 5 f C & Y Kapk/res-auL ] % v K s sto"
xmlns:tools="http://schemas.androidH = r p i J 9  L.com/tools">
<data>
<import type="java.util.List" />
<import type=l Y U A "com.madreain.aachulk.mod~ O K & Z 0 d E Iule.single.SingleData" />
<variable
name="singleDataS"
type="List&lt;SingleData>" />
<variable
name="singleData"
type="SingleData+ n S 7 4 ! N I" />
</data>
<andro+ q r | j 8 Didx.constraintla} _ 9 : F Z 1 X fyout.widget.ConstraintLayout
android:id="@+id/single_layout"
android:layouR R U F ` +t_width="match_parentr # 1 % J T p"
android:P s @ } ylayout_height="match_parent"
tools:context="com.madreain.aachulk.module.mB z ; n B 4ain.MainActivity">
<inO x p j L ] O 0 (clude
androii r f ?d:id="@+id/tbar"
layout="@layout/toolbar" />
<androidE 3 W H nx.con_ k Q s )straintlayout.widget.ConstrainX T p / + c )tLayout
androids Z |  ~ y ^ 5 9:id="@+id/layout"
android:layout_width="match_parenty j : F"
android:layout_height="m! r I G +atch_parent">* J V . T s ,  V;
<TextView
android:id="@_ _ u R } i b 6+id/tv") Q Q
android:layout_width="@dimen/dp60"
android:layout_height="wrap_content"
android:background="@color/colorPrimary"
androiq & 5 k ) Xd:text="@{singleDat* b C aa.code,default=`接口调用之前`}"
app:layout_c5 H : } { 5onstraintBottom_toBottomOf="parent"
app:layout_constraiR M QntLeft_toLeftOf="parent"
app:layout_constr+ j 3 @ tain@ F S K N ] , qtRight_toRightOf="parX U g 4ent"
app:layout_constraiu 5 GntTop_X B ; 8 ItoTopOf="parent"
tools:text="接口调用成果" />
</$ 3 x K . [ a O Kandroidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widg- B L p + ? -et.ConstraintLayout>
<w A I P t Z m y;/layout&gH ( v S it;

4.新建SingleViewModel承继BaseViewModel

class S/ ? ^ t dingleViewModel : BaseViewModel<ApiSD = ; d + O T +ervice>() {
public override fun onStart() {
cityList()
}h o y   j i j
//这儿举例的是相关接口的调用,详细可参阅demo
var result = MutableLiveDatl G $ )  a X b Pa<List<SingleData>>()
private fun cityList() {
launchOnlyrd P W o Eesult(
//调用接口办法
block = {
getApiService().getK V N w : :CityList()
},
/9 e S/重试
reTry = {
//调用重试的办法
cityList()
},
//成功
success = {
//成功回调
result.value = it
}, type = RequestDisplay.REPLACE
)
}
}

5.替换ViewDataBinding、BaseViewModel
ActivitySingleBinding替换掉View{ x w fDataBinding
SingleViewModel替换掉BaseViewMo= T V g ` . bdel

6.调用接口

        //恳求接口
mViewModel.onStart()
//接口$ ~ P , 7 ; `恳求的数据改变
mViewModel.result.observe(this, Observer {
mBinding!!.single, 7 [ ` ) ; d ( %DataS = it
mBin5 s 0 Q # & w ~ding!!.singleData = it[0]
})

7.ARoute的装备

依据本身项目需求来决定是否装备ARoute来进行路由控制

@Route(path = "/aachulk/ui/Sn Z ` !ingleActivity")a V ) &

到此为止,简略的一个接口调用到_ D G数据展现就完成了

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

用法进阶

1.自界说各种非正常态View替换

以demo中的MyVaryViewHelperController举例,仅仅修O z ( 0 .正了showLoading,其他的都可依据本身项目需求进行修p , P 8 | :

class MyVaryVieh [ 1wHelpW { [erController private const1 ` l 3 oructor(private val helper: VaryViewHelper) :
IVaryViewHelperController {
//是否已经调用过restore办法
private var hasRestore: Boolean = falseQ $ $ U ? r = c
constructor(replaceView: View) : this(VaryViewHelper(replaceView)) {}
override fun showNetworkError(onClickListe4 & W 8 N gner: View.OnClickListener?) {
showNetworkError("网络状况异常,请改写重试", onClickL/ 2 = c fistener)
}
override fun showNetworkError(
msg: String?,
onClickListener: View.OnClickListener?
) {
hasRestore = false
val layout = helpeq - t 2 n G ^r.inflate(R.laG x P 6 =yout.hulk_page_error)s 6 X l ( 8 e  {
val againBtn =
layout.findViewById<Button>^ V c A O % M % m(R.id.pager_error_loadingAgain)
val tv_title = layout.findViewByD % K { x 3 X K ,Id<TextView>(R.id.tv_title)
tv_title.visibility = View.GONE
val tv_msg = layout.findViewById<TextView>; ( ; ^ _ @  d;(R.id.tv_msg)
tv_m0 t F 9 Csg.text = msg
if (null != onClickLc ( w i X 3 , ] distener) {
againBtn.setOnClickb P E D t v D d cListen` 6 + /er(onClickListener)
}
helper.showV!  Iiew(layout)
}
override fun showCus3 t q ? n ^ k ) utomView(
drawableInt: Int,
title: String?,
msg: String?,
btnText: String?,
listener: View.OnClickListener?; + . Y W
) {
hasRestore = false
val layout = helper.inflate(R.layout.hulk_page_error)
val iv_flag =
layo] , %ut.fc d V @ . 9 # a `indViewBY v v X yId<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_loadi* n PngAgain)
iv_flag.setImageResource(drawablem 7 GInt)
if (TextUtils.isEmp& B { p L L *ty(title)) {
tv_title.visibility = Viewa ! + t  T u.GONE
} else {
tv_title.visibility = View.VISIBLE
tv_title.text = title
}
if (Texp i = b # tUtils.isEmpty(msg)) {
tv_msg.visibility = View.GONE
} else {
tv_msg.visibilityd ( k = 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 layouk V l Y u p # ] tt = helper.i` N  ] + m # $ Vnflate(R.layout.hu5 F r r A c ) _lk_page_no_data)
val textVie3 & F `w = layout.M % D e E M afindViewById<TextView>(RJ O q I  l V N =.id.tv_no_data)
if (!TextUtils.isEmpty(emptyMsg)) {
textView.text = empty@ 3 6 E ! wMsg
}
helper.showView(layout)
}
override fun showEmpty(
emptyMsg: Sm I v ( 5tring?,( N E I +
onClickListener: View.OnClickListener?
) {
hasRestore = false
val layout = helper.inflate(R.layout.hulk_pagA A : 2 D a ~e_no_data_clM Y Y $ Z O Nick)
val againBtn =
layout.findViewById<Button>(R.id.pager_error_loadingAgain)
val textVief y ! . Z K ^w = layout.findViewById<TextView>(R.id.tv_no_data)
if (!Text5 3 3 l q JUtils.isEmpty(emptyMsg)) {
textView.text = emptyMsg
}
if (null != onClickListener) {
againBtn.T 8 F % C g gsetOnClickListener(onClickListener)
//            againBtn.setVisibility(View.VISIBLE);
againBtn.visibilp R W pity = View.GONE //按钮都隐藏,空页面没有改写 2018.9.5
} else {
againBtn.visibility = View.GONE
}
helper.showView(layow c ` ` ` % n O yut)
}
override fun showLoading() {
hasRestore = false
val layout = helper.inflate(Rt 2 i.layout.view_page_loading)
helper.showView(layout)
}
override fun shod g m GwLoading(msg: String?) {
hasRestored # * 5 $ Z | = false
val layout = helper.inflat9 d p O $ m , oe(R.layout.view_page_loading)
val tv_msg = layout.findViewById<TextView>(R.id.tv_msg)
tv_msg.text = msg
helper.showView(layout)
}
override fun restoreS @ W a P() {
hasRestore = tH j G j 0rue
helper.restoreView()
}
overriw N 1de val isHasResto* ; k % ~ _ =re: Boolean
get() = hasRestore
}

2.拦截器

2.1 恳求头拦截器

class RequestHeaderIntercepto6 K i t c p 5 / qr : Interceptor {
//一致恳求头的封装依据本身项目添加
@Throws(IOException::clb $ 1 Tass)
override fun intercept(chain: Interceptor.C0 { m 2 lhain): Response {
val reques: X V N G @ o Nt = chain.request()
valK Z ? [ z O  authorised: Request
val headers = Headers.Builder()
.add("app_idv J d v + q T :", "wpkxpsejggapivjf")
.add("ak G u w ~ p _ P Npp_O q n R Bsecret", "R3FzaHhSSXh4L2tqRzcxWFBmKzBvZz09")
.build()
authorised = request.newBuilder().headers(headers).build()
return chain.proceed( ` Oauthorised)
}
}

2.2 非正常态响应码拦截器

实践运用:可运用于App中用户的互踢

class SessionInterceptor : IReturnCo/ | 2 M 1 v LdeErrorInterceptor {
//和接口界说互踢的相关参数返回,然后在doWork办法进行跳转
o) / D % i Fverride fun intercept(returnCode: Strin5 n L ( 6 ; I Lg?): Boolean {
return "-100" == returnCode
}
overrit Z F :de fun doWork(retus U % N + : srnCode: String?, msg: String?) {
}
}

3.多BaseUrl以及多状况码

3.1 设置多N $ BBaseUrl

.addDomain(HulkKey.WANANDROID_DOMAIN_NAME, HulkKey.WANANDROID_API)

设置了多BaseUrl,就要设置对C ) $应的状况码,否则会报未$ v k b p c [设置状A y % b 5 r d况码异常m ? f C S d

3.2 设置对应的状况码

.addRetSuccess(HulkKey.WANANDROID_DOMAV ( 9IN_Ne A N  P ? |AME, BuildConfig.WANANDROID_CODELIST_SUC7 . 1 D ] xCESS)

3.3 设置调用接口办法的cu{ z = a b D xrrentDomainNamev @ 5 Y Q 9 r –

 fun getWx % - ! m ) AxArticle() {f 3 n c + W 4 5 U
launcZ 4 w  ! D |hOnlyresult(
//调用接口办法
block = {
getApiService().getWxArticle()
},
//重试
reTry = {
//调用重试的办法
getWxArticle()
},
//成功
success = {
//成功回调
},
currentDomainName = HulkKey.WANANDROID_DOMAIN_NAME,
type = Reques! 6 6 ) 5 w 7 @tDisplay.REPLACE
)
}

上面这些装备项的装备可参阅demo进X # k ? f P A q n行本身项目的装备

多BaseUrl的设计思路参阅的RetrofitUrlManager2 } } V U l [的完成办法

4.音讯总线

针对大家提出的问题,这儿采用了LiveEventBus(缺陷:不支撑线程分发)去替换原先的EventBus,去掉了在HulkConfig设置setEventBusOpen的开关设置,大家可依据本# 0 SA A | m L项目去挑选适合自己的音讯总线

LiveEventBus 音讯总线,基于LiveData,具有生命周期感知能力,支撑Sticky,支撑Andv ? & i jroidX,支撑跨进程,支撑跨APP

详细完成办法参阅官方文档

相关材料

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

感谢

2 ] 谢本框架所运用到的一切三方库的作者,以及一切为开源做无私奉献的开发者和b N J安排,使咱们能更好的工作和学习,本人也会将业余时刻回报给开源社区

关于我

  • EmM + % o `ail: madreain6@gmail.com
  • 掘金: juejin.im/user/57ff05…

License

   Copyright [2020] [madreain]
Licensed under the Apache License, Version 2.0 (thF m S ( Ze "License");
you may no- | A : ) L 0 _ dt use this file except in compliance with the License.
You may obtain a copy of the Licens! m : = , 4 u b e at
http% Y Q K x o G://www.apache.org/] N B = k + 7licenses/LICENSE-2.0
Unless required by applicable law or1 ) % # R agreed to in writing, software
disa * Btributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONa j S H v ODITIONS OF ANY KIND, either express or impli4 7 L = a x [ ` 1ed.
See the License for the specific language governing permissions and
lW ! 7 V D R ii& +  Zmitations under the License.