Android 框架

背景

我们有一个需求:我们需要查询用户账号信息,用户输入账号,点击按钮可进行查询账号信息,如果查询数据成功,则将数据展示在界面上;如果查询数据失败,则在界面上提升获取数据失败。
假如说我们不去使用框架来实现这个需求会是什么样子的呢?
无框架
布局文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <EditText
        android:id="@+id/userName"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        android:hint="请输入用户名"
        android:textSize="14sp"/>

    <Button
        android:id="@+id/submit"
        android:layout_margin="30dp"
        android:text="提交"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <TextView
        android:id="@+id/result"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        android:textSize="14sp"/>
</LinearLayout>

用户信息实体类
这里我们就直接使用了 Kotlin 中的数据类来进行实现

package com.xjh.queryuser.bean

data class Account(var name:String,var level:Int)

回调接口
对于数据请求的成功和失败提供相关接口

package com.xjh.queryuser.callback

import com.xjh.queryuser.bean.Account

interface MCallback {
    fun onSuccess(account:Account)
    fun onFailed()
}

NormalActivity

package com.xjh.queryuser

import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import com.xjh.queryuser.bean.Account
import com.xjh.queryuser.callback.MCallback
import kotlinx.android.synthetic.main.activity_normal.*
import java.util.*

class NormalActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_normal)
        initEvent()
    }

    private fun initEvent() {
        submit.setOnClickListener {
            val userInput = getUserInput()
            getAccountData(userInput, object : MCallback{
                override fun onSuccess(account: Account) {
                    showSuccessPage(account)
                }

                override fun onFailed() {
                    showFailedPage()
                }
            })
        }
    }

    // 获取用户输入信息
    private fun getUserInput(): String {
        return userName.text.toString()
    }

    // 展示获取数据成功的界面
    private fun showSuccessPage(account: Account) {
        result.text = "用户账号:" + account.name + " | " + "用户等级:" + account.level
    }

    // 展示获取数据失败的界面
    private fun showFailedPage() {
        result.text = "获取数据失败"
    }

    // 模拟查询账号数据
    private fun getAccountData(accountName: String, callback: MCallback) {
        val random = Random()
        val isSuccess = random.nextBoolean()
        if (isSuccess) {
            val account = Account(accountName, 100)
            callback.onSuccess(account)
        } else {
            callback.onFailed()
        }
    }
}

这时候我们就会发现所有的功能都是堆积在一个Activity中的,导致Activity中代码的可复用性降低,Activity过于累赘。
而Android的框架就是来解决这些问题的,Android中的框架主要是有传统的MVC模式,以及Android中的MVP以及MVVM模式,那他们分别是什么呢?

MVC

简介

MVC的全名是Model View Controller,即模型(model)- 视图(view)- 控制器(controller)。
MVC
在 Android 中 Controller 就是 Activity、Fragment 等,而 View 则是 layout, view 等控件,最后 Model 则对应的就是一些数据处理的逻辑,比如:网络请求,数据库,文件查询等
主要是这个过程,我们对 View 进行点击会传递到 Controller 中,也就是 Activity 上,然后呢,Controller 就会通知给 Model,Model 对数据进行处理以后将结构返回给 View 进行展现。
比如我们将数据从 Controller 传递到 Model 上,可以让 Controller 持有 Model的引用,而 Model 要向 View 传递数据的话我们一般不会让 Model 持有 View 的引用,而是类似 CallBack 的注册监听的方式进行数据的传递。
然后我们怎么来使用 MVC 模式优化我们之前的代码呢?
我们可以将上面的五条按照 MVC 的规则进行分层,其实 MVC 主要是把数据处理这块的逻辑给放到 Model 层中进行处理。
MVC优化

代码实现

然后我们需要怎么做呢?我们需要将数据的获取与界面的展示分离,以及解决 MVC 各层之间的通信问题。对于前一个问题我们只需要将查询账号数据的功能从 Activity 中分离到 Model 中就可以了,而后者就需要想办法让 Activity 通知 Model 获取数据和 Model 通知 Activity 更新界面。
MVCModel

package com.xjh.queryuser.mvc

