1. 程式人生 > >搭建自己的 Android MVP 快速開發框架

搭建自己的 Android MVP 快速開發框架

Android 開發進入「死丟丟」的時代後,引用三方庫在 Gradle 的支援下變得十分輕鬆。各種高手寫的開源框架,極大程度降低了新手入行(坑)的門檻,「一週開發一款 App 並上線」也不再遙不可及。

關於快速開發,筆者本人的意見是不一定什麼功能都自己寫,但框架最好是自己搭。雖然網上有很多非常成熟好用的完整框架,但直接「拿來主義」的話可能有 2 點不妥之處——

框架提供的功能你未必都用得到。比如你只寫一個純閱讀型別的應用(不帶大資料收藏功能),那麼你就用不到本地資料庫,這樣完整框架裡有關資料庫的內容,就給白白浪費了。

高手也有疏忽時,即便技術大牛,也不敢保證自己寫的程式碼沒有任何 bug,在任何使用場景都健壯堅挺。如果某天突然發現完整框架有什麼 bug 或者侷限,自己又沒能力解決,到頭來只能重構大塊內容甚至整個專案,這代價就非常大了。

綜上,筆者更傾向新手「站在巨人的肩膀上搭積木」,用高手寫的不同功能庫,自己動手搭屬於自己的快速開發框架。而且在搭框架的過程中,你能不知不覺中學到很多進階知識,對自己的成長也很有利。

限於水平和篇幅,筆者只用老牌輪子Volley做例子,搭一個僅涉及網路請求和圖片載入的MVP框架。當下最流行的原生框架應屬RxJava + Retrofit + OkHttp + Dagger,如果你想了解得更多,推薦下面幾篇文章——

當然,這些庫本質和 Volley 一樣,都是去實現具體功能的輪子,而MVP 的架構是不變的,所以下文的內容對它們同樣適用。

動手開始

開啟 Android Studio,新建一個專案MvpFrameTest

。再在專案根目錄右鍵 new 一個 Module,選擇第二項 Android Library ,取名MVP

這時你會看到你的專案下面多了一個叫 mvp 的Module(和 app 一樣是加粗顯示的),不過角標是一個書架而非手機。這代表此模組是一個依賴庫,而非獨立執行的應用,我們今天主要的程式碼都寫在它裡面。


1.png

匯入依賴

點開mvp 下面的 build.gradle檔案(別錯搞成 app 下面的了哦),在dependency節點下面匯入我們要用的輪子——

compile'com.android.support:design:25.3.1'compile'com.android.volley:volley:1.0.0'compile'com.google.code.gson:gson:2.7'

這裡我希望內容儘量簡潔一點,因此只匯入設計適配(包含RecyclerView以及各種 Material Design 控制元件)、Volley(包含網路請求圖片載入)和Gson(包含Json 解析)三個庫。語句後面的版本號僅供參考,因為當你看到這篇文章時,建議使用的版本號可能又變了。

下面點開app 下面的 build.gradle檔案,同樣在dependency節點下面新增依賴——

compile project(path:':mvp')

點選提示行裡面的Sync Now,一會兒任務完成,從此以後你在 mvp 裡面依賴的庫(包括自己寫的各種類),app 就都可以用了。

建議把整體性的功能諸如網路請求、圖片載入等寫在 mvp 裡,具體性的實現諸如 UI 配色、訪問地址寫在 app 裡,這樣你的框架使用起來才更加靈活。

如果你對 Gradle 還不瞭解,推薦一篇文——

MVP

下面開始寫自己的東西了,由於我們想要的是 MVP 設計模式,首先應該完成通用的 M、V 和 P。對 MVP 不瞭解的推薦一篇文——

找到 mvp 下面的com.example.mvp包,如圖所示


2.png

在裡面新建兩個介面(Interface),分別取名BaseViewBaseModel

publicinterfaceBaseView{voidshowLoading();voidhideLoading();voidshowError();}

BaseView 裡面我們定義了三個抽象方法,分別用於顯示載入、隱藏載入和顯示載入失敗的內容。這些方法最終會交給你的檢視(也就是 Activity 或者 Fragment)去實現。

publicinterfaceBaseModel{}

BaseModel 裡面目前可以什麼都不寫。如果你參與一個團隊開發,介面和資料有比較統一的格式,那可以在此做一些規範工作。

OK,M 和 V 都有了,再新建一個抽象類,取名BasePresenter

