MVC架构、演示及问题

本文探讨了MVC架构的三层模型及其在Android中的应用,指出Controller层承担过多职责的问题,以及可能导致内存泄漏的致命缺点。通过代码演示和分析,揭示了MVC在实际项目中的体现,并解释了由于内存泄漏问题,MVC如何逐渐演变为MVP架构。

MVC架构的三层模型

MVC

MVC的流程:

  • View接收到用户操作,并将这些操作交给Controller
  • Controller完成具体业务逻辑
  • 得到结果的封装Model,再进行View更新

缺点:类似单线、Controller做的事情多,不可或缺的环节

MVC架构设计

MVC设计
在Android中Controller(如Activity)层做的事情特别多,要做Model的职责,又要做View层的职责,违反了面向对象的单一原则。

MVC的项目很多,中小项目中高达50%以上,这是由项目的适合场景决定的。

MVC的致命缺点:内存泄漏

Java经典模型及Android对照

Java MVC
Java而言,MVC是一种非常完善的设计。在Java后台设计流程是这样的,由View层(JSP)发起一个事件请求,Controller层(action)收到请求,控制功能模块完成业务逻辑,再将结果封装成Model层数据,最后刷新View层。
Android MVC
Controller层的业务逻辑尤为多,如注册/登录需求、网络请求、图片加载等等。

在所有的Android开发文档中,几乎没有MVC的字眼,也没有明确指出项目当中的模型就是MVC。
其实,MVC就存在项目中,如:新创建的项目,MainActivity,activity_main.xml,XxxBean等,在Controller层(MainActivity)中接收了View层的点击事件,在处理事件中操作了Model。也可以通过Model层(数据的更改)来刷新UI。
这就是最经典的、最简单的MVC。

代码演示

M层:

public class ImageBean {
    private String requestPath; // 网络图片地址
    private Bitmap 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;
    }
}

V层:

<?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:text="获取图片" />

    <ImageView
        android:id="@+id/iv_image"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/bt_get_image" />

</RelativeLayout>

C层:

@ContentView(value = R.layout.activity_main)
public class MainActivity extends BaseActivity implements ImageCallback {

    private final static String PATH = "http://xxx/yyy/zzz.png";

    // 耗时操作,需要Handler子线程来接收。用来刷新UI的
    private Handler handler = 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 error
                    break;
            }
            return false;
        }
    });

    @InjectView(R.id.bt_get_image)
    Button btnImg;
    @InjectView(R.id.iv_image)
    ImageView imageView;

    @Override
    protected void onStart() {
        super.onStart();
    }

    // 触发ImageDownloader功能模块工作
    @OnClick({R.id.bt_get_image})
    public void getImage(View view){
        ImageBean imageBean = new ImageBean();
        imageBean.setRequestPath(PATH);
        new ImageDownloader().down(this, imageBean);
    }

    // 请求完成,就需要handler来刷新UI,假设返回成功,就可以从ImageBean中拿到Bitmap对象
    @Override
    public void callback(int resultCode, ImageBean imageBean) {
        Message message = handler.obtainMessage(resultCode);
        message.obj = imageBean.getBitmap();
        handler.sendMessage(message);
    }
}

回调接口:

/**
 * 一旦做耗时操作,就需要在Activity中接收这样的及时反馈,最平常的就是接口来处理。
 */
public interface ImageCallback {

    /**
     * @param resultCode 请求结果返回标识码
     * @param imageBean Model层数据中bitmap对象(用于C层刷新V)
     */
    void callback(int resultCode, ImageBean imageBean);
}

功能模块:下载图片

/**
 * 功能模块
 * 1.下载地址
 * 2.下载后,通知Activity(通过Callback回调通知)
 */
public class ImageDownloader {

    // 成功
    public static final int SUCCESS = 200;
    // 失败
    public static final int ERROR = 404;

    /**
     * 下载需要地址,下载完成后需要回调给Activity
     * @param callback 回调接口
     * @param imageBean 携带地址的Model层对象
     */
    public void down(ImageCallback callback, ImageBean imageBean) {
        new Thread(new DownLoader(callback, imageBean)).start();
    }

    // final跟性能优化有关系
    static final class DownLoader implements Runnable {
        private final ImageCallback callback;
        private final ImageBean imageBean;

        public DownLoader(ImageCallback callback, ImageBean imageBean) {
            this.callback = callback;
            this.imageBean = imageBean;
        }

        @Override
        public void run() {
            InputStream inputStream = null;
            try {
                // 知道地址
                URL url = new URL(imageBean.getRequestPath());
                // 打开链接
                HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();
                httpURLConnection.setConnectTimeout(5000);
                httpURLConnection.setRequestMethod("GET");

                if (httpURLConnection.getResponseCode() == HttpURLConnection.HTTP_OK) {
                    // 拿到流
                    inputStream = httpURLConnection.getInputStream();
                    // 将流转成Bitmap
                    Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
                    showUi(SUCCESS, bitmap);
                } else {
                    showUi(ERROR, null);
                }
            } catch (Exception e) {
                e.printStackTrace();
                showUi(ERROR, null);
            } finally {
                if (inputStream!=null) {
                    try {
                        inputStream.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }

        private void showUi(int resultCode, Bitmap bitmap) {
            if (callback != null) {
                imageBean.setBitmap(bitmap);
                callback.callback(resultCode, imageBean);
            }
        }
    }
}

异常解决:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.uml">

    <uses-permission android:name="android.permission.INTERNET"/>

    <!-- 9.0设备之后,Android默认要求使用加密连接。
    application节点下有个属性usesCleartextTraffic 9.0之前均为true,之后为false,所以要设置为true-->
    <application
        android:usesCleartextTraffic="true"
        android:name=".app.BaseApp"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.Test">
        <activity android:name=".activity.MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

MVC 为何演变为 MVP ?

大家想象一个场景:用户在某个界面中按了返回键退出了,界面销毁了,那肯定是调用了onDestroy方法,那么该Activity是被回收了还是没有被回收?

修改下MainActivity中的代码,打开性能分析,看一下:

@Override
protected void onStart() {
    super.onStart();
    
    // 这不是耗时操作,是一个线程消耗
    new Thread(new Runnable() {
        @Override
        public void run() {
            SystemClock.sleep(50000);
        }
    }).start();
}

run一下看看,点击Profiler,打开性能分析,进入内存:
性能分析—进入内存
现在线程开销已经跑起来了,点击返回键,在性能分析中点击快照。
返回键—快照
根据包名进行分析,发现MainActivity并未结束:
包名分析
等待着的回调
这显然是个非常严重的问题:内存泄漏。

无论是 MVC、MVP、MVVM,Activity对于用户来说都是最直观的,Activity在销毁的时候,能不能干净、纯粹,是内存泄漏评估的一个点。但是这个程序告诉我们,onDestroy调用了,界面也销毁了,但是Activity并未被回收。这就是MVC中C层和M层,和V层之间的交互特别多,导致它很容易产生内存泄漏。

生活中这种例子常不常见呢?当你在玩一个应用,来了一个电话或微信消息,你接听了电话或打开了微信消息,花费了一些时间,再回头打开刚才的应用,发现它重启了,为什么?说明它是有内存泄漏的问题的。

致命弱点——内存泄漏,也许就是MVC演变为MVP的关键。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值