本文用来认识MVP项目结构,第二篇会补充处理内存泄漏等注意点。
理论知识,吧啦吧啦~,没兴趣可以直接 跳至Demo部分。
MVP初探
关系:
View收到用户的操作
View把用户的操作,交给Presenter
Presenter控制Model进行业务逻辑处理
Presenter处理完毕后,数据封装到Model
Presenter收到通知后,再更新View方式:是双向的通信方式
优点:
View层与Model层完全分离
所有的逻辑交互都在Presenter
MVP分层较为严谨
MVP思想精髓
View层只需面向Presenter层,不需要知道Model层;
Model层只需面向Presenter层,不需要知道View层;
View层和Model层逻辑交互在Presenter。
DownloadLoaderEngine.kt
// View层交互,Model层交互共同的需求(契约、合同)
interface DownloaderContract {
interface M {
// P层告诉M层,需要做什么事情
fun requestDownloader(imageBean: ImageBean)
}
interface PV {
// V层告诉P层,需要做什么事情
fun requestDownloader(imageBean: ImageBean)
// P层得到M层的结果返回,再通知V层
fun responseDownloaderResult(isSuccess: Boolean, imageBean: ImageBean)
}
}
ImageBean.kt
data class ImageBean(
var requestPath: String,// 网络图片请求地址
var bitmap: Bitmap?// 结果返回bitmap对象
)
DownloadPresenter.kt
// P层几乎不做事情?谷歌的sample中,P层是包揽了所有的活
class DownloadPresenter(var view: MainActivity) : DownloaderContract.PV{
private var model: DownloadLoaderEngine = DownloadLoaderEngine(this)// 下载的模型
override fun requestDownloader(imageBean: ImageBean) {
// 接收到View层的指令,去完成某个需求(可以自己完成,也可以让别人去完成)
model.requestDownloader(imageBean)
}
override fun responseDownloaderResult(isSuccess: Boolean, imageBean: ImageBean) {
// 将完成的结果告知View层(刷新UI)
// 将完成的结果告知View层(刷新UI)
view.runOnUiThread { view.responseDownloaderResult(isSuccess, imageBean) }
}
}
DownloadLoaderEngine.kt
class DownloadLoaderEngine(val presenter: DownloadPresenter) : DownloaderContract.M{
override fun requestDownloader(imageBean: ImageBean) {
// P层让我做这个需求
Thread(DownLoader(imageBean)).start()
}
inner class DownLoader(private val imageBean: ImageBean) : Runnable {
override fun run() {
try {
val url = URL(imageBean.requestPath)
val httpURLConnection = url.openConnection() as HttpURLConnection
httpURLConnection.connectTimeout = 5000
httpURLConnection.requestMethod = "GET"
if (httpURLConnection.responseCode == HttpURLConnection.HTTP_OK) {
val inputStream = httpURLConnection.inputStream
val bitmap = BitmapFactory.decodeStream(inputStream)
showUi(Constant.SUCCESS, bitmap)
} else {
showUi(Constant.ERROR, null)
}
} catch (e: Exception) {
e.printStackTrace()
showUi(Constant.ERROR, null)
}
}
private fun showUi(resultCode: Int, bitmap: Bitmap?) {
imageBean.bitmap = bitmap
presenter.responseDownloaderResult(resultCode == Constant.SUCCESS, imageBean)
}
}
}
MainActivity.kt
// MVC中Activity是C层,MVP中Activity是V层
class MainActivity : AppCompatActivity(), DownloaderContract.PV{
private lateinit var imageView: ImageView
private val presenter: DownloadPresenter = DownloadPresenter(this)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
imageView = findViewById(R.id.iv);
}
override fun requestDownloader(imageBean: ImageBean) {
presenter.requestDownloader(imageBean)
}
override fun responseDownloaderResult(isSuccess: Boolean, imageBean: ImageBean) {
Toast.makeText(this, if (isSuccess) "下载成功" else "下载失败", Toast.LENGTH_SHORT).show()
if (isSuccess && imageBean.bitmap != null) {
imageView.setImageBitmap(imageBean.bitmap)
}
}
// 点击事件
fun down(view: View?) {
val imageBean = ImageBean("", null)
imageBean.requestPath = Constant.IMAGE_PATH
requestDownloader(imageBean)
}
}
Constant.kt
object Constant {
// 网络图片地址
const val IMAGE_PATH =
"https://bkimg.cdn.bcebos.com/pic/b3119313b07eca80653838478b6880dda144ad342572?x-bce-process=image/resize,m_lfit,h_500,limit_1/format,f_auto"
// 成功
const val SUCCESS = 200
// 失败
const val ERROR = 404
}
最终效果