admin管理员组文章数量:1794759
android轻量级业务开发框架jectpack
前言
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. 总结
- 基于MVVM模式用了 kotlin+协程+retrofit+livedata+DataBinding+dagger,是基于androidx 主要封装了BaseActivity、BaseFragment、BaseViewModel、BaseLifeViewModel,基于协程和rxjava网络请方式更加方便,可自由选择。dagger不喜欢用的也可以不用,不设计到基础封装
- 使用Rxjava 处理不好的话会有内存泄露的风险,这里使用AutoDispose,在Androidx中 activity和fragment中可以直接直接使用,但是在viewmodel中不能,所以在BaseLifeViewModel中是处理rxjava的封装
上文架构介绍中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与FragmentbaseActivity和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 内容由林淑君副主任自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.xiehuijuan.com/baike/1686825688a107384.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论