先定一個小目標!比如說先用MVP和快速開發框架打造一個免費下載小說的app老司機來手把手教你半天搞定

Lance_小超發表於2019-03-04

前言

使用這個標題先表示對老王的尊敬
api全部開放 但是伺服器使用的是美國伺服器 訪問速度特別慢 只用於學習
快速開發框架是我整理出來的一套框架 使用簡單 實現快速 GitHub地址,喜歡的童鞋歡迎star
MVP是一種開發模式 按照你自己理解和程式設計習慣的去實現就好 沒有必要一股腦的照搬
可能理論什麼的我也不蠻會說,接下來了部分,我帶你真正的打一場戰役
看到這裡如果你感興趣我建議你先下載app跑一遍,知道我們需要做的是什麼
專案的原始碼地址Freebook

這裡有那麼一群志同道合的人在等你加入 QQ群:173999252

先定一個小目標!比如說先用MVP和快速開發框架打造一個免費下載小說的app老司機來手把手教你半天搞定


效果圖

先定一個小目標!比如說先用MVP和快速開發框架打造一個免費下載小說的app老司機來手把手教你半天搞定
先定一個小目標!比如說先用MVP和快速開發框架打造一個免費下載小說的app老司機來手把手教你半天搞定
先定一個小目標!比如說先用MVP和快速開發框架打造一個免費下載小說的app老司機來手把手教你半天搞定
先定一個小目標!比如說先用MVP和快速開發框架打造一個免費下載小說的app老司機來手把手教你半天搞定
先定一個小目標!比如說先用MVP和快速開發框架打造一個免費下載小說的app老司機來手把手教你半天搞定


目錄

  • 底層框架搭建
  • 網路請求框架搭建
  • MVP模式實現
  • 使用的第三方框架介紹

底層框架搭建

萬事開頭難,實質上只要你走出第一步了,後面的路就能迎刃而解

在這裡我要先介紹一下我的底層框架LCRapidDevelop,這個框架能幹嘛呢?

  • 異常奔潰統一友好管理 無需擔心程式出現異常而遺失使用者
  • 頁面狀態 載入中 載入失敗 無資料快速實現
  • 下拉重新整理以及自動載入
  • RecyclerView的相關封裝快速實現item動畫adapter的編寫
  • Tab+Fragment快速實現效果Duang Duang Duang 可按照需求隨意修改成自己想要的
  • 視訊播放快速實現 這個功能是今天我們需要編寫的app唯一一個用不到的東西 我會考慮去除這個東西

功能呢列舉到這裡就差不多了,接下來我們需要把LCRapidDevelop新增到我們的專案裡並編譯專案

先定一個小目標!比如說先用MVP和快速開發框架打造一個免費下載小說的app老司機來手把手教你半天搞定
專案結構

匯入後編譯一下如果沒有報錯我們進行下一步,新建好相應的資料夾

先定一個小目標!比如說先用MVP和快速開發框架打造一個免費下載小說的app老司機來手把手教你半天搞定

  • Constant --- 用於存放常量資料
  • Data --- 編寫資料請求相關程式碼
  • Dialog ---編寫自定義對話方塊
  • MVP --- 所有頁面都些這裡 等等我會針對這個進行解釋
  • MyApplication ---存放自定義Application
  • Util ---存放工具類
  • Widget --存在自定義view

然後就是Application的編寫了

/*
 *自定義Application
 * 用於初始化各種資料以及服務
 *  */

public class MyApplication extends Application {
    //記錄當前棧裡所有activity
    private List activities = new ArrayList();
    @Override
    public void onCreate() {
        super.onCreate();
        instance = this;

        //異常友好管理初始化
        Recovery.getInstance()
                .debug(true)
                .recoverInBackground(false)
                .recoverStack(true)
                .mainPage(WelcomeActivity.class)
//                .skip(H5PayActivity.class)  如果應用整合支付寶支付 記得加上這句程式碼  沒時間解釋了  快上車  老司機發車了
                .init(this);
    }
    /**
     * 應用例項
     **/
    private static MyApplication instance;