publicabstractclassBasePresenter{protectedM mModel;protectedWeakReference mViewRef;protectedvoidonAttach(M model, V view){        mModel = model;        mViewRef =newWeakReference<>(view);    }protectedVgetView(){returnisViewAttached() ? mViewRef.get() :null;    }protectedbooleanisViewAttached(){returnnull!= mViewRef &&null!= mViewRef.get();    }protectedvoidonDetach(){if(null!= mViewRef) {            mViewRef.clear();            mViewRef =null;        }    }}

首先聲明瞭兩個泛型 M 和 V,M 對應要處理的 Model,V 則對應負責展示的View。由於 V 一般比較大,這裡採用了弱引用的寫法,避免記憶體洩漏。

isViewAttached()用於檢測 V 是否已關聯 P,為真則讓getView()返回對應的 V,否則返回 null。另外兩個方法負責 V 和 P 的關聯與解關聯,很簡單。

等等,你這不都是具體方法麼,為啥還要弄成抽象類?待會自見分曉。

應用入口

新建一個MyApp類,繼承 Application,用於獲取應用全域性的上下文。

publicclassMyAppextendsApplication{privatestaticMyApp instance;publicstaticMyAppgetInstance(){returninstance;    }@OverridepublicvoidonCreate(){super.onCreate();        instance =this;    }}

這個類是你整個應用的入口,一些你希望在應用一跑起來就立即完成的工作(比如初始化一些三方庫,包括 SDK),可以寫入它的 onCreate() 方法。

切記不要用 instance = new MyApp() 一類的賦值去獲取例項,這樣你得到的只是一個普通的 Java 類,不會具備任何 Application 的功能!

完成以後別忘了去app 模組的 AndroidManifest.xml,在 Application 節點下新增一行——

android:name="com.example.mvp.MyApp"

網路請求

前面已經說過,網路請求這類整體功能的封裝應寫入框架,這樣應用呼叫起來就很方便。這裡用的請求庫是Volley,不夠了解的請看這篇文——

這是一個系列文,共四篇,新手建議看完前三篇。

新建一個RequestManager類,用於管理網路請求。

publicclassRequestManager{privateRequestQueuequeue;privatestaticvolatileRequestManager instance;privateRequestManager(){queue= Volley.newRequestQueue(MyApp.getInstance());    }publicstaticRequestManagergetInstance(){if(instance == null) {            synchronized (RequestManager.class) {if(instance == null) {                    instance =newRequestManager();                }            }        }returninstance;    }publicRequestQueuegetRequestQueue(){returnqueue;    }}

這裡定義了一個請求佇列的物件,在構造器裡實例化,物件和構造器均設為私有,只暴露兩個 get 方法。因為請求佇列一個便夠(多了很浪費資源哦),這裡採用了雙重校驗鎖單例模式的寫法。不瞭解單例模式請看——

下面定製我們的專屬網路請求,網上大多數 API 返回資料都是 Json 物件,可以通過 Gson 很輕鬆的把它們轉換成 Java 物件。新建一個MyRequest類,繼承 Volley 裡面的 Request 類。

publicclassMyRequestextendsRequest{privateGson mGSon;privateClass mClass;privateResponse.Listener mListener;publicMyRequest(String url, Class clazz,

                    Response.Listener listener, Response.ErrorListener errorListener){this(Request.Method.GET, url, clazz, listener, errorListener);    }publicMyRequest(intmethod, String url, Class clazz,                    Response.Listener listener, Response.ErrorListener errorListener){super(method, url, errorListener);        mGSon =newGson();        mClass = clazz;        mListener = listener;    }@OverrideprotectedResponseparseNetworkResponse(NetworkResponse response){try{            String json =newString(response.data,                    HttpHeaderParser.parseCharset(response.headers));returnResponse.success(mGSon.fromJson(json, mClass),                    HttpHeaderParser.parseCacheHeaders(response));        }catch(UnsupportedEncodingException e) {returnResponse.error(newParseError(e));        }    }@OverrideprotectedvoiddeliverResponse(T response){        mListener.onResponse(response);    }}

程式碼看著不少,其實很好理解。首先我們想要的 Java 物件不確定,所以用一個泛型 T 去描述,並指定為與 Request 類的泛型相同。

構造器是重寫自父類,裡面例項化了馬上要講到的 Gson,然後過載了一個不帶請求型別的,此時預設請求型別為 GET。

