移動架構之MVP框架

MVP是在開發中經常使用的框架,要了解其原理,先要從瞭解MVC開始,這裏就對MVP框架作一個簡單的介紹java

MVC

MVC爲Model,View與Controllor的縮寫
Model:業務邏輯和實體模型
View:對應於佈局文件,可是細細的想一想這個View對應於佈局文件,其實能作的事情特別少,實際上關於該佈局文件中的數據綁定的操做,事件處理的代碼都在Activity中,形成了Activity既像View又像Controller
Controllor:對應於Activitygit

MVP

MVC爲Model,View與Presenter的縮寫
Model:業務邏輯和實體模型
View:對應於Activity,負責View的繪製以及與用戶交互
Presenter:負責完成View於Model間的交互github

MVP對比MVC

  • 減小了Activity的職責,簡化了Activity中的代碼,將複雜的邏輯代碼提取到了Presenter中進行處理。與之對應的好處就是,耦合度更低web

  • Activity 代碼變得更加簡潔:使用MVP以後,Activity就能瘦身許多了,基本上只有FindView、SetListener以及Init的代碼。其餘的就是對Presenter的調用,還有對View接口的實現。這種情形下閱讀代碼就容易多了,並且你只要看Presenter的接口,就能明白這個模塊都有哪些業務,很快就能定位到具體代碼。Activity變得容易看懂,容易維護,之後要調整業務、刪減功能也就變得簡單許多網絡

  • 方便進行單元測試:通常單元測試都是用來測試某些新加的業務邏輯有沒有問題,若是採用傳統的代碼風格(習慣性上叫作MV模式,少了P),咱們可能要先在Activity裏寫一段測試代碼,測試完了再把測試代碼刪掉換成正式代碼,這時若是發現業務有問題又得換回測試代碼,咦,測試代碼已經刪掉了!好吧從新寫吧……MVP中,因爲業務邏輯都在Presenter裏,咱們徹底能夠寫一個PresenterTest的實現類繼承Presenter的接口,如今只要在Activity裏把Presenter的建立換成PresenterTest,就能進行單元測試了,測試完再換回來便可。萬一發現還得進行測試,那就再換成PresenterTest吧框架

  • 避免 Activity 的內存泄露:異步

    • 發生OOM異常的緣由:現內存泄露形成APP的內存不夠用,而形成內存泄露的兩大緣由之一就是Activity泄露(Activity Leak)(另外一個緣由是Bitmap泄露(Bitmap Leak));Java一個強大的功能就是其虛擬機的內存回收機制,這個功能使得Java用戶在設計代碼的時候,不用像C++用戶那樣考慮對象的回收問題。然而,Java用戶老是喜歡隨便寫一大堆對象,而後幻想着虛擬機能幫他們處理好內存的回收工做。但是虛擬機在回收內存的時候,只會回收那些沒有被引用的對象,被引用着的對象由於還可能會被調用,因此不能回收;Activity是有生命週期的,用戶隨時可能切換Activity,當APP的內存不夠用的時候,系統會回收處於後臺的Activity的資源以免OOM
    • MVC產生內存泄漏異常分析:採用傳統的MVC模式,一大堆異步任務和對UI的操做都放在Activity裏面,好比你可能從網絡下載一張圖片,在下載成功的回調裏把圖片加載到 Activity 的 ImageView 裏面,因此異步任務保留着對Activity的引用。這樣一來,即便Activity已經被切換到後臺(onDestroy已經執行),這些異步任務仍然保留着對Activity實例的引用,因此係統就沒法回收這個Activity實例了,結果就是Activity Leak。Android的組件中,Activity對象每每是在堆(Java Heap)裏佔最多內存的,因此係統會優先回收Activity對象,若是有Activity Leak,APP很容易由於內存不夠而OOM
    • MVC模式如何比面內存泄漏:只要在當前的Activity的onDestroy裏,分離異步任務對Activity的引用,就能避免 Activity Leak

MVP角色

View::負責繪製UI元素、與用戶進行交互(在Android中體現爲Activity)
Activity interface:須要View實現的接口,View經過View interface與Presenter進行交互,下降耦合,方便進行單元測試
Model:負責存儲、檢索、操縱數據(有時也實現一個Model interface用來下降耦合)
Presenter:做爲View與Model交互的中間紐帶,處理與用戶交互的負責邏輯ide

開源MVP框架

理解實例

這裏使用一個ListView的設計來講明MVP在實際項目開發過程當中運用
首先準備一個java bean類,用來做爲ListView的顯示部分

public class Fruit {
    private int icon;
    private String name;
    private String describe;

    public Fruit(int icon, String name, String describe) {
        this.icon = icon;
        this.name = name;
        this.describe = describe;
    }

    public int getIcon() {
        return icon;
    }

