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

前言

使用這個標題先表示對老王的尊敬
api所有開放 可是服務器使用的是美國服務器 訪問速度特別慢 只用於學習
快速開發框架是我整理出來的一套框架 使用簡單 實現快速 GitHub地址,喜歡的童鞋歡迎star
MVP是一種開發模式 按照你本身理解和編程習慣的去實現就好 沒有必要一股腦的照搬
可能理論什麼的我也不蠻會說,接下來了部分,我帶你真正的打一場戰役
看到這裏若是你感興趣我建議你先下載app跑一遍,知道咱們須要作的是什麼
項目的源碼地址Freebookjavascript

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


效果圖


目錄

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

底層框架搭建

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

在這裏我要先介紹一下個人底層框架LCRapidDevelop,這個框架能幹嗎呢?java

  • 異常奔潰統一友好管理 無需擔憂程序出現異常而遺失用戶
  • 頁面狀態 加載中 加載失敗 無數據快速實現
  • 下拉刷新以及自動加載
  • RecyclerView的相關封裝快速實現item動畫adapter的編寫
  • Tab+Fragment快速實現效果Duang Duang Duang 可按照需求隨意修改爲本身想要的
  • 視頻播放快速實現 這個功能是今天咱們須要編寫的app惟一一個用不到的東西 我會考慮去除這個東西

功能呢列舉到這裏就差很少了,接下來咱們須要把LCRapidDevelop添加到咱們的項目裏並編譯項目 react

項目結構

導入後編譯一下若是沒有報錯咱們進行下一步,新建好相應的文件夾android

  • Constant --- 用於存放常量數據
  • Data --- 編寫數據請求相關代碼
  • Dialog ---編寫自定義對話框
  • MVP --- 全部頁面都些這裏 等等我會針對這個進行解釋
  • MyApplication ---存放自定義Application
  • Util ---存放工具類
  • Widget --存在自定義view

而後就是Application的編寫了git

/* *自定義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"github

而後就是BaseActivity和BaseFragment的編寫了
在MVP文件夾內新建文件夾Base 而後新建BaseActivity.classspring

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文件

網絡請求框架結構

  • 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") Observable 
               
               
                 < 
                String>>> 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) Observable 
               
               
                 < 
                String>>> getHotLable(Observable 
                 
                   < 
                  String>> 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(Observer 
          
            < 
           String>> observer){ Observable observable=service.getHotLable().map(new HttpResultFunc 
            
              < 
             String>>());; Observable observableCache=providers.getHotLable(observable,new DynamicKey("搜索熱門標籤"), new EvictDynamicKey(false)).map(new HttpResultFuncCcche 
              
                < 
               String>>()); 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

  • Adapter ---存放適配器
  • Base ---存放BaseActivity等等
  • Entity ---存放實體
  • BookInfo Home Search 本應該是單獨存放到一塊兒的 這個是功能 可是這個app呢比較小功能也少我就懶得分開了

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

這一個查看書籍詳情的功能

  • 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.comapi你們能夠隨意使用 可是用的是美國服務器,會比較的慢,api是用spring mvc寫,須要源碼的能夠聯繫我

相關文章
相關標籤/搜索