Seek first to understand, then to be understood. —— Daily English
在Android项目中,MVC架构设计由于它较高的耦合性,非常容易造成内存泄漏,从而被MVP,MVVM等架构替代。
但作为一种经典的架构设计,MVC在现在的Android项目中,还有很高的占有率。因此我们很有必要去了解一番。
这篇文章将介绍
- MVC架构设计和这种模式在Android项目中的体现。
- Android项目中MVC内存泄漏分析,以及为什么会有MVP的演变。
介绍
初探
MVC架构设计
MVC流程关系:
- View接收到用户的操作
- View将用户的操作,交给Controller。
- Controller完成具体业务逻辑。
- 得到结果封装Model,再进行View更新。
从图可看出:
Controller是作为一个媒介,处于Model和View之间。
Model和View之间有紧密的联系,耦合性偏强。
C层处理的东西过多,违反了面向对象编程思想的单一性原则。
但是我们平常在比较的过程中,不能说哪个架构就一定好,要根据项目实际情况,做一定的取舍。
经典的三层模型
三层模型在Android中的体现
了解了三层模型在Android项目中的体现之后,我们通过一个Domo来加深印象。
实战
Domo很简单,实现一个网络下载一张图片,并展示的功能。
布局
布局中就一个Button
,一个ImageView
两个控件。点击按钮后,根据Path
下载图片并展示到ImageView
上。
代码如下
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/bt_get_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="getImage"
android:text="获取图片" />
<ImageView
android:id="@+id/iv_image"
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_below="@id/bt_get_image" />
</RelativeLayout>
实体类
定义实体类ImageBean
,在里面定义两个属性,requestPath
用来记录请求的图片地址,bitmap
用于接收返回的图片。
代码如下
public class ImageBean {
// 网络图片地址
private String requestPath;
// 结果返回bitmap对象
private Bitmap bitmap;
public String getRequestPath() {
return requestPath;
}
public void setRequestPath(String requestPath) {
this.requestPath = requestPath;
}
public Bitmap getBitmap() {
return bitmap;
}
public void setBitmap(Bitmap bitmap) {
this.bitmap = bitmap;
}
}
回调接口
public interface Callback {
/**
* @param resultCode 请求结果返回标识码
* @param imageBean Model层数据中bitmap对象(用于C层刷新V)
*/
void callback(int resultCode, ImageBean imageBean);
}
下载图片工具类
public class ImageDownloader {
// 成功
static final int SUCCESS = 200;
// 失败
static final int ERROR = 404;
public void down(Callback callback, ImageBean imageBean) {
new Thread(new Downloader(callback, imageBean)).start();
}
static final class Downloader implements Runnable {
private final Callback callback;
private final ImageBean imageBean;
public Downloader(Callback callback, ImageBean imageBean) {
this.callback = callback;
this.imageBean = imageBean;
}
@Override
public void run() {
try {
URL url = new URL(imageBean.getRequestPath());
HttpsURLConnection httpsURLConnection = (HttpsURLConnection) url.openConnection();
httpsURLConnection.setConnectTimeout(5000);
httpsURLConnection.setRequestMethod("GET");
if (httpsURLConnection.getResponseCode() == httpsURLConnection.HTTP_OK) {
InputStream inputStream = httpsURLConnection.getInputStream();
Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
showUi(SUCCESS, bitmap);
} else {
showUi(ERROR, null);
}
} catch (Exception e) {
e.printStackTrace();
showUi(ERROR, null);
}
}
private void showUi(int resultCode, Bitmap bitmap) {
if (callback != null) {
imageBean.setBitmap(bitmap);
callback.callback(resultCode, imageBean);
}
}
}
}
MainActivity
public class MainActivity extends AppCompatActivity implements Callback {
private ImageView imageView;
private final static String PATH = "https://dss1.baidu.com/70cFfyinKgQFm2e88IuM_a/forum/pic/item/9f2f070828381f300704a682a7014c086e06f0f8.jpg";
private Handler mHandler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(@NonNull Message msg) {
switch (msg.what) {
case ImageDownloader.SUCCESS:// 成功
imageView.setImageBitmap(((Bitmap) msg.obj));
break;
case ImageDownloader.ERROR:// 失败
Toast.makeText(MainActivity.this, "下载失败", Toast.LENGTH_SHORT).show();
break;
}
return false;
}
});
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
imageView = findViewById(R.id.iv_image);
}
// 点击事件
public void getImage(View view) {
ImageBean imageBean = new ImageBean();
imageBean.setRequestPath(PATH);
new ImageDownloader().down(this, imageBean);
}
@Override
public void callback(int resultCode, ImageBean imageBean) {
Message message = mHandler.obtainMessage(resultCode);
message.obj = imageBean.getBitmap();
mHandler.sendMessageDelayed(message, 500);
}
}
代码就这么多,加上权限后,我们来看运行效果
这个例子就是典型的用MVC架构设计搭建的项目。
V层收到指令(点击事件)后,告知C层去处理(下载图片),C层完成业务逻辑处理之后会将结果(Bitmap)更新到M层,最后M层的结果会返回给V层(展示图片)。
总的来讲就是V——>C——>M——>V的循环过程。
为什么会演变成MVP
我们做一个简单的测试,在MainActivity
的onCreate()
方法中加入以下代码。
new Thread(new Runnable() {
@Override
public void run() {
SystemClock.sleep(50000);
}
}).start();
启动的一个新的线程,休眠50s。当启动Activity之后,立马按返回键退出应用,并立即回收堆内存之后,我们会发现:
会有一个tartget in Thread
仍然在运行,还会有一个CallBack
在等待执行。这问题非常严重,想必大家都知道,这就是我们所说的存在内存泄露!
(对Profiler不熟悉的同学,可以参考我图中标注的1234步棸查看)
这就是MVC架构在Android项目中存在的巨大缺陷。
无论是在MVC,MVP,MVVM架构的项目中,用户最直观的就是Activity。Activity被销毁的时候,能不能非常的干净和纯粹就是我们作为内存泄漏评估的最关键的一个点。
但是我们看见Activity回调destory()
要销毁了,并没有真正的被销毁。
主要的原因就是在MVC架构中,作为C层的Activity对M层和V层的交互会非常多,比如开启了一个服务、请求网络或者做其他一些耗时操作,这个时候用户有可能已经把Activity页面关掉了,但是Activity没法真正被销毁,导致这种架构模式会很容易出现内存泄漏。
这也就是后来会慢慢演变用MVP架构去搭建Android项目。
本文完。MVC架构设计就介绍到这里,希望能让大家对MVC的架构有一个了解。MVP架构的介绍文章将在近期发布,欢迎批评指正。
文中Demo下载地址。