    /**
     * 獲得例項
     *
     * @return
     */
    public static MyApplication getInstance() {
        return instance;
    }

    /**
     * 新建了一個activity
     *
     * @param activity
     */
    public void addActivity(Activity activity) {
        activities.add(activity);
    }

    /**
     * 結束指定的Activity
     *
     * @param activity
     */
    public void finishActivity(Activity activity) {
        if (activity != null) {
            this.activities.remove(activity);
            activity.finish();
            activity = null;
        }
    }
    /**
     * 應用退出,結束所有的activity
     */
    public void exit() {
        for (Activity activity : activities) {
            if (activity != null) {
                activity.finish();
            }
        }
        System.exit(0);
    }

}複製程式碼

並且在AndroidManifest.xml中使用這個android:name=".MyApplication.MyApplication"

然後就是BaseActivity和BaseFragment的編寫了
在MVP資料夾內新建資料夾Base 然後新建BaseActivity.class

public abstract class BaseActivity extends AppCompatActivity implements View.OnClickListener {
    protected Context mContext;
    private ConnectivityManager manager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);// 鎖定豎屏
        mContext = getActivityContext();
        initView();
        ButterKnife.bind(this);
        initdata();
        MyApplication.getInstance().addActivity(this);
    }
    /**
     * 初始activity方法
     */
    private void initView() {
        loadViewLayout();
    }
    private void initdata(){
        findViewById();
        setListener();
        processLogic();
    }
    @Override
    protected void onDestroy() {
        // TODO Auto-generated method stub
        super.onDestroy();
        MyApplication.getInstance().finishActivity(this);
    }
    /**
     * 載入頁面layout
     */
    protected abstract void loadViewLayout();

    /**
     * 載入頁面元素
     */
    protected abstract void findViewById();

    /**
     * 設定各種事件的監聽器
     */
    protected abstract void setListener();

    /**
     * 業務邏輯處理,主要與後端互動
     */
    protected abstract void processLogic();


    /**
     * Activity.this
     */
    protected abstract Context getActivityContext();

    /**
     * 彈出Toast
     * 
     * @param text
     */
    public void showToast(String text) {
        Toast toast = Toast.makeText(this, text, Toast.LENGTH_SHORT);
        toast.setGravity(Gravity.CENTER, 0, 0);
        toast.show();
    }
    public boolean checkNetworkState() {
        boolean flag = false;
        //得到網路連線資訊
        manager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
        //去進行判斷網路是否連線
        if (manager.getActiveNetworkInfo() != null) {
            flag = manager.getActiveNetworkInfo().isAvailable();
        }
        return flag;
    }
}複製程式碼

然後就是BaseFragment.class

/**
 * 這個是最簡單的 大家實際使用時 可新增我自想要的元素
 */
public abstract class BaseFragment extends Fragment{

    private View mRootView;
    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        mRootView = initView(inflater,container);
        ButterKnife.bind(this, mRootView);//繫結到butterknife
        return mRootView;
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        initListener();
        initData();
    }

    protected abstract View initView(LayoutInflater inflater,ViewGroup container);
    protected abstract void initListener();
    protected abstract void initData();
}複製程式碼

到這裡基本上底層框架搭建就搭建好了,如果熟練了的話,這個過程複製貼上不到兩分鐘就能搞定, 第一次搭建的話算個10分鐘吧


網路請求框架搭建

網路請求框架實質上就是上面我們提到的Data檔案

先定一個小目標!比如說先用MVP和快速開發框架打造一個免費下載小說的app老司機來手把手教你半天搞定
網路請求框架結構

  • api -- 編寫網路請求的api以及快取api
  • db --SQLite資料的相關處理 這裡主要用於儲存下載資訊
  • HttpData ---統一網路請求處理
  • Retrofit ---是相關的配置 包括請求時彈出載入中對話方塊什麼的

