前言
使用這個標題先表示對老王的尊敬
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 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"
然後就是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> 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")
ObservableString>>> 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)
ObservableString>>> getHotLable(ObservableString>> 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(ObserverString>> observer){
Observable observable=service.getHotLable().map(new HttpResultFuncString>>());;
Observable observableCache=providers.getHotLable(observable,new DynamicKey("搜尋熱門標籤"), new EvictDynamicKey(false)).map(new HttpResultFuncCccheString>>());
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.com
api大家可以隨意使用 但是用的是美國伺服器,會比較的慢,api是用spring mvc寫,需要原始碼的可以聯絡我