接下來就是重寫 Request 類的parseNetworkResponse()和 ** deliverResponse()** 方法,前者用於解析請求到的響應(也就是返回資料),後者用於將響應傳遞給回撥介面mListener。解析時我們採用了Gson,它會強制我們處理UnsupportedEncodingException,最終返回的便是我們想要的 Java 物件。對 Gson 不瞭解請看——

這是一個系列文,共四篇,新手可以只看第一篇。

現在去處理響應,首先新建一個介面MyListener——

publicinterfaceMyListener{voidonSuccess(T result);voidonError(String errorMsg);    }

這是一個回撥,成功時攜帶泛型描述的 Java 物件,失敗時則攜帶錯誤資訊。

然後補充前面的 RequestManager,添加發送 GET 和 POST 請求的封裝。

publicvoidsendGet(String url, Class clazz,finalMyListener listener){        MyRequest request =newMyRequest<>(url, clazz,newResponse.Listener() {@OverridepublicvoidonResponse(T response){                listener.onSuccess(response);            }        },newResponse.ErrorListener() {@OverridepublicvoidonErrorResponse(VolleyError error){                listener.onError(error.getMessage());            }        });        addToRequestQueue(request);    }publicvoidsendPost(String url, Class clazz,finalHashMap map,finalMyListener listener){        MyRequest request =newMyRequest(Request.Method.POST, url, clazz,newResponse.Listener() {@OverridepublicvoidonResponse(T response){                listener.onSuccess(response);            }        },newResponse.ErrorListener() {@OverridepublicvoidonErrorResponse(VolleyError error){                listener.onError(error.getMessage());            }        }) {@OverrideprotectedMapgetParams()throwsAuthFailureError{returnmap;            }        };        addToRequestQueue(request);    }publicvoidaddToRequestQueue(Request req){        getRequestQueue().add(req);    }

網路請求搞定!這裡很明顯看出 Volley 的侷限,就是不支援 POST 大資料,因此不適合上傳檔案(下載檔案倒是可以通過 DownloadManager 實現)。如果你的專案有上傳檔案需求,應該轉戰 Retrofit 或 OkHttp。

圖片載入

這裡只用 Volley 自帶的 ImageLoader 模組實現圖片載入。該模組效能不錯,但功能不如 Glide 一類的專業圖片載入框架豐富,大家可根據需求自行選擇合適的輪子。新手推薦看下面這篇文——

新建一個ImageUtil類,用於封裝圖片載入。

