前言
使用这个标题先表示对老王的尊敬
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写,需要源码的可以联系我
网友评论