前言
使用这个标题先表示对老王的尊敬
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 Listactivities = 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() {
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> getHomeInfo();
//获取书籍详情
@GET("api/getBookInfo")
Observable<httpresult> getBookInfo(@Query("id") int id);
//获取类别列表
@GET("api/getTypeConfigList")
Observable<httpresult<list>> getTypeList();
//根据类别获取书籍列表
@GET("api/getTypeBooks")
Observable<httpresult<list>> getBookList(@Query("type")int type,@Query("pageIndex")int pageIndex);
//根据关键词获取搜索书籍列表
@GET("api/getSearchList")
Observable<httpresult<list>> 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>> getBookList(Observable<list> oRepos, DynamicKey userName, EvictDynamicKey evictDynamicKey);
//获取书库分类信息缓存数据 缓存时间 永久
Observable<reply<list>> getTypeList(Observable<list> oRepos, DynamicKey userName, EvictDynamicKey evictDynamicKey);
//获取首页配置数据 banner 最热 最新 缓存时间 7 天
@LifeCache(duration = 7, timeUnit = TimeUnit.DAYS)
Observable<reply> getHomeInfo(ObservableoRepos, 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> getBookInfo(ObservableoRepos, DynamicKey userName, EvictDynamicKey evictDynamicKey);
//根据关键词获取搜素列表 缓存时间 1 天
@LifeCache(duration = 1, timeUnit = TimeUnit.DAYS)
Observable<reply> getSearchList(ObservableoRepos, 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> observer){
Observable observable=service.getTypeList().map(new HttpResultFunc<list>());
Observable observableCahce=providers.getTypeList(observable,new DynamicKey("书本类别"),
new EvictDynamicKey(false)).map(new HttpResultFuncCcche<list>());
setSubscribe(observableCahce,observer);
}
//获取 app 首页配置信息 banner 最新 最热
public void getHomeInfo(boolean isload,Observerobserver){
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<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> observer) {
Observable observable = service.getBookList(bookType,pageIndex).map(new HttpResultFunc<list>());
Observable observableCache=providers.getBookList(observable,new DynamicKey("getStackTypeHtml"+bookType+pageIndex),
new EvictDynamicKey(false)).map(new HttpResultFuncCcche<list>());
setSubscribe(observableCache, observer);
}
//根据关键字搜索书籍
public void getSearchList(String key,Observer<list> observer){
try {
//中文记得转码 不然会乱码 搜索不出想要的效果
key = URLEncoder.encode(key, "utf-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
Observable observable=service.getSearchList(key).map(new HttpResultFunc<list>());
Observable observableCache=providers.getSearchList(observable,new DynamicKey("getSearchList&"+key),
new EvictDynamicKey(false)).map(new HttpResultFuncCcche<list>());
setSubscribe(observableCache, observer);
}
//获取书籍详情
public void getBookInfo(int id, Observerobserver){
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(Observableobservable, Observerobserver) {
observable.subscribeOn(Schedulers.io())
.subscribeOn(Schedulers.newThread())//子线程访问网络
.observeOn(AndroidSchedulers.mainThread())//回调到主线程
.subscribe(observer);
}
/**
* 用来统一处理 Http 的 resultCode,并将 HttpResult 的 Data 部分剥离出来返回给 subscriber
*
* @paramSubscriber 真正需要的数据类型,也就是 Data 部分的数据类型
**/
private class HttpResultFunc<T> implements Func1<HttpResult<T>, T> {@Override
public T call(HttpResulthttpResult) {
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(ReplyhttpResult) {
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.com
api 大家可以随意使用 但是用的是美国服务器,会比较的慢,api 是用 spring mvc 写,需要源码的可以联系我
转自:https://gold.xitu.io/post/58297a042f301e00585c7a21
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于