import com.xjh.queryuser.bean.Account
import com.xjh.queryuser.callback.MCallback
import java.util.*

class MVCModel {

    // 模拟查询账号数据
    fun getAccountData(accountName: String, callback: MCallback) {
        val random = Random()
        val isSuccess = random.nextBoolean()
        if (isSuccess) {
            val account = Account(accountName, 100)
            callback.onSuccess(account)
        } else {
            callback.onFailed()
        }
    }
}

然后我们在 Activity 中持有 Model 的引用就可以了
MVCActivity

package com.xjh.queryuser.mvc

import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import com.xjh.queryuser.R
import com.xjh.queryuser.bean.Account
import com.xjh.queryuser.callback.MCallback
import kotlinx.android.synthetic.main.activity_normal.*

class MVCActivity : AppCompatActivity() {

    private lateinit var mMVCModel: MVCModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_normal)
        mMVCModel = MVCModel()
        initEvent()
    }

    private fun initEvent() {
        submit.setOnClickListener {
            mMVCModel.getAccountData(getUserInput(), object : MCallback {
                override fun onSuccess(account: Account) {
                    showSuccessPage(account)
                }

                override fun onFailed() {
                    showFailedPage()
                }
            })
        }
    }

    // 获取用户输入信息
    private fun getUserInput(): String {
        return userName.text.toString()
    }

    // 展示获取数据成功的界面
    private fun showSuccessPage(account: Account) {
        result.text = "用户账号:" + account.name + " | " + "用户等级:" + account.level
    }

    // 展示获取数据失败的界面
    private fun showFailedPage() {
        result.text = "获取数据失败"
    }
}

这样我们就将数据请求的部分代码抽离了出去,通过引用进行调用,让代码更加灵活、干净。

优缺点
优点

一定程度上的实现了 Model 与 View 的分离,降低了代码的耦合性。

缺点

Controller 与 View 难以完全解耦,并且随着项目复杂度的提升,Controller 将会越来越臃肿,Activity 承担了控制器的功能,又要承担部分视图层的工作。
其实 MVC 模式的实现也可以这样进行表示:
MVC
这样就体现出来了 View 和 Controller 的耦合关系。

MVP

简介

MVP 的全称为 Model - View - Presenter 模型,他是将 Model 和 View 隔离开来,两者之间不相互作用,而是通过 Presenter 作为一个中间件进行通信。
MVP
Model 和 MVC 模式中的 Model 的功能是相同的,主要是做一些数据处理的功能,而 View 就是直接对应了 Activity,Fragment 以及 layout 和 view 等控件,Presenter 就是他们俩之间沟通的桥梁。
这样 Activity 的功能就被简化了,不再充当控制器,主要就是负责 View 层面的工作。
这样我们就又可以将业务逻辑处理部分代码从 MVC 中的 Controller 中取出,放入 Presenter 中。
在这里插入图片描述

代码实现

这时候我们就要定义相关的接口,让 Presenter 可以调用 Activity 中实现的方法。
接口定义

package com.xjh.queryuser.mvp

import com.xjh.queryuser.bean.Account

interface IMVPView {
    fun getUserInput(): String
    fun showSuccessPage(account: Account)
    fun showFailedPage()
}

我们的 Activity 实现这个接口
MVPActivity

package com.xjh.queryuser.mvp

import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import com.xjh.queryuser.R
import com.xjh.queryuser.bean.Account
import kotlinx.android.synthetic.main.activity_normal.*

class MVPActivity : AppCompatActivity(), IMVPView {

    private lateinit var mMVPPresenter: MVPPresenter

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_normal)
        mMVPPresenter = MVPPresenter(this)
        initEvent()
    }

    private fun initEvent() {
        submit.setOnClickListener {
            mMVPPresenter.getData()
        }
    }

    // 获取用户输入信息
    override fun getUserInput(): String {
        return userName.text.toString()
    }

    // 展示获取数据成功的界面
    override fun showSuccessPage(account: Account) {
        result.text = "用户账号:" + account.name + " | " + "用户等级:" + account.level
    }

    // 展示获取数据失败的界面
    override fun showFailedPage() {
        result.text = "获取数据失败"
    }
}

