前言
jectpack从发布以来,一直备受好评,旗下组件可单独使用,也可以协同工作,当使用kotlin语言特性时,可以提高效率。网上已经有很多关于jectpack的文章,那么这里为什么还有要写呢?主要原因是记录、总结、交流,并且以此搭建一个基于jectpack的mvvm快速开发框架,或者快速开发模块 JectPack官方介绍
文章目录
- 前言
- 1.架构搭建
- 1.1架构介绍
- 1.1 JectPack组件
- 1.2 架构图
- 2 .ViewModel
- 3. Activity与Fragment
- 4. RecycleView
- 5.Kotlin 扩展函数工具集
- 6. 实战
- 7. 总结
1.架构搭建
1.1架构介绍
- 基于MVVM模式用了 kotlin+协程+retrofit+livedata+DataBinding+dagger,是基于Androidx 主要封装了BaseActivity、BaseFragment、BaseViewModel、BaseLifeViewModel,基于协程和rxjava网络请方式更加方便,可自由选择。dagger不喜欢用的也可以不用,不设计到基础封装
- 使用Rxjava 处理不好的话会有内存泄露的风险,这里使用AutoDispose,在Androidx中 activity和fragment中可以直接直接使用,但是在viewmodel中不能,所以在BaseLifeViewModel中是处理rxjava的封装
1.1 JectPack组件
![Jetpack的分类](https://img-blog.csdnimg/20200917182304373.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMwNzEwNjE1,size_16,color_FFFFFF,t_70#pic_center)
上文架构介绍中DataBinding
、livedata
、ViewModel
为主要使用组件 room 以及其他根本业务选择使用
1.2 架构图
引用Google的图
2 .ViewModel
在viewModel中我们要做什么事呢?主要的任务就是网络请求和对返回数据进行处理,在baseViewModel 中只有协程的请求体的封装,在这当中不会有状态的展示StateView,StateView在继承baseViewModel的ViewModel中处理,当然如果说是直接引入代码,可直接在Base中处理
在viewModel中有一个管理协程生命周期的一个类叫做viewModelScope,viewModelScope会减少大量的模块代码,在viewModel的clear()方法中会自动清理取消协程,我们只需要直接引用viewModelScope
private fun launchUi(block: suspend CoroutineScope.() -> Unit) =
viewModelScope.launch { block() }
我们建立起一个网络请求的请求体名为async() 这个方法中我们将传入,网络请求service 以及成功失败的回调接口
fun <T> async(
request: suspend CoroutineScope.() -> T,
success: (T) -> Unit,
error: suspend CoroutineScope.(BaseResponseThrowable) -> Unit,
complete: suspend CoroutineScope.() -> Unit = {}
) {
launchUi {
//这里处理网络请求
}
}
当传入网络请求service之后需要一个实际请求网络的载体 将载体的方法名为handleRequest()
private suspend fun <T> handleRequest(
block: suspend CoroutineScope.() -> T,
success: suspend CoroutineScope.(T) -> Unit,
error: suspend CoroutineScope.(BaseResponseThrowable) -> Unit,
complete: suspend CoroutineScope.() -> Unit
) {
coroutineScope {
try {
success(block())
} catch (e: Throwable) {
error(ThrowableHandler.handleThrowable(e))
} finally {
complete()
}
}
}
这个时候我们只要在业务层的viewModel中调用async()方法就可以处理网络请求
//这是一个网络请求方法
fun getNews(type: String) {
async({ repository.getNews(type) }
, {
itemNews.clear()
itemNews.addAll(it.list)
}, {
it.printStackTrace()
}, {
})
}
现在看来网络请求是不是显得一样的简洁。如果在返回数据中是以BaseResponse<T>
这种方式做为接受数据,那么增加一个请求数据的过滤
//请求数据过滤
private suspend fun <T> executeResponse(
response: BaseResponse<T>,
success: suspend CoroutineScope.(T) -> Unit
) {
coroutineScope {
if (response.isSuccess()) success(response.data())
else throw BaseResponseThrowable(response.code(), response.msg())
}
}
同时我们可以增加请求时loading状态的控制,这里就不具体阐述,可在代码中查看,代码均有注释 除开协程,rxjava是我们最常用的请求方式,而在rxjava中主要注意的就是内存泄漏问题,现有比较有名的管理rxjava内存的库有RxLifecycle和AutoDispose 这里使用AutoDispose管理在0.8.0版本之后是针对Androidx的 如果不是androidx 要用之前的版本。在activity和fragment中可以直接使用,在Androidx中activity和fragment本身是实现了lifecycle的
Observable.interval(1, TimeUnit.SECONDS)
.doOnDispose {
Log.i(TAG, "Disposing subscription from onResume() with untilEvent ON_DESTROY")
}
.autoDisposable(AndroidLifecycleScopeProvider.from(this, Lifecycle.Event.ON_DESTROY))//OnDestory时自动解绑
.subscribeBy { num -> Log.i(TAG, "Started in onResume(), running until in onDestroy(): $num") }
但是viewModel中并不能这么引用,viewmodel的生命周期和activity的生命周期是有区别的,这种情况下我们应该怎么使用呢?总不能在activity中传入lifecycle到viewmodel中吧?这个时候我们就需要实现LifecycleScopeProvider
了
open class BaseLifeViewModel (application: Application) : AndroidViewModel(application),
LifecycleScopeProvider<ViewEvent> {
private val lifecycleEvents = BehaviorSubject.createDefault(ViewEvent.CREATED)
override fun lifecycle(): Observable<ViewEvent> {
return lifecycleEvents.hide()
}
override fun correspondingEvents(): CorrespondingEventsFunction<ViewEvent> {
return CORRESPONDING_EVENTS
}
/**
* Emit the [ViewModelEvent.CLEARED] event to
* dispose off any subscriptions in the ViewModel.
* 在nCleared() 中进行解绑
*/
override fun onCleared() {
lifecycleEvents.onNext(ViewEvent.DESTROY)
super.onCleared()
}
override fun peekLifecycle(): ViewEvent {
return lifecycleEvents.value as ViewEvent
}
companion object {
var CORRESPONDING_EVENTS: CorrespondingEventsFunction<ViewEvent> = CorrespondingEventsFunction { event ->
when (event) {
ViewEvent.CREATED -> ViewEvent.DESTROY
else -> throw LifecycleEndedException(
"Cannot bind to ViewModel lifecycle after onCleared.")
}
}
}
fun <T> auto(provider: ScopeProvider): AutoDisposeConverter<T> {
return AutoDispose.autoDisposable(provider)
}
}
在baseviewModel中继承BaseLifeViewModel,在业务viewModel中使用
fun getRxNews(type: String) {
repository.getRxNews(type)
.`as`(auto(this))
.subscribes({
//请求结果
},{
//返回异常
})
为了使用方便使用以及自定义异常,利用扩展函数将AutoDisposeConverter增加了一个使用函数subscribes
fun <T> SingleSubscribeProxy<T>.subscribes(onSuccess: (T) -> Unit,
onError: (BaseResponseThrowable)->Unit) {
ObjectHelper.requireNonNull(onSuccess, "onSuccess is null")
ObjectHelper.requireNonNull(onError, "onError is null")
val observer: RequestObserver<T> = RequestObserver(onSuccess, onError)
subscribe(observer)
}
具体的可见代码 [TOC]
3. Activity与Fragment
baseActivity和fragment里面的内容很简单只有一个toolbar的设置以及dagger的注入
abstract class CommonBaseActivity<VB: ViewDataBinding>:AppCompatActivity(){
lateinit var binding: VB
override fun onCreate(savedInstanceState: Bundle?) {
AndroidInjection.inject(this)
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView<VB>(this, getLayout())
initView()
}
@LayoutRes
protected abstract fun getLayout(): Int
protected abstract fun initView()
//设置toolbar
fun setSupportToolBar(toolBar: Toolbar) {
setSupportActionBar(toolBar)
val actionBar = supportActionBar
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true)
actionBar.setDisplayShowHomeEnabled(true)
actionBar.setHomeButtonEnabled(true)
}
}
fun setTitle(title: String) {
Objects.requireNonNull<ActionBar>(supportActionBar).setTitle(title)
}
override fun setTitle(title: Int) {
Objects.requireNonNull<ActionBar>(supportActionBar).setTitle(getString(title))
}
override fun onSupportNavigateUp(): Boolean {
onBackPressed()
return true
}
}
dagger的使用这里就不多说了,在开发中我们主要关注ActivityBindingModule
和FragmentBindingModule
这个类主要用于activity和fragment的注入,详细可看代码
@Module
abstract class ActivityBindingModule {
@ActivityScoped
@ContributesAndroidInjector
abstract fun mainActivity(): MainActivity
@FragmentScoped
@ContributesAndroidInjector
abstract fun newFragment(): NewFragment
}
AppModule
类中主要做网络api的初始化操作
@Module
public abstract class AppModule {
@Provides
@Singleton
static Retrofit providerRetrofit() {
return Net.INSTANCE.getRetrofit(UriConfig.INSTANCE.getBASE_URL(),6000L);
}
@Provides
@Singleton
static BaseApiService providerBaseApi() {
return providerRetrofit().create(BaseApiService.class);
}
}
object Net {
private var retrofit: Retrofit? = null
private var okHttpClient: OkHttpClient? = null
private var timeOut = 6000L
fun getRetrofit(baseUrl: String, time: Long = 6000L): Retrofit {
timeOut = time
if (null == retrofit) {
if (null == okHttpClient) {
okHttpClient = getOkHttpClient()
}
//Retrofit2后使用build设计模式
retrofit = Retrofit.Builder()
//设置服务器路径
.baseUrl("$baseUrl/")
//添加转化库,默认是Gson DecodeConverterFactory DecodeConverterFactory
// .addConverterFactory(DecodeConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
//添加回调库,采用RxJava
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
//设置使用okhttp网络请求
.client(okHttpClient!!)
.build()
return retrofit!!
}
return retrofit!!
}
private fun getOkHttpClient(): OkHttpClient {
val loggingInterceptor = HttpLoggingInterceptor()
if (LogUtils.isDebug) {
loggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
}
val headerInterceptor = Interceptor { chain ->
val builder = chain.request().newBuilder()
//请求头携token
builder.addHeader("Authorization", "")
chain.proceed(builder.build())
}
return OkHttpClient.Builder()
.connectTimeout(timeOut, TimeUnit.SECONDS)
.addInterceptor(loggingInterceptor)
.addInterceptor(headerInterceptor)
.writeTimeout(timeOut, TimeUnit.SECONDS)
.readTimeout(timeOut, TimeUnit.SECONDS)
.build()
}
}
4. RecycleView
在项目中用刀的列表最多的应该就是RecycleView了,针对RecycleView的封装的开源库已经有很多了,可满足各个场景的取消,这里针对RecycleView做简单封装,达到方便使用的效果以及适用于大多数普遍场景的需要,
abstract class BaseRecyclerViewAdapter<T,Vb:ViewDataBinding>(
//这里使用ObservableList,在init代码块中ListChangedCallback的方法一一对应,这样的话可以充分利用RecycleView的特性,单个数据改变的刷新
var itemData: ObservableList<T>,
var layoutId: Int,
var dataId: Int
) : RecyclerView.Adapter<BaseDataBingViewHolder<Vb>>() {
private lateinit var bing:Vb
override fun onCreateViewHolder(viewGroup: ViewGroup, i: Int): BaseDataBingViewHolder<Vb> {
bing = DataBindingUtil.inflate<Vb>(
LayoutInflater.from(viewGroup.context),
layoutId,
viewGroup,
false
)
return BaseDataBingViewHolder(bing)
}
override fun onBindViewHolder(viewHolder: BaseDataBingViewHolder<Vb>, i: Int) {
viewHolder.binding.setVariable(dataId,itemData[i])
bindViewHolder(viewHolder,i,itemData[i])
}
protected open fun bindViewHolder(
@NonNull viewHolder: BaseDataBingViewHolder<Vb>,
position: Int,
t: T
) {
}
override fun getItemCount(): Int {
return if (itemData == null) 0 else itemData.size
}
fun getItemLayout(itemData: T): Int {
return layoutId
}
fun onSetItem(newItemData: ObservableList<T>) {
itemData = newItemData
notifyDataSetChanged()
}
init {
itemData.addOnListChangedCallback(object : ObservableList.OnListChangedCallback<ObservableList<T>>() {
override fun onChanged(observableList: ObservableList<T>) {
notifyDataSetChanged()
}
override fun onItemRangeChanged(
observableList: ObservableList<T>,
i: Int,
i1: Int
) {
notifyItemRangeChanged(i, i1)
}
override fun onItemRangeInserted(
observableList: ObservableList<T>,
i: Int,
i1: Int
) {
notifyItemRangeInserted(i, i1)
}
override fun onItemRangeMoved(
observableList: ObservableList<T>,
i: Int,
i1: Int,
i2: Int
) {
if (i2 == 1) {
notifyItemMoved(i, i1)
} else {
notifyDataSetChanged()
}
}
override fun onItemRangeRemoved(
observableList: ObservableList<T>,
i: Int,
i1: Int
) {
notifyItemRangeRemoved(i, i1)
}
})
}
}
public class BaseDataBingViewHolder<VB extends ViewDataBinding> extends RecyclerView.ViewHolder {
public VB binding;
public BaseDataBingViewHolder(VB binding) {
super(binding.getRoot());
this.binding = binding;
}
}
用法
public class NewAdapter extends BaseRecyclerViewAdapter<NewResponses.T1348647853363Bean,ItemNewBinding> {
public NewAdapter(@NotNull ObservableList<NewResponses.T1348647853363Bean> itemData, int layoutId, int brId) {
super(itemData, layoutId, brId);
}
@Override
protected void bindViewHolder(@NonNull @NotNull BaseDataBingViewHolder<ItemNewBinding> viewHolder, int position, NewResponses.T1348647853363Bean t1348647853363Bean) {
super.bindViewHolder(viewHolder, position, t1348647853363Bean);
viewHolder.binding.title.setText(getItemData().get(position).getTitle());
viewHolder.binding.source.setText(getItemData().get(position).getSource());
GlideApp.loadImage(getItemData().get(position).getImgsrc(), viewHolder.binding.image);
}
}
在activity或者fragment中
private val adapter by lazy {
NewAdapter(viewModel.itemNews, R.layout.item_new, 0)
}
在使用中为了更加方便,利用dataBinding来自定以xml属性,这里就要用到@BindingAdapter
@BindingAdapter({"itmes"})
public static <T> void addItem(RecyclerView recyclerView, ObservableList<T> it) {
BaseRecyclerViewAdapter adapter = (BaseRecyclerViewAdapter) recyclerView.getAdapter();
if (adapter != null) {
adapter.onSetItem(it);
}
}
在xml中
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:itmes="@{viewModel.itemNews}"
android:id="@+id/recycle_view"/>
5.Kotlin 扩展函数工具集
用Kotlin开发 必定不能缺少的就是扩展函数,api的扩展会极大的方便开发,相信很小伙伴已经体会过了,对于扩展函数简单说哈作用,扩展函数就是将对象自定义一系列对象本身不具备的方法或者api对外使用,比如Toast提示在四大组件中我们使用toast提示是用Toast.makeText(context.getApplicationContext(), msg, 0);
获取自定义的ToastUtils,那么如何将activity以及fragment中扩展呢,直接上代码
fun Activity.toast(msg: String?) {
Toast.makeText(context.getApplicationContext(), msg, 0);
}
在activity中我们就可以直接this.toast()
或者taost()
来调用 当然还有TextView设置drawableLeft 我们也可以写成扩展函数
fun TextView.setImageLeft(imageId: Int) {
val drawable =
resources.getDrawable(imageId)
drawable.setBounds(0, 0, drawable.minimumWidth, drawable.minimumHeight)
setCompoundDrawables(drawable, null, null, null)
}
调用的时候 textview.setImageLeft(R.mipmap.ic_back_close)
这样看起来不是就像是textview自带这个方法设置,又比如我们在EditText中如何没有任何输入我们将button禁止点击
fun EditText.watcher(textView: TextView) {
this.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(p0: Editable?) {
}
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
}
override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
textView.isEnabled = p0?.length != 0
}
})
}
调用的时候 edittext.watcher(textview)
这样是不是很方便呢?看起来就像是系统api,当然我们还可以自定以业务相关的api,更多的使用可以查看Base.kt
这个类
到这里已经将ui层以及viewModel 和网络api有个基本的封装了。接下来我们模拟业务进行网络api请求然后到ui显示
6. 实战
这里以网易的新闻api作为接口 首先在apiservice中申明请求
interface BaseApiService {
@GET("/nc/article/headline/{id}/0-10.html")
suspend fun getNews(@Path("id") id : String?): NewResponses
@GET("/nc/article/headline/{id}/0-10.html")
fun getRxNews(@Path("id") id : String?): Single<NewResponses>
}
接下来就是Repository
class UserRepository @Inject internal constructor(private val apiService: BaseApiService) {
/**
* 协程请求
*/
suspend fun getNews(type: String): NewResponses = apiService.getNews(type)
/**
*rxjava 请求
*/
fun getRxNews(type: String)=apiService.getRxNews(type).async()
}
然后到viewModel
class NewViewModel @Inject constructor(application: Application) : BaseViewModel(application) {
@Inject
lateinit var repository: UserRepository
var itemNews: ObservableList<NewResponses.T1348647853363Bean> = ObservableArrayList()
//直接获取结果的
fun getNews(type: String) {
async({ repository.getNews(type) }
, {
itemNews.clear()
itemNews.addAll(it.list)
}, {
it.printStackTrace()
}, {
})
}
//带loading的
fun getNewsLoading() {
async({ repository.getNews("") }
, {
//返回结果
}
, true, {}, {})
}
fun getRxNews(type: String) {
repository.getRxNews(type)
.`as`(auto(this))
.subscribes({
},{
})
}
在fragment中
@FragmentScoped
class NewFragment : CommonBaseFragment<FragmentNewBinding>() {
@Inject
lateinit var viewModel: NewViewModel
private val adapter by lazy {
NewAdapter(viewModel.itemNews, R.layout.item_new, 0)
}
fun newInstance(type: String): NewFragment {
val args = Bundle()
args.putString("type", type)
val fragment = NewFragment()
fragment.arguments = args
return fragment
}
override fun getLayout(): Int {
return R.layout.fragment_new
}
override fun initView() {
val type = arguments!!.getString("type", "")
viewModel.getNews(type)
binding.viewModel = viewModel
binding.recycleView.layoutManager = LinearLayoutManager(activity)
binding.recycleView.adapter = adapter
}
override fun loadData() {
}
}
到此一个业务请求完结
7. 总结
到了这里文章基本上算完了,至于jetpack中的其他组件如room等,根据项目实际业务引入。文章粗略的介绍了搭建过程,如果你觉得对你有帮助可下载源码看看,如果你觉得不足以及错误之处,欢迎留言指出,这个开发框架的搭建是一个很轻量级的,其本质也是搭建一个轻量级的,Android发展到现在,出现很模式 mvc、mvp、mvvm等,可根据实际需求选择,没必要钟情于某一个模式,毕竟没有更好的,只有更适合的。后期会上传java版本,以及组件化开发的版本
github demo
更多推荐
android轻量级业务开发框架jectpack-Mvvm
发布评论