網路請求採用的是 RxJava +Retrofit2.0 + okhttp +RxCache ,是目前最為主流也是個人認為最好用最高效的網路請求 首先相應的包先導好

    compile 'io.reactivex:rxjava:1.1.8'
    compile 'io.reactivex:rxandroid:1.2.1'
    compile 'com.squareup.retrofit2:retrofit:2.0.0-beta4'
    compile 'com.squareup.retrofit2:converter-gson:2.0.0-beta4'
    compile 'com.squareup.retrofit2:adapter-rxjava:2.0.0-beta4'
    compile 'com.github.VictorAlbertos.RxCache:core:1.4.6'複製程式碼

關於這個網路請求框架的搭建我就不細說了,掘金的文章太多了,懶得去了解的朋友呢直接複製我的程式碼,我教你怎麼使用好了,就跟我賜予你一把寶劍,知道使用就好乾嘛還要去了解寶劍是怎麼製造的,哈哈 當然這是一句玩笑話啦
首先是BookService.class的編寫 api文件地址

/**
 * API介面
 * 因為使用RxCache作為快取策略 所以這裡不需要寫快取資訊
 */
public interface BookService {
    //獲取首頁詳情
    @GET("api/getHomeInfo")
    Observable> getHomeInfo();

    //獲取書籍詳情
    @GET("api/getBookInfo")
    Observable> getBookInfo(@Query("id") int id);

    //獲取類別列表
    @GET("api/getTypeConfigList")
    Observable>> getTypeList();

    //根據類別獲取書籍列表
    @GET("api/getTypeBooks")
    Observable>> getBookList(@Query("type")int type,@Query("pageIndex")int pageIndex);

    //根據關鍵詞獲取搜尋書籍列表
    @GET("api/getSearchList")
    Observable>> getSearchList(@Query("key")String key);

    //獲取熱門搜尋標籤
    @GET("api/getSearchLable")
    ObservableString>>> getHotLable();
}複製程式碼

然後就是快取api的編寫CacheProviders.class

/**
 * 快取API介面
 * @LifeCache設定快取過期時間. 如果沒有設定@LifeCache , 資料將被永久快取理除非你使用了 EvictProvider, EvictDynamicKey or EvictDynamicKeyGroup .
 * EvictProvider可以明確地清理清理所有快取資料.
 * EvictDynamicKey可以明確地清理指定的資料 DynamicKey.
 * EvictDynamicKeyGroup 允許明確地清理一組特定的資料. DynamicKeyGroup.
 * DynamicKey驅逐與一個特定的鍵使用EvictDynamicKey相關的資料。比如分頁,排序或篩選要求
 * DynamicKeyGroup。驅逐一組與key關聯的資料,使用EvictDynamicKeyGroup。比如分頁,排序或篩選要求
 */
public interface CacheProviders {
    //獲取書庫對應類別書籍列表  快取時間 1天
    @LifeCache(duration = 7, timeUnit = TimeUnit.DAYS)
    Observable>> getBookList(Observable> oRepos, DynamicKey userName, EvictDynamicKey evictDynamicKey);

    //獲取書庫分類資訊快取資料 快取時間 永久
    Observable>> getTypeList(Observable> oRepos, DynamicKey userName, EvictDynamicKey evictDynamicKey);

    //獲取首頁配置資料 banner 最熱 最新  快取時間7天
    @LifeCache(duration = 7, timeUnit = TimeUnit.DAYS)
    Observable> getHomeInfo(Observable oRepos, DynamicKey userName, EvictDynamicKey evictDynamicKey);

    //獲取搜尋標籤  快取時間7天
    @LifeCache(duration = 7, timeUnit = TimeUnit.DAYS)
    ObservableString>>> getHotLable(ObservableString>> oRepos, DynamicKey userName, EvictDynamicKey evictDynamicKey);

