頁面結構化在 Android 上的嘗試

本文來自於騰訊Bugly公衆號(weixinBugly),未經做者贊成,請勿轉載,原文地址:https://mp.weixin.qq.com/s/M45DM5Ix7a2fmrsE8VPvxgjava

做者:bizaitanweb

導語:MVP開發模式能夠幫助項目結構解耦,但其龐大的方法數增長,較爲笨重設計對於手Q項目並不很適合。參考以前Web開發經驗,提出以頁面結構化的解耦方式組織代碼。下面講講Lego在Android上一次小小嚐試設計模式

一,MVP簡介

MVC太過常見這裏不囉嗦。實際應用MVC當中,Activity佔據打部分的工做,View和Controller的身份分不清。而MVP則是一種設計模式專門優化Activity / Fragment。緩存

先來看看MVP模式的核心思想:View不直接與Model交互性能優化

MVP 把 Activity 中的 UI 邏輯抽象成 View 接口,把業務邏輯抽象成 Presenter 接口,Model 類仍是原來的 Model微信

在MVP設計模式中,網絡

  • View:由Activity充當,而且響應生命週期
  • Model:仍是原來的數據層,網絡,緩存,解析等。
  • Presenter:做爲View和Model的中間紐帶,View不能直接對Model進行操做,必須通過Presenter
  • View interface:須要View實現的接口,View經過View interface與Presenter進行交互,下降耦合

二,日跡MVP實戰應用

【Mode層】咱們直接忽略ide

【View Interface】首頁的View接口,抽離出view和presnter交互的接口。由Activity繼承實現(Now.java,QQStoryMainActivity.java)工具

public interface IMyStoryListView {
    public void setData(MyStorys myStoryList, RecentStory recentStoryList);
    public void setSegmentData(String key, Object data,boolean needRefreshUi);
    /**
     * 更新數據後刷新界面走的回調
     * @param success
     * @param isManualPullRefresh
     */
    public void pullRefreshCompleted(boolean success,boolean isReqCompleted);
    public void launchNewVideoTakeActivity(boolean autoStart, boolean checkSo, int entranceType,String extra);
    public void setPlayVideoBtnDisplay(boolean display);
    public void showStartDownload();
    public void showDownloadCompleted(boolean success);
    public void storyPreLoadCompleted(String category, String uin);
    public void LoadMoreCompleted(boolean repositoryUpdated, boolean isEnd);
    public void showEmptyView(boolean display);
    public void requestDataCompleted();
    public void openMyStoryListView(boolean open);
}

【View】咱們的Activity實現了View接口,而且實現生命週期組件化

public class QQStoryMainAcitivty extends QQStoryBaseActivity implements IMyStoryListView {

    protected StoryHomePushYellowBarHandler mStoryHomePushYellowBarHandler = new StoryHomePushYellowBarHandler();
    protected MystoryListView mainListView;
    protected IMyStroyPresenter myStoryListPresenter;

    @Override
    protected boolean doOnCreate(Bundle savedInstanceState) {
        super.doOnCreate(savedInstanceState);
        mainListView = (MystoryListView) super.findViewById(R.id.qqstory_story_main_listview);
        //Presenter
        myStoryListPresenter = new StoryListPresenter(this);
        myStoryListPresenter.setIView(this);
        return true;
    }

    @Override
    public void onStartAutoRequestFromNet() {
        startTitleProgress();
        mainListView.pullToRefresh();
        mStoryHomePushYellowBarHandler.clearYellowBar();
        myStoryListPresenter.requestAllDataFromNet();
    }

    private void startTitleProgress(){
        // do more
    }
}

舉個例子,用戶下拉刷新一下。觸發到Activity的onStartAutoRequestFromeNet。View邏輯在Activity。

業務邏輯則由Presnter的requestAllDataFromNet去實現。

【Presenter】具體的View->Model,Mode->View由這裏實現,其中View是有View接口抽象,進一步規範化View的邏輯。

必要是能夠抽出Presenter接口(其實日跡這裏沒有必要)

public class StoryListPresnter implements IMyStroyPresenter{
    protected IMyStoryListView mIView;
    protected FeedItem mFeedItem;
    protected ParallelStepExecutor mRequestNetDataExecutor;