Model 与 MVC 中的 Model 的作用相同,所以代码可以直接复用过来
MVPModel

package com.xjh.queryuser.mvp

import com.xjh.queryuser.bean.Account
import com.xjh.queryuser.callback.MCallback
import java.util.*

class MVPModel {

    // 模拟查询账号数据
    fun getAccountData(accountName: String, callback: MCallback) {
        val random = Random()
        val isSuccess = random.nextBoolean()
        if (isSuccess) {
            val account = Account(accountName, 100)
            callback.onSuccess(account)
        } else {
            callback.onFailed()
        }
    }
}

Presenter 持有 View 和 Model 的引用,通过 Presenter 我们向 Model 请求相关数据,并根据判断将结果返回 View 上面显示出来
MVPPresenter

package com.xjh.queryuser.mvp

import com.xjh.queryuser.bean.Account
import com.xjh.queryuser.callback.MCallback

class MVPPresenter {

    private val mView: IMVPView
    private val mModel: MVPModel

    constructor(mView: IMVPView) {
        this.mView = mView
        mModel = MVPModel()
    }

    fun getData() {
        mModel.getAccountData(mView.getUserInput(), object : MCallback {
            override fun onSuccess(account: Account) {
                mView.showSuccessPage(account)
            }

            override fun onFailed() {
                mView.showFailedPage()
            }
        })
    }
}
优缺点
优点

解决了 MVC 中 Controller 与 View 过度耦合的缺点,职责划分明显,更加易于维护。

缺点

接口数量过多,项目复杂的升高。随着项目的复杂度升高, Presenter 层将会越来越臃肿
结合上面所说的优缺点,有几条关于 MVP 的使用建议:

  1. 接口规范化(封装父类接口以减少接口的使用量)
  2. 使用第三方插件自动生成 MVP 代码
  3. 对于一些简单的页面,可以选择不使用框架
  4. 根据项目的复杂程度,部分模块可以选择不使用接口

其实这些操作都是为了一个目的:减少接口的数量

MVVM

简介

MVVM 的全称是 Model - View - ViewModel 模型,他的模型结构和 MVP 很相像,但是在代码逻辑上 MVVM 会显得更加简洁,其实他就是将 Presenter 换为了 ViewModel。
MVVM
MVVM 在 MVP 的基础上实现了数据视图的绑定(DataBinding),这样的话就不用使用接口进行传递了,而是当数据变化的时候,视图会自动更新;反之,当视图发生改变的时候,数据也会进行自动更新。这样做有什么好处呢?

  1. 减少了接口数量
  2. 不用使用 findViewById 去操控 View
DataBinding

DataBinding 是谷歌官方发布的一个实现数据绑定的框架(实现数据与视图的双向绑定),DataBinding 可以帮助我们在 Android 中更好的实现 MVVM 模式。那我们如何使用 DataBinding 呢?

  1. 在代码中启用 DataBinding
  2. 修改布局文件为 DataBinding 布局
  3. 数据绑定

详细的 DataBinding 用法可以看我另一篇博客:传送门

代码实现

其实这里的流程图和 MVP 的形式是一样的,只不过通讯手段从接口的形式变为了 DataBinding 的形式
MVVM
首先我们需要在 build.gradle 中声明使用 DataBinding
build.gradle( app )
在 android 中添加下面代码

android {
    dataBinding {
        enabled = true
    }
}

如果使用的是 Kotlin 的话还需要添加下面代码

apply plugin: 'kotlin-kapt'

kapt {
    generateStubs = true
}

然后我们的 Model 和 MVP 中的是一样的
MVVMModel

package com.xjh.queryuser.mvvm

import com.xjh.queryuser.bean.Account
import com.xjh.queryuser.callback.MCallback
import java.util.*

class MVVMModel {

    // 模拟查询账号数据
    fun getAccountData(accountName: String, callback: MCallback) {
        val random = Random()
        val isSuccess = random.nextBoolean()
        if (isSuccess) {
            val account = Account(accountName, 100)
            callback.onSuccess(account)
        } else {
            callback.onFailed()
        }
    }
}