    //獲取書籍詳情  快取時間7天
    @LifeCache(duration = 7, timeUnit = TimeUnit.DAYS)
    Observable> getBookInfo(Observable oRepos, DynamicKey userName, EvictDynamicKey evictDynamicKey);

    //根據關鍵詞獲取搜素列表  快取時間1天
    @LifeCache(duration = 1, timeUnit = TimeUnit.DAYS)
    Observable> getSearchList(Observable oRepos, DynamicKey userName, EvictDynamicKey evictDynamicKey);
}複製程式碼

最後就是HttpData.class的使用了

/*
 *所有的請求資料的方法集中地
 * 根據MovieService的定義編寫合適的方法
 * 其中observable是獲取API資料
 * observableCahce獲取快取資料
 * new EvictDynamicKey(false) false使用快取  true 載入資料不使用快取
 */
public class HttpData extends RetrofitUtils {

    private static File cacheDirectory = FileUtil.getcacheDirectory();
    private static final CacheProviders providers = new RxCache.Builder()
            .persistence(cacheDirectory)
            .using(CacheProviders.class);
    protected static final BookService service = getRetrofit().create(BookService.class);

    //在訪問HttpMethods時建立單例
    private static class SingletonHolder {
        private static final HttpData INSTANCE = new HttpData();
    }

    //獲取單例
    public static HttpData getInstance() {
        return SingletonHolder.INSTANCE;
    }