    @Override
    public void onCreate(boolean needUpdateFromNet) {
        // 生命週期的邏輯處理
        mFeedItem = new FeedItem();
    }
    @Override
    public void setIView(IMyStoryListView IView) {
        // 設置View接口(目前實現的是Activity,但其實由其餘Fragment,View實現都是能夠的,這就是MVP的好處之一,解耦)
        mIView = IView;
    }

    public boolean requestAllDataFromNet() {
        mRequestNetDataExecutor.addStep(new GetUserSelfInfoStep(null))
                .addStep(new ReportWatchVideoListStep(StoryListPresenter.this))
                .addStep(new GetUserGuideInfoStep(StoryListPresenter.this))
                .onCompleted(new SimpleStepExector.CompletedHandler() {
                    @Override
                    public void done(FeedItem item) {
                        // 僞代碼
                        mFeedItem = item;                       // 處理Model層
                        mIView.openMyStoryListView(mFeedItem);  // 根據View接口調用View更新
                    }
                }).run();
    }

}

MVP的優缺點:

優勢:

  1. 解耦,絕對的。否則抽這麼多接口乾嗎
  2. 模塊職責明確,層次清晰
  3. Presenter可複用(在日跡的需求中,首頁和4Tab公用一個Presnter)
  4. 方便單元測試
  5. 避免Activity內存泄露, Acitvity一身輕鬆

MVP的缺點也是很是明確的:

  1. 很是的笨重。一個View就對應一個Presenter,輕業務一個Activity能解決的就不要解決
  2. Presnter依然邏輯繁重。Acitivty輕鬆了,業務邏輯龐大的時候Presnter依然是大胖子。
  3. 代碼複雜度,學習成本。這玩意很差理解,須要實戰中理解。
  4. 在手Q項目裏,MVP會激增不少方法數。

三,Lego頁面結構化

前面鋪墊這麼多,終於到我要吹水的時候了。MVC,MVP,還有MVVM等MVX系列的設計模式,都是一種大而全的統一管理。在項目結構中最爲關鍵實際上是:分模塊!

看看某寶的首頁,頂部搜索欄,banner,導航分類,搶購,特價,底部Tab。這是一個Activity的話,你再怎麼MVP,也是須要劃分模塊,而後分而治之。

一個再大的系統,均可以劃分一個個小的模塊,分而治之

頁面結構化,並非新玩意,是當時作web的一套代碼風格。下圖是當時作Web總結組件化的一張圖。如今看來,也就並無過期

頁面被劃分問一個個區域的模塊,有自身的邏輯和規劃。有人說,這不就是一個個組件嘛。而後「頁面結構化」並非指組件。

例如上圖的tabContainer,imgsContainer,listContainer,每個模塊都有本身的渲染模板(xml),請求的數據的CGI(數據源),自身的事件綁定(listener) ,狀態機(生命週期),並不僅是一個組件,而是一個個有本身生命力,能本身管理的小頁面。

根據頁面結構,劃分出一個個獨立維護模塊,這就是頁面結構化。

頁面結構化(Lego)與組件化的區別

  1. 組件處於通用性,是不帶業務邏輯的。而頁面結構化是帶業務邏輯。
  2. 頁面結構化目的是爲了代碼維護性,項目管理,優化。組件複用能夠有,但不是必要
  3. 組件與Lego不衝突。組件 +數據,業務邏輯 = Lego

下面就以問答的形式,用日跡評論贊項目實戰,來說解Lego好處

四,分析頁面結構化特性

Lego本身拉取本身的數據,若是一個頁面5,6個模塊,就拉5,6分PB協議,談何性能?

這裏帶出Lego兩個特性:

  1. 每一個Lego是有本身的數據,並非必定要本身拉取,數據能夠有其餘Lego傳遞
  2. Lego有父子關係。一個頁面/Activity須要一個頂層Lego管理

日跡首頁評論贊