之后我们就要分析在 ViewModel 中我们需要哪些变量以及方法,比如:getData(),我们需要调用 Model 中的 getAccountData 方法去获取数据,因为我们是使用 DataBinding 方法,所以在 ViewModel 中还需要记录对应的返回值用于展现,因为我们希望和数据变更进行绑定,防止每次都要更新所有的数据,所以我们的类需要继承 BaseObservable。然后在元素的 get 和 set 方法做出修改,get 方法需要添加注解 @Bindable 而 set 方法中需要加入更新该元素显示的代码 notifyPropertyChanged(BR.XXX); ,BR 是什么呢,其实就是相当于我们的 R 文件,用来确定是那个控件需要更新,只不过这里可以直接用变量名。
MVVMViewModel

package com.xjh.queryuser.mvvm

import android.databinding.BaseObservable
import android.databinding.Bindable
import android.view.View

import com.xjh.queryuser.bean.Account
import com.xjh.queryuser.callback.MCallback

import com.xjh.queryuser.BR

class MVVMViewModel : BaseObservable() {

    private val mvvmModel: MVVMModel = MVVMModel()
    @get:Bindable
    var userInput: String? = null
        set(userInput) {
            field = userInput
            notifyPropertyChanged(BR.userInput)
        }
    @get:Bindable
    var result: String? = null
        set(result) {
            field = result
            notifyPropertyChanged(BR.result)
        }

    fun getData(view: View) {
        this.userInput?.let {
            mvvmModel.getAccountData(it, object : MCallback {
                override fun onSuccess(account: Account) {
                    result = "用户账号:" + account.name + " | " + "用户等级:" + account.level
                }

                override fun onFailed() {
                    result = "获取数据失败"
                }
            })
        }
    }
}

然后我们就要进入使用 DataBinding 的第二步:修改布局文件,我们需要将 ViewModel 作为布局文件的参数,然后在布局文件中进行调用。
布局文件

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>

        <variable
            name="viewModel"
            type="com.xjh.queryuser.mvvm.MVVMViewModel" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <EditText
            android:id="@+id/userName"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="10dp"
            android:hint="请输入用户名"
            android:text="@={viewModel.userInput}"
            android:textSize="14sp" />

        <Button
            android:id="@+id/submit"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="30dp"
            android:onClick="@{viewModel.getData}"
            android:text="提交" />

        <TextView
            android:id="@+id/result"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="10dp"
            android:text="@{viewModel.result}"
            android:textSize="14sp" />
    </LinearLayout>
</layout>

这样的话我们就可以直接在布局文件中调用 ViewModel 的方法了。
最后我们就要进行使用 DataBinding 的最后一步操作了,绑定数据,这时候我们就要修改原有的 setContentView 方法,改为使用 DataBindingUtil.setContentView 方法进行绑定,然后我们将我们需要的 ViewModel 传入进去。
DataBinding 有单向绑定和双向绑定的区别,如果是单向绑定的话就是直接用 @{XXX} 的形式,如果是需要双向绑定的话就是需要改为 @={XXX} 的形式。
双向绑定的好处就是不仅在数据变化的时候进行刷新 View,也会在 View 主动修改数据的时候对数据进行更新,多用于编辑框等控件。
MVVMActivity

package com.xjh.queryuser.mvvm

import android.databinding.DataBindingUtil
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import com.xjh.queryuser.R
import com.xjh.queryuser.databinding.ActivityMvvmBinding

class MVVMActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        var binding = DataBindingUtil.setContentView<ActivityMvvmBinding>(this, R.layout.activity_mvvm)
        var mvvmViewModel = MVVMViewModel()
        binding.viewModel = mvvmViewModel
    }
}

优缺点
优点

实现了数据和视图的双向绑定,极大的简化代码

缺点

Bug 难以调试,学习成本较大。

总结

框架名总结
MVC学习简单,但是解耦不够彻底
MVP解耦更加彻底,学习起来较为简单,但是代码相对较为繁琐
MVVM代码逻辑简洁欸,但是学习成本较大

项目GitHub地址:传送门

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值