    public void setIcon(int icon) {
        this.icon = icon;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getDescribe() {
        return describe;
    }

    public void setDescribe(String describe) {
        this.describe = describe;
    }
}

另外還須要一個數據類

public class FruitListAdapter extends BaseAdapter {
    private LayoutInflater inflater;
    private List<Fruit> data;

    public FruitListAdapter(Context context, List<Fruit> data) {
        this.inflater = LayoutInflater.from(context);
        this.data = data;
    }

    @Override
    public int getCount() {
        return data.size();
    }

    @Override
    public Object getItem(int position) {
        return data.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder holder = null;
        if (convertView == null) {
            convertView = inflater.inflate(R.layout.item, null);
            holder = new ViewHolder();
            holder.iv_icon = convertView.findViewById(R.id.iv_icon);
            holder.tv_name = convertView.findViewById(R.id.tv_name);
            holder.tv_describe = convertView.findViewById(R.id.tv_describe);
            convertView.setTag(holder);
        } else {
            holder = (ViewHolder) convertView.getTag();
        }
        holder.iv_icon.setImageResource(data.get(position).getIcon());
        holder.tv_name.setText(data.get(position).getName());
        holder.tv_describe.setText(data.get(position).getDescribe());
        return convertView;
    }

    private class ViewHolder {
        ImageView iv_icon;
        TextView tv_name;
        TextView tv_describe;
    }
}

model層

public interface IFruitModel {
    void loadFruit(FruitOnLoadListener fruitOnLoadListener);

    //監聽數據返回
    interface FruitOnLoadListener {
        void onComplete(List<Fruit> fruits);
    }
}
public class FruitModel implements IFruitModel {
    @Override
    public void loadFruit(FruitOnLoadListener fruitOnLoadListener) {
        List<Fruit> data = new ArrayList<>();
        data.add(new Fruit(R.drawable.p01, "石榴", "性味甘、酸澀、溫,具備殺蟲、收斂、澀腸、止痢等功效。石榴果實養分豐富,維生素C含量比蘋果、梨要高出一二倍"));
        data.add(new Fruit(R.drawable.p02, "荔枝", "荔枝味甘、酸、性溫,入心、脾、肝經;可止呃逆,止腹瀉,是頑固性呃逆及五更瀉者的食療佳品,同時有補腦健身,開胃益脾,有促進食慾之功效。因性熱,多食易上火。荔枝木材堅實,紋理雅緻,耐腐,從來爲上等名材"));
        data.add(new Fruit(R.drawable.p03, "獼猴桃", "獼猴桃的質地柔軟,口感酸甜。味道被描述爲草莓、香蕉、菠蘿三者的混合。獼猴桃除含有獼猴桃鹼、蛋白水解酶、單寧果膠和糖類等有機物,以及鈣、鉀、硒、鋅、鍺等微量元素和人體所需17種氨基酸外,還含有豐富的維生素C、葡萄酸、果糖、檸檬酸、蘋果酸、脂肪"));
        data.add(new Fruit(R.drawable.p04, "香瓜", "各類香瓜均含有蘋果酸、葡萄糖、氨基酸、甜菜茄、維生素C等豐富養分。全國各地普遍栽培,世界溫帶至熱帶地區也普遍栽培"));
        data.add(new Fruit(R.drawable.p05, "橘子", "橘子中的維生素A還可以加強人體在黑暗環境中的視力和治療夜盲症。橘子不宜食用過量,吃太多會患有胡蘿蔔素血癥,皮膚呈深黃色,如同黃疸通常"));
        data.add(new Fruit(R.drawable.p06, "檸檬", "檸檬因其味極酸,肝虛孕婦最喜食,故稱益母果或益母子。檸檬中含有豐富的檸檬酸,所以被譽爲「檸檬酸倉庫」。它的果實汁多肉脆,有濃郁的芳香氣。由於味道特酸,故只能做爲上等調味料,用來調製飲料菜餚、化妝品和藥品"));
        data.add(new Fruit(R.drawable.p07, "西瓜", "西瓜爲夏季之水果,果肉味甜,能降溫去暑;種子含油,可做消遣食品;果皮藥用,有清熱、利尿、降血壓之效"));
        data.add(new Fruit(R.drawable.p08, "蘋果", "蘋果是一種低熱量食物,每100克只產生60千卡熱量。蘋果中養分成分可溶性大,易被人體吸取,故有「活水」之稱。其有利於溶解硫元素,使皮膚潤滑柔嫩"));
        data.add(new Fruit(R.drawable.p09, "葡萄", "葡萄爲著名水果,生食或制葡萄乾,並釀酒,釀酒後的酒腳可提酒食酸,根和藤藥用能止嘔、安胎"));
        data.add(new Fruit(R.drawable.p10, "火龍果", "火龍果屬涼性,且果肉的葡萄糖不甜,但其糖分卻比通常水果的要高一些"));
        data.add(new Fruit(R.drawable.p11, "草莓", "原產南美,中國各地及歐洲等地廣爲栽培。草莓養分價值高,含有多種養分物質 ,且有保健功效"));
        data.add(new Fruit(R.drawable.p12, "柿子", "柿葉中含有多種活性成分,如維生素 C、 多種黃酮甙類、 二萜類、 膽鹼、 β- 胡蘿蔔素等"));
        data.add(new Fruit(R.drawable.p13, "香蕉", "香蕉是澱粉質豐富的有益水果。味甘性寒,可清熱潤腸,促進腸胃蠕動,但脾虛泄瀉者卻不宜。根據「熱者寒之」的原理,最適合燥熱人士享用。痔瘡出血者、因燥熱而致胎動不安者,均可生吃蕉肉"));
        data.add(new Fruit(R.drawable.p14, "菠蘿", "菠蘿性平,味甘、微酸、微澀、性微寒,具備清暑解渴、消食止瀉、補脾胃、固元氣、益氣血、消食、祛溼、養顏瘦身等功效,爲夏令醫食兼優的時令佳果,不過一次也不宜吃太多"));
        data.add(new Fruit(R.drawable.p15, "梨子", "梨含有大量蛋白質、脂肪、鈣、磷、鐵和葡萄糖、果糖、蘋果酸、胡蘿蔔素及多種維生素。梨仍是治療疾病的良藥,民間經常使用冰糖蒸梨治療喘咳,「梨膏糖」更是舉世聞名。梨還有降血壓、清熱鎮涼的做用,因此高血壓及心臟病患者食梨大有益處"));
        fruitOnLoadListener.onComplete(data);
    }
}

View層

public interface IFruitView {
    //UI業務邏輯,加載進度條
    void showLoading();
    //回調給Present
    void showFruit(List<Fruit> fruits);
}

Present層

public class FruitPresent {
    //持有視圖層引用
    private IFruitView fruitView;
    //持有模型層引用
    private IFruitModel fruitModel = new FruitModel();

