美文网首页android 项目开发Android学习开源项目
先定一个小目标!比如说先用MVP和快速开发框架打造一个免费下载小

先定一个小目标!比如说先用MVP和快速开发框架打造一个免费下载小

作者: lance_小超 | 来源:发表于2016-11-16 18:04 被阅读1002次

    前言

    使用这个标题先表示对老王的尊敬
    api全部开放 但是服务器使用的是美国服务器 访问速度特别慢 只用于学习
    快速开发框架是我整理出来的一套框架 使用简单 实现快速 GitHub地址,喜欢的童鞋欢迎star
    MVP是一种开发模式 按照你自己理解和编程习惯的去实现就好 没有必要一股脑的照搬
    可能理论什么的我也不蛮会说,接下来了部分,我带你真正的打一场战役
    看到这里如果你感兴趣我建议你先下载app跑一遍,知道我们需要做的是什么
    项目的源码地址Freebook

    这里有那么一群志同道合的人在等你加入 QQ群:173999252


    效果图


    目录

    • 底层框架搭建
    • 网络请求框架搭建
    • MVP模式实现
    • 使用的第三方框架介绍

    底层框架搭建

    万事开头难,实质上只要你走出第一步了,后面的路就能迎刃而解

    在这里我要先介绍一下我的底层框架LCRapidDevelop,这个框架能干嘛呢?

    • 异常奔溃统一友好管理 无需担心程序出现异常而遗失用户
    • 页面状态 加载中 加载失败 无数据快速实现
    • 下拉刷新以及自动加载
    • RecyclerView的相关封装快速实现item动画adapter的编写
    • Tab+Fragment快速实现效果Duang Duang Duang 可按照需求随意修改成自己想要的
    • 视频播放快速实现 这个功能是今天我们需要编写的app唯一一个用不到的东西 我会考虑去除这个东西

    功能呢列举到这里就差不多了,接下来我们需要把LCRapidDevelop添加到我们的项目里并编译项目

    项目结构

    导入后编译一下如果没有报错我们进行下一步,新建好相应的文件夹

    • Constant --- 用于存放常量数据
    • Data --- 编写数据请求相关代码
    • Dialog ---编写自定义对话框
    • MVP --- 所有页面都些这里 等等我会针对这个进行解释
    • MyApplication ---存放自定义Application
    • Util ---存放工具类
    • Widget --存在自定义view

    然后就是Application的编写了

    /*
     *自定义Application
     * 用于初始化各种数据以及服务
     *  */
    
    public class MyApplication extends Application {
        //记录当前栈里所有activity
        private List<Activity> activities = new ArrayList<Activity>();
        @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文件

    网络请求框架结构
    • 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<HttpResult<HomeDto>> getHomeInfo();
    
        //获取书籍详情
        @GET("api/getBookInfo")
        Observable<HttpResult<BookInfoDto>> getBookInfo(@Query("id") int id);
    
        //获取类别列表
        @GET("api/getTypeConfigList")
        Observable<HttpResult<List<BookTypeDto>>> getTypeList();
    
        //根据类别获取书籍列表
        @GET("api/getTypeBooks")
        Observable<HttpResult<List<BookInfoListDto>>> getBookList(@Query("type")int type,@Query("pageIndex")int pageIndex);
    
        //根据关键词获取搜索书籍列表
        @GET("api/getSearchList")
        Observable<HttpResult<List<BookInfoListDto>>> getSearchList(@Query("key")String key);
    
        //获取热门搜索标签
        @GET("api/getSearchLable")
        Observable<HttpResult<List<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<Reply<List<BookInfoListDto>>> getBookList(Observable<List<BookInfoListDto>> oRepos, DynamicKey userName, EvictDynamicKey evictDynamicKey);
    
        //获取书库分类信息缓存数据 缓存时间 永久
        Observable<Reply<List<BookTypeDto>>> getTypeList(Observable<List<BookTypeDto>> oRepos, DynamicKey userName, EvictDynamicKey evictDynamicKey);
    
        //获取首页配置数据 banner 最热 最新  缓存时间7天
        @LifeCache(duration = 7, timeUnit = TimeUnit.DAYS)
        Observable<Reply<HomeDto>> getHomeInfo(Observable<HomeDto> oRepos, DynamicKey userName, EvictDynamicKey evictDynamicKey);
    
        //获取搜索标签  缓存时间7天
        @LifeCache(duration = 7, timeUnit = TimeUnit.DAYS)
        Observable<Reply<List<String>>> getHotLable(Observable<List<String>> oRepos, DynamicKey userName, EvictDynamicKey evictDynamicKey);
    
        //获取书籍详情  缓存时间7天
        @LifeCache(duration = 7, timeUnit = TimeUnit.DAYS)
        Observable<Reply<BookInfoDto>> getBookInfo(Observable<BookInfoDto> oRepos, DynamicKey userName, EvictDynamicKey evictDynamicKey);
    
        //根据关键词获取搜素列表  缓存时间1天
        @LifeCache(duration = 1, timeUnit = TimeUnit.DAYS)
        Observable<Reply<BookInfoListDto>> getSearchList(Observable<BookInfoListDto> 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<List<BookTypeDto>> observer){
            Observable observable=service.getTypeList().map(new HttpResultFunc<List<BookTypeDto>>());
            Observable observableCahce=providers.getTypeList(observable,new DynamicKey("书本类别"),new EvictDynamicKey(false)).map(new HttpResultFuncCcche<List<BookTypeDto>>());
            setSubscribe(observableCahce,observer);
        }
        //获取app首页配置信息  banner  最新 最热
        public void getHomeInfo(boolean isload,Observer<HomeDto> observer){
            Observable observable=service.getHomeInfo().map(new HttpResultFunc<HomeDto>());;
            Observable observableCache=providers.getHomeInfo(observable,new DynamicKey("首页配置"),new EvictDynamicKey(isload)).map(new HttpResultFuncCcche<HomeDto>());
            setSubscribe(observableCache,observer);
        }
        //获得搜索热门标签
        public void getSearchLable(Observer<List<String>> observer){
            Observable observable=service.getHotLable().map(new HttpResultFunc<List<String>>());;
            Observable observableCache=providers.getHotLable(observable,new DynamicKey("搜索热门标签"), new EvictDynamicKey(false)).map(new HttpResultFuncCcche<List<String>>());
            setSubscribe(observableCache,observer);
        }
        //根据类型获取书籍集合
        public void getBookList(int bookType, int pageIndex, Observer<List<BookInfoListDto>> observer) {
            Observable observable = service.getBookList(bookType,pageIndex).map(new HttpResultFunc<List<BookInfoListDto>>());
            Observable observableCache=providers.getBookList(observable,new DynamicKey("getStackTypeHtml"+bookType+pageIndex), new EvictDynamicKey(false)).map(new HttpResultFuncCcche<List<BookInfoListDto>>());
            setSubscribe(observableCache, observer);
        }
        //根据关键字搜索书籍
        public void getSearchList(String key,Observer<List<BookInfoListDto>> observer){
            try {
                //中文记得转码  不然会乱码  搜索不出想要的效果
                key = URLEncoder.encode(key, "utf-8");
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
            Observable observable=service.getSearchList(key).map(new HttpResultFunc<List<BookInfoListDto>>());
            Observable observableCache=providers.getSearchList(observable,new DynamicKey("getSearchList&"+key), new EvictDynamicKey(false)).map(new HttpResultFuncCcche<List<BookInfoListDto>>());
            setSubscribe(observableCache, observer);
        }
        //获取书籍详情
        public void getBookInfo(int id, Observer<BookInfoDto> observer){
            Observable observable=service.getBookInfo(id).map(new HttpResultFunc<BookInfoDto>());
            Observable observableCache=providers.getBookInfo(observable,new DynamicKey("getBookInfo&"+id), new EvictDynamicKey(false)).map(new HttpResultFuncCcche<BookInfoDto>());
            setSubscribe(observableCache, observer);
        }
    
        /**
         * 插入观察者
         *
         * @param observable
         * @param observer
         * @param <T>
         */
        public static <T> void setSubscribe(Observable<T> observable, Observer<T> observer) {
            observable.subscribeOn(Schedulers.io())
                    .subscribeOn(Schedulers.newThread())//子线程访问网络
                    .observeOn(AndroidSchedulers.mainThread())//回调到主线程
                    .subscribe(observer);
        }
        /**
         * 用来统一处理Http的resultCode,并将HttpResult的Data部分剥离出来返回给subscriber
         *
         * @param <T>   Subscriber真正需要的数据类型,也就是Data部分的数据类型
         */
        private  class HttpResultFunc<T> implements Func1<HttpResult<T>, T> {
    
            @Override
            public T call(HttpResult<T> 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<T> 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<BookInfoDto>() {
                @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写,需要源码的可以联系我

    相关文章

      网友评论

      本文标题:先定一个小目标!比如说先用MVP和快速开发框架打造一个免费下载小

      本文链接:https://www.haomeiwen.com/subject/rrjluttx.html