android轻量级业务开发框架jectpack-Mvvm

前言

jectpack从发布以来,一直备受好评,旗下组件可单独使用,也可以协同工作,当使用kotlin语言特性时,可以提高效率。网上已经有很多关于jectpack的文章,那么这里为什么还有要写呢?主要原因是记录、总结、交流,并且以此搭建一个基于jectpack的mvvm快速开发框架,或者快速开发模块 JectPack官方介绍

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.cn/20200917182304373.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMwNzEwNjE1,size_16,color_FFFFFF,t_70#pic_center)

上文架构介绍中DataBindinglivedataViewModel 为主要使用组件 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内存的库有RxLifecycleAutoDispose 这里使用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的使用这里就不多说了,在开发中我们主要关注ActivityBindingModuleFragmentBindingModule

这个类主要用于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

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值