    public FruitPresent(IFruitView fruitView) {
        this.fruitView = fruitView;
    }

    public void fectch() {
        fruitView.showLoading();
        if (fruitModel != null) {
            //回調監聽
            fruitModel.loadFruit(new IFruitModel.FruitOnLoadListener() {
                @Override
                public void onComplete(List<Fruit> fruits) {
                    fruitView.showFruit(fruits);
                }
            });
        }
    }
}

MainActivity調用,這裏的MainActivity其實就是View層的實現

//View層
public class MainActivity extends AppCompatActivity implements IFruitView {

    private ListView listView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        listView = findViewById(R.id.listview);
        //實例化Present並獲得數據
        new FruitPresent(this).fectch();
    }

    @Override
    public void showLoading() {

    }

    @Override
    public void showFruit(List<Fruit> fruits) {
        listView.setAdapter(new FruitListAdapter(this,fruits));
    }
}

這裏還應該對其優化,防止內存泄漏
增長BasePresent類,使其添加綁定和解綁操做

public abstract class BasePresent<T> {
    //持有UI接口的弱引用
    protected WeakReference<T> viewRef;

    //獲取數據方法
    public abstract void fectch();

    //弱引用綁定
    public void attachView(T view) {
        viewRef = new WeakReference<T>(view);
    }

    //解綁
    public void detachView() {
        if (viewRef != null) {
            viewRef.clear();
            viewRef = null;
        }
    }
}

讓其子類繼承

public class FruitPresent<T> extends BasePresent<IFruitView> {
    //持有視圖層引用
    private IFruitView fruitView;
    //持有模型層引用
    private IFruitModel fruitModel = new FruitModel();

    public FruitPresent(IFruitView fruitView) {
        this.fruitView = fruitView;
    }

    @Override
    public void fectch() {
        fruitView.showLoading();
        if (fruitModel != null) {
            //回調監聽
            fruitModel.loadFruit(new IFruitModel.FruitOnLoadListener() {
                @Override
                public void onComplete(List<Fruit> fruits) {
                    fruitView.showFruit(fruits);
                }
            });
        }
    }
}

添加BaseActivity,完成綁定和解綁操做

public abstract class BaseActivity<V, T extends BasePresent<V>> extends Activity {
    protected T present;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        present = createPresent();
        present.attachView((V) this);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        present.detachView();
    }

    //子類實現具體構建過程
    protected abstract T createPresent();
}

讓其子類繼承完成

//View層
public class MainActivity extends BaseActivity<IFruitView, FruitPresent<IFruitView>> implements IFruitView {

    private ListView listView;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main_v1);
        listView = findViewById(R.id.listview);
        present.fectch();
    }

    @Override
    protected FruitPresent<IFruitView> createPresent() {
        return new FruitPresent<>(this);
    }

    @Override
    public void showLoading() {
        Toast.makeText(this, "loading", Toast.LENGTH_SHORT).show();
    }

    @Override
    public void showFruit(List<Fruit> fruits) {
        listView.setAdapter(new FruitListAdapter(this, fruits));
    }
}
相關文章
相關標籤/搜索