publicclassImageUtil{publicstaticvoidloadImage(String url, ImageView iv,intplaceHolder,interrorHolder){        ImageLoader loader =newImageLoader(                RequestManager.getInstance().getRequestQueue(),newBitmapCache());if(ivinstanceofNetworkImageView) {            ((NetworkImageView) iv).setDefaultImageResId(placeHolder);            ((NetworkImageView) iv).setErrorImageResId(errorHolder);            ((NetworkImageView) iv).setImageUrl(url, loader);        }else{            ImageLoader.ImageListener listener = ImageLoader.getImageListener(iv,                    placeHolder, errorHolder);            loader.get(url, listener);        }    }privatestaticclassBitmapCacheimplementsImageLoader.ImageCache{privateLruCache cache;privatefinalintmaxSize =10*1024*1024;//快取大小設為10MBitmapCache() {            cache =newLruCache(maxSize) {@OverrideprotectedintsizeOf(String key, Bitmap value){returnvalue.getByteCount() /1024;                }            };        }@OverridepublicBitmapgetBitmap(String url){returncache.get(url);        }@OverridepublicvoidputBitmap(String url, Bitmap bitmap){            cache.put(url, bitmap);        }    }}

首先寫了一個內部類BitmapCache(因為工具類對外方法是靜態的,所以它也應是靜態),實現 Volley 的 ImageCache 介面並重寫方法。這裡採用了 LruCache 實現圖片快取,不瞭解請看這篇文——

然後暴露一個loadImage()方法給外部呼叫。Volley 帶有一個 繼承自 ImageView 的控制元件NetworkImageView,並有一套專屬的載入流程,因此在 loadImage() 方法裡,針對它和原生 ImageView 做了區分。

OK,圖片載入也搞定了。回首一看我們已寫了不少類和介面,整理一下吧,如下圖示。這已經是一個還算像樣的 MVP 快速開發框架了。


3.png

補充潤色

繼續新增輪子。我們都知道 MVP 的優點,但它也是有不少坑的——

類爆炸,這也是 MVP 最受詬病之處。嚴格的 MVP 寫法下,每寫 1 個頁面(不算介面卡和實體),要為之建立 8 個類。

P 應當具備和 V 相似的生命週期,但在眾多 V 裡一個個呼叫 onAttach() 和 onDetach() 一個個關聯解關聯,顯然是重複勞動。

有些 V 的展現內容是共通的,比如進度條、空白頁。

另外實際開發中我們還有一些需求,簡單列舉 2 個——

View 載入控制元件和資料的邏輯有時會很多,混雜一起閱讀相當不方便。

應用要求單擊返回鍵只彈出提示警告,雙擊才是回到桌面。

現在我們就來解決它們。

首先在 util 目錄下新建兩個類,分別取名ToastUtilReflectUtil

publicclassToastUtil{privatestaticToast toast;publicstaticvoidshowToast(String text){if(toast ==null) {            toast = Toast.makeText(MyApp.getInstance(), text, Toast.LENGTH_SHORT);        }else{            toast.setText(text);        }        toast.show();    }}

該類用於顯示一段土司(原生的介面有不妥之處,連續點選會連續土司)。

publicclassReflectUtil{publicstaticTgetT(Object o,inti){try{return((Class) ((ParameterizedType)                    (o.getClass().getGenericSuperclass())).getActualTypeArguments()[i]).newInstance();        }catch(Exception e) {            e.printStackTrace();        }returnnull;    }}

該類則用於反射獲取指定泛型。

然後在 base 目錄下新建兩個抽象類BaseActivityBaseMvpActivity,前者繼承 AppCompatActivity,並實現我們寫的 BaseView;後者繼承前者。

publicabstractclassBaseActivityextendsAppCompatActivityimplementsBaseView{@OverrideprotectedvoidonCreate(@Nullable Bundle savedInstanceState){super.onCreate(savedInstanceState);        setContentView(getLayoutId());        initView();    }protectedabstractintgetLayoutId();protectedabstractvoidinitView();@OverridepublicvoidshowLoading(){    }@OverridepublicvoidhideLoading(){    }@OverridepublicvoidshowError(){    }@OverridepublicbooleanonKeyDown(intkeyCode, KeyEvent event){returncheckBackAction() ||super.onKeyDown(keyCode, event);    }//雙擊退出相關privatebooleanmFlag =false;privatelongmTimeout = -1;privatebooleancheckBackAction(){longtime =3000L;//判定時間設為3秒booleanflag = mFlag;        mFlag =true;booleantimeout = (mTimeout == -1|| (System.currentTimeMillis() - mTimeout) > time);if(mFlag && (mFlag != flag || timeout)) {            mTimeout = System.currentTimeMillis();            ToastUtil.showToast("再點選一次回到桌面");returntrue;        }return!mFlag;    }}

有時我們的活動只是一個靜態的容器(比如歡迎頁),這時其實是沒必要使用 MVP 的。所以把包括 UI 的邏輯(雙擊退出)封裝在此。BaseView 裡面的方法也在此重寫,簡明起見,就不具體實現了。

另外為了提升可讀性,BaseActivity 添加了兩個抽象方法getLayoutId()initView()。子類在重寫時,將前者的返回值改為佈局 ID,在後者中進行初始化(findViewById、setOnClickListener)即可。如果子類不在 onCreate() 方法裡幹其它事,重寫 onCreate() 一步也可以省略。

皮埃斯:如果你用了 ButterKnife、Dagger 等依賴注入框架,初始化和解綁(去 onDestory() 方法)工作同樣可以在這個 BaseActivity 裡完成。

有意思的是如果你在子類裡用了 Android Studio 一款關於 ButterKnife 的助手外掛(人氣很高的說),它依然會很「認真負責」的幫你重寫 onCreate() 和 onDestory()…… 只有自己動手咔嚓掉了。

publicabstractclassBaseMvpActivityextendsBaseActivity{protectedT mPresenter;protectedM mModel;@OverrideprotectedvoidonCreate(@Nullable Bundle savedInstanceState){super.onCreate(savedInstanceState);        mPresenter = ReflectUtil.getT(this,0);        mModel = ReflectUtil.getT(this,1);        mPresenter.onAttach(mModel,this);    }@OverrideprotectedvoidonStart(){super.onStart();        loadData();    }protectedabstractvoidloadData();@OverrideprotectedvoidonDestroy(){super.onDestroy();        mPresenter.onDetach();    }}

遇到動態的,有資料請求和處理的頁面,再讓 MVP 出馬。這個 BaseMvpActivity 繼承了 BaseActivity,因此包含了裡面全部功能,同時又添加了一個抽象方法loadData(),有關資料互動的方法寫在裡面即可。

舉一反三,如果要讓碎片也能選擇性使用 MVP,你應該能寫出對應的 BaseFragment 和 BaseMvpFragment 來了吧?

最後在 base 下建立介面MvpListener,用於資料從 M 到 V 的層間傳遞。

publicinterfaceMvpListener{voidonSuccess(T result);voidonError(String errorMsg);}

好了,屬於你的簡易 MVP 快速開發框架已經搭建完成,撒花慶祝一下吧。


4.png

開車上路

現在就在 app 模組中寫個「知乎日報」測試測試,順便也學習一下 MVP 杜絕類爆炸的使用姿勢。簡明起見,只用一個 RecyclerView 請求今天的內容(圖片 + 標題),不再涉及詳情。

首先建立知乎日報的實體類DailyBean。推薦用Postman做請求,然後用 Android Studio 的外掛Gson Format自動生成。

publicclassDailyBean{privateString date;privateList stories;publicStringgetDate(){returndate;    }publicvoidsetDate(String date){this.date = date;    }publicListgetStories(){returnstories;    }publicvoidsetStories(List stories){this.stories = stories;    }publicstaticclassStoriesBean{privateinttype;privateintid;privateString ga_prefix;privateString title;privatebooleanmultipic;privateList images;publicintgetType(){returntype;        }publicvoidsetType(inttype){this.type = type;        }publicintgetId(){returnid;        }publicvoidsetId(intid){this.id = id;        }publicStringgetGa_prefix(){returnga_prefix;        }publicvoidsetGa_prefix(String ga_prefix){this.ga_prefix = ga_prefix;        }publicStringgetTitle(){returntitle;        }publicvoidsetTitle(String title){this.title = title;        }publicbooleanisMultipic(){returnmultipic;        }publicvoidsetMultipic(booleanmultipic){this.multipic = multipic;        }publicListgetImages(){returnimages;        }publicvoidsetImages(List images){this.images = images;        }    }}

然後建立一個契約介面DailyContract,這是 Google 推薦的類爆炸解決方案(不過筆者此處並沒嚴格按照官方要求去執行)——

publicinterfaceDailyContract{interfaceDailyModelextendsBaseModel{voidloadDaily(String url, MvpListener> listener);    }interfaceDailyViewextendsBaseView{voidsetData(List beanList);    }abstractclassDailyPresenterextendsBasePresenter{protectedabstractvoidloadData(String url);    }}

接口裡同時承載了 Daily 這個模組的 M,V 和 P(現在明白為何一開始要把 BasePresenter 弄成抽象類了吧),並且定義了方法規則。

下面開始具體實現這三層,首先是 P 層,建立一個DailyPresenterImpl類,讓它繼承契約裡面的 DailyPresenter。

publicclassDailyPresenterImplextendsDailyContract.DailyPresenter{@OverridepublicvoidloadData(String url){finalDailyContract.DailyView mView = getView();if(mView ==null) {return;        }        mView.showLoading();        mModel.loadDaily(url,newMvpListener>() {@OverridepublicvoidonSuccess(List result){                mView.hideLoading();                mView.setData(result);            }@OverridepublicvoidonError(String errorMsg){                mView.hideLoading();                mView.showError();            }        });    }}

邏輯很簡單,首先拿到契約裡 DailyView 的例項 mView,做非空判斷,然後呼叫 showLoading() 方法顯示載入進度條。

此後呼叫 mModel(也就是契約裡 DailyModel 的例項)的 loadDaily() 方法,出結果後告知 mView,首先關閉進度條。成功則執行 setData() 展示資料,失敗則執行 showError() 展示錯誤資訊。

建立DailyModelImpl類,繼承契約裡的 DailyModel。

publicclassDailyModelImplimplementsDailyContract.DailyModel{@OverridepublicvoidloadDaily(String url,finalMvpListener> listener){        RequestManager.getInstance().sendGet(url, DailyBean.class,newMyListener() {@OverridepublicvoidonSuccess(DailyBean result){                listener.onSuccess(result.getStories());            }@OverridepublicvoidonError(String errorMsg){                listener.onError(errorMsg);            }        });    }}

這裡具體實現 loadDaily() 方法去請求資料,具體途徑當然是之前我們封裝的網路請求類。成功則執行 MvpListener 的成功回撥,失敗則執行失敗回撥。

建立我們用於展示的條目佈局檔案item_daily

這裡我沒新增分割線,其實也不推薦直接在 item 里加分割線。

這裡插播 2 個小知識——

在層級相同時,FrameLayout 的效能略高於 LinearLayout,LinearLayout 又略高於RelativeLayout。對應的百分比佈局同理。

約束佈局能保證佈局層級始終為 1,如果你的 item 很複雜,有必要考慮一下它。如果你不習慣拖拖拽拽,可以先寫 XML 再轉換。

建立知乎日報的介面卡DailyAdapter。這裡我用了 RecyclerView,因為它的依賴已經包含在了 mvp 裡,app 裡就不用再重複聲明瞭。

publicclassDailyAdapterextendsRecyclerView.Adapter{privateContext context;privateList beanList;publicDailyAdapter(Context context){this.context = context;        beanList =newArrayList<>();    }publicvoidsetBeanList(List list){this.beanList.addAll(list);        notifyDataSetChanged();    }@OverridepublicDailyHolderonCreateViewHolder(ViewGroup parent,intviewType){returnnewDailyHolder(LayoutInflater.from(context)                .inflate(R.layout.item_daily, parent,false));    }@OverridepublicvoidonBindViewHolder(DailyHolder holder,intposition){        DailyBean.StoriesBean bean = beanList.get(position);        holder.tv.setText(bean.getTitle());        ImageUtil.loadImage(bean.getImages().get(0), holder.iv,                R.mipmap.ic_launcher_round, R.mipmap.ic_launcher_round);    }@OverridepublicintgetItemCount(){returnbeanList.size();    }staticclassDailyHolderextendsRecyclerView.ViewHolder{        TextView tv;        NetworkImageView iv;        DailyHolder(View itemView) {super(itemView);            tv = (TextView) itemView.findViewById(R.id.item_daily_tv);            iv = (NetworkImageView) itemView.findViewById(R.id.item_daily_iv);        }    }}

簡單起見我們只加載當天的全部內容。onBindViewHolder() 方法裡面用到了之前封裝的圖片工具,佔位圖就簡單用小機器人代替了。

建立主介面的佈局檔案activity_main

建立一個日期工具類DateUtil,封裝日期格式化流程。

聰明如你,應該知道這個類是放 app 更好,還是放 mvp 更好吧?

publicclassDateUtil{privatestaticfinalLocale LOCALE = Locale.CHINA;publicstaticStringformat(Date date, String s){returnnewSimpleDateFormat(s, LOCALE).format(date);    }}

養成好習慣,建立一個類Api,統一管理訪問介面。

publicclassApi{publicstaticfinalString DAILY_HISTORY ="http://news.at.zhihu.com/api/4/news/before/";}

最後寫展示用的類MainActivity,也就是 MVP 的 V 層。繼承 BaseMvpActivity 並實現契約裡的 DailyView。

publicclassMainActivityextendsBaseMvpActivityimplementsDailyContract.DailyView{privateDailyAdapter adapter;@OverrideprotectedintgetLayoutId(){returnR.layout.activity_main;    }@OverrideprotectedvoidinitView(){        adapter =newDailyAdapter(this);        RecyclerView rcv = (RecyclerView) findViewById(R.id.ac_main_rcv);        rcv.setLayoutManager(newLinearLayoutManager(this));        rcv.setHasFixedSize(true);        rcv.setAdapter(adapter);    }@OverrideprotectedvoidloadData(){        mPresenter.loadData(Api.DAILY_HISTORY                + DateUtil.format(newDate(),"yyyyMMdd"));    }@OverridepublicvoidsetData(List beanList){        adapter.setBeanList(beanList);    }}

由於無須在活動建立時做其它事,onCreate() 方法可以不重寫了。其它 4 個重寫方法依次負責佈局檔案,初始化控制元件,請求和展示資料,一目瞭然。

最後別忘了在AndroidManifest裡新增網路訪問許可權——

OK,可以跑應用了~~


1.gif

實際效果比 gif 更好,Volley 做純閱讀應用還是比較給力的。

再看一看我們搭好框架後真正寫的程式碼(筆者做了歸類整理)——


5.png

除去介面卡和實體類,一個頁面我們只寫了 4 個類,有效解決了類爆炸;如果是類似歡迎頁那樣不涉及互動的,那直接繼承 BaseActivity 即可,不再用 MVP 模式寫了,這樣一個頁面只須寫 1 個類。

本文結束,歡迎指教 and  拍磚~~

作者:彼岸sakura

連結:https://www.jianshu.com/p/965e67222454

來源:簡書

著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。