public FeedCommentLikeLego(Context context, Activity activity, ViewGroup parentView, HomeFeedItem feedItem, int feedType) {
    super(context, parentView);
    mHomeFeedItem = feedItem;
    mFeedItem = feedItem.mFeedBasicItem;
    mActivity = activity;
    mFeedType = feedType;
    mLikeManager = (LikeManager) SuperManager.getAppManager(SuperManager.LIKE_MANAGER);
    mParentView = LayoutInflater.from(context).inflate(R.layout.qqstory_feed_commentlike_view, parentView, true);

    // 頁面結構
    FeedCommentLego commentLego = new FeedCommentLego(mContext, mParentView, mFeedItem, mFeedType);
    FeedLikeLego likeLego = FeedLikeLego.createIndexFeedLikeLego(mContext, activity, mParentView, mFeedItem, mFeedType);
    addLego(LEGO_KEY_COMMENT, commentLego);
    addLego(LEGO_KEY_LIKE, likeLego);
    commentLego.feed(mHomeFeedItem.getCommentList());
    likeLego.feed(mHomeFeedItem.getLikeEntryList());
    boot();
}

從FeedCommentLikeLego的構造方法,咱們得知

  1. 我是爸爸,我有兩個兒子
  2. 我兩個兒子不爭氣,須要我來餵養數據,本身不會掙錢(本身不拉數據)
  3. 全家我是一家之主,啓動我說了算(Lego啓動boot後,會本身拉數據本身渲染,同時子Lego也會相繼boot)

日跡710這裏就有場景,體驗出Lego切換數據源的優點。

【首頁】出於性能優化,都會作請求合併。返回多個Feed的視頻列表,評論贊列表數據。

commentLego.feed(mHomeFeedItem.getCommentList());
 likeLego.feed(mHomeFeedItem.getLikeEntryList());

被餵養數據後,Lego內部的DataProvider將不啓動

【詳情頁】同一Lego,默認狀況就會啓動資金的DataProvider,會本身拉數據

@Override
public LegoDataProvider getDataProvider() {
    return new FeedLikeDataProvider(this, mIsDetailPage);
}

一個Lego類是到底是什麼?Lego類之間的紐帶?

大部分頁面的渲染流程線,以下圖

咱們把這些經常使用的網絡請求,處理數據,事件綁定,上報,容錯處理等一系列邏輯方法,以頁面塊爲單位封裝成一個Lego模塊。

這樣的一個抽象層Lego,咱們能夠清晰地看到該頁面塊,請求的數據是什麼,綁定了什麼事件,作了什麼上報,出錯怎麼處理。

最後加上生命週期,頁面結構化的Lego,已經算是一個完整的功能單元了。

繼承LegoBase,有幾個核心的方法須要重寫:

還有生命週期方法能夠重寫,但不是必要的。

你閱讀/接手一個Lego類,會是件很輕鬆的事情。一個Lego類,核心方法這幾個,其他都是業務邏輯方法。

改事件去該Lego的EventHandler,數據要改去DataProvider,產品要求大V才展現底部尾巴,好,去render方法找。

Lego之間的紐帶,有三個:

  • parentView(公用xml)
  • feedData(公用數據)
  • getLego(Lego關係)

四,總結

Lego的核心思想是:頁面結構分模塊,分而治之。解耦,代碼可讀性高,底層統一優化
在使用了兩個版本以後,感受完成度仍是不夠。

  1. 頂層Lego狀況複雜,底層統一優化很差作
  2. 接口之間約束,不夠自由

可是對比MVP,Lego能體驗出輕便,邏輯清晰,方法數量少的優點。

Lego頁面結構化的應用其實還在嘗試階段。以上算個人一些我的思考和總結。


更多精彩內容歡迎關注騰訊 Bugly的微信公衆帳號:

騰訊 Bugly是一款專爲移動開發者打造的質量監控工具,幫助開發者快速,便捷的定位線上應用崩潰的狀況以及解決方案。智能合併功能幫助開發同窗把天天上報的數千條 Crash 根據根因合併分類,每日日報會列出影響用戶數最多的崩潰,精準定位功能幫助開發同窗定位到出問題的代碼行,實時上報能夠在發佈後快速的瞭解應用的質量狀況,適配最新的 iOS, Android 官方操做系統,鵝廠的工程師都在使用,快來加入咱們吧!

相關文章
相關標籤/搜索