先定一个小目标!好比说先用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写,须要源码的能够联系我