    //獲取app書本類別
    public void getBookTypes(Observer> observer){
        Observable observable=service.getTypeList().map(new HttpResultFunc>());
        Observable observableCahce=providers.getTypeList(observable,new DynamicKey("書本類別"),new EvictDynamicKey(false)).map(new HttpResultFuncCcche>());
        setSubscribe(observableCahce,observer);
    }
    //獲取app首頁配置資訊  banner  最新 最熱
    public void getHomeInfo(boolean isload,Observer observer){
        Observable observable=service.getHomeInfo().map(new HttpResultFunc());;
        Observable observableCache=providers.getHomeInfo(observable,new DynamicKey("首頁配置"),new EvictDynamicKey(isload)).map(new HttpResultFuncCcche());
        setSubscribe(observableCache,observer);
    }
    //獲得搜尋熱門標籤
    public void getSearchLable(ObserverString>> observer){
        Observable observable=service.getHotLable().map(new HttpResultFuncString>>());;
        Observable observableCache=providers.getHotLable(observable,new DynamicKey("搜尋熱門標籤"), new EvictDynamicKey(false)).map(new HttpResultFuncCccheString>>());
        setSubscribe(observableCache,observer);
    }
    //根據型別獲取書籍集合
    public void getBookList(int bookType, int pageIndex, Observer> observer) {
        Observable observable = service.getBookList(bookType,pageIndex).map(new HttpResultFunc>());
        Observable observableCache=providers.getBookList(observable,new DynamicKey("getStackTypeHtml"+bookType+pageIndex), new EvictDynamicKey(false)).map(new HttpResultFuncCcche>());
        setSubscribe(observableCache, observer);
    }
    //根據關鍵字搜尋書籍
    public void getSearchList(String key,Observer> observer){
        try {
            //中文記得轉碼  不然會亂碼  搜尋不出想要的效果
            key = URLEncoder.encode(key, "utf-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        Observable observable=service.getSearchList(key).map(new HttpResultFunc>());
        Observable observableCache=providers.getSearchList(observable,new DynamicKey("getSearchList&"+key), new EvictDynamicKey(false)).map(new HttpResultFuncCcche>());
        setSubscribe(observableCache, observer);
    }
    //獲取書籍詳情
    public void getBookInfo(int id, Observer observer){
        Observable observable=service.getBookInfo(id).map(new HttpResultFunc());
        Observable observableCache=providers.getBookInfo(observable,new DynamicKey("getBookInfo&"+id), new EvictDynamicKey(false)).map(new HttpResultFuncCcche());
        setSubscribe(observableCache, observer);
    }

    /**
     * 插入觀察者
     *
     * @param observable
     * @param observer
     * @param 
     */
    public static  void setSubscribe(Observable observable, Observer observer) {
        observable.subscribeOn(Schedulers.io())
                .subscribeOn(Schedulers.newThread())//子執行緒訪問網路
                .observeOn(AndroidSchedulers.mainThread())//回撥到主執行緒
                .subscribe(observer);
    }
    /**
     * 用來統一處理Http的resultCode,並將HttpResult的Data部分剝離出來返回給subscriber
     *
     * @param    Subscriber真正需要的資料型別,也就是Data部分的資料型別
     */
    private  class HttpResultFunc<T> implements Func1<HttpResult<T>, T> {

        @Override
        public T call(HttpResult httpResult) {
            if (httpResult.getCode() !=1 ) {
                throw new ApiException(httpResult);
            }
            return httpResult.getData();
        }
    }
    /**
     * 用來統一處理RxCacha的結果
     */
    private  class HttpResultFuncCcche<T> implements Func1<Reply<T>, T> {

        @Override
        public T call(Reply httpResult) {
            return httpResult.getData();
        }
    }

}複製程式碼

到這裡呢網路框架的搭建和使用介紹完了,這裡如果是複製貼上只需要修改api相關資料的還是不需要多少時間的,這裡我們算1小時吧


MVP模式實現

之前的專案結構我們也看到了,其中有一個資料夾就叫MVP

先定一個小目標!比如說先用MVP和快速開發框架打造一個免費下載小說的app老司機來手把手教你半天搞定

  • Adapter ---存放介面卡
  • Base ---存放BaseActivity等等
  • Entity ---存放實體
  • BookInfo Home Search 本應該是單獨存放到一起的 這個是功能 但是這個app呢比較小功能也少我就懶得分開了

mvp的實現方式很多很多,我呢是通過功能區分 對於MVP我想說的是 我是用的最好理解的方式去實現 老司機繞路 新手可以先在這個基礎上跑通 再去學習進階 學一個新東西最怕的就是在你接觸的時候 技術深度太深 讓你放棄治療

先定一個小目標!比如說先用MVP和快速開發框架打造一個免費下載小說的app老司機來手把手教你半天搞定

這一個檢視書籍詳情的功能

  • model---用於請求資料
  • view ---使用者互動和檢視顯示
  • presenter --負責完成View於Model間的邏輯和互動

首先我們需要確定BookInfoActivity有一些什麼樣的互動,比如說在載入的時候顯示載入頁面 網路異常時顯示異常頁面等等

當我們清這個壓面的互動和檢視的顯示是,我們就可以編寫BookInfoView.class (其實不是蠻清楚也可以的 你先把知道的加上,後面想起來了在新增就好了)

public interface BookInfoView {
    //顯示載入頁
    void showProgress();
    //關閉載入頁
    void hideProgress();
    //資料載入成功
    void newData(BookInfoDto data);
    //顯示載入失敗
    void showLoadFailMsg();
}複製程式碼

然後呢我們就要開始去編寫BookInfoModel.class 網路請求我們寫到這個裡面

/**
 * 獲取書籍詳情資料
 */
public class BookInfoModel {
    public void loadData(int id, final OnLoadDataListListener listener){
        HttpData.getInstance().getBookInfo(id, new Observer() {
            @Override
            public void onCompleted() {

            }

            @Override
            public void onError(Throwable e) {
                listener.onFailure(e);
            }

            @Override
            public void onNext(BookInfoDto bookInfoDto) {
                listener.onSuccess(bookInfoDto);
            }
        });
    }
}複製程式碼

最後就是BookInfoPresenter

public class BookInfoPresenter implements OnLoadDataListListener<BookInfoDto>{
    private BookInfoView mView;
    private BookInfoModel mModel;

    public BookInfoPresenter(BookInfoView mView) {
        this.mView = mView;
        mModel=new BookInfoModel();
    }

    public void loadData(int id){
        mModel.loadData(id,this);
        mView.showProgress();
    }

    @Override
    public void onSuccess(BookInfoDto data) {
        if(data.getBookName().equals("")){
            mView.showLoadFailMsg();
        }else{
            mView.newData(data);
            mView.hideProgress();
        }
    }

    @Override
    public void onFailure(Throwable e) {
        mView.showLoadFailMsg();
    }
}複製程式碼

最後就是BookInfoActivity對這些進行使用了,仔細看程式碼,Activity裡面將不會出現任何資料邏輯

public class BookInfoActivity extends BaseActivity implements BookInfoView {

    @BindView(R.id.book_info_toolbar_textview_title)
    TextView bookInfoToolbarTextviewTitle;
    .....
    private int  bookid;
    private BookInfoPresenter presenter;

    @Override
    protected void loadViewLayout() {
        setContentView(R.layout.activity_book_info);
    }

    @Override
    protected void findViewById() {
        Intent intent = getIntent();
        bookid = intent.getIntExtra("bookid",0);
    }

    public void initview(BookInfoDto data) {

        bookInfoTextviewName.setText(data.getBookName());
        .....資料顯示
    }

    @Override
    protected void setListener() {

    }

    @Override
    protected void processLogic() {
        presenter = new BookInfoPresenter(this);
        presenter.loadData(bookid);
    }

    @Override
    protected Context getActivityContext() {
        return this;
    }

    /*
    以下是BookInfoView定義的相關介面  activity是需要實現就好了
    */
    @Override
    public void showProgress() {//顯示載入頁
        bookInfoProgress.showLoading();
    }

    @Override
    public void hideProgress() {//顯示資料頁
        bookInfoProgress.showContent();
    }

    @Override
    public void newData(BookInfoDto data) {//
        initview(data);
    }

    @Override
    public void showLoadFailMsg() {
        toError();
    }

    public void toError() {
        bookInfoProgress.showError(getResources().getDrawable(R.mipmap.load_error), Constant.ERROR_TITLE, Constant.ERROR_CONTEXT, Constant.ERROR_BUTTON, new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                bookInfoProgress.showLoading();
                //重試
                presenter.loadData(bookid);
            }
        });
    }
}複製程式碼

