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下載地址。