MVP的使用大概就是這樣,新司機可以先按照我這種比較簡單易理解的方式先實現,當你實現了再去看深度比較深的MVP相關文章是,你就不會覺得很難理解了,這裡話的把app的功能都實現差不多要話2個小時左右


使用的第三方框架介紹

首頁介紹一下我們這個app都用到了哪些第三方框架

  • LCRapidDevelop ---底層框架
  • butterknife ---這個不需要解釋了吧
  • glide---圖片顯示快取框架
  • BGABanner ---支援大於等於1頁時的無限迴圈自動輪播、手指按下暫停輪播、抬起手指開始輪播
  • FileDownloader ---Android 檔案下載引擎,穩定、高效、簡單易用

每個框架我都提供了連結,感興趣的直接點進去檢視,畢竟人家寫的好詳細的,我就不多嘴了, 這些框架使用到專案裡主要是BGABanner和FileDownloader 加上頁面的編寫以及介面卡的編寫,差不哦兩小時左右


結語

從你開始構建這個專案開始,到這個專案結束,半天時間足以
先把東西先玩起來再去細緻的瞭解,會比你先詳細瞭解在開發要輕鬆的多
我沒有太多的耐心去寫的很細緻,但是你們有任何疑問可以發郵件給我mychinalance@gmail.com
api大家可以隨意使用 但是用的是美國伺服器,會比較的慢,api是用spring mvc寫,需要原始碼的可以聯絡我

相關文章