雞湯:積跬步撇叁,至千里
本篇文章是從零開始搭建框架的第二章壳贪,將要講述的是flux架構氢妈。
架構簡述
關于架構眼坏,比較傳統(tǒng)的該屬MVC模式(Model-View-Controllor),這個模式設計之初的有個作用就是解耦,將代碼的各個模塊分離出來地粪。
Android一開始的架構模式也是MVC取募,
View:對應布局文件
Model:業(yè)務邏輯和實體模型
Controller:對應Activity
看起來好像沒什么問題,實際上Activity作為一個Controller做的事情太多了蟆技,數(shù)據(jù)綁定玩敏,事件處理等代碼都是寫在Activity中的。等到項目一大质礼,代碼一多旺聚,程序就會看起來很臃腫。
因為這些原因眶蕉,才會有后來的MVP架構(Model-View-Presenter)砰粹,以及MVVM等架構。
MVP:
View:對應Activity造挽,負責View的繪制和事件處理
Model:業(yè)務邏輯和實體模型
Presenter:負責view和model間的交互碱璃。
MVP的設計理念MVC是一樣的,解耦饭入,應該說所有的架構的設計理念都是解耦嵌器。
MVC的數(shù)據(jù)流向
MVP的數(shù)據(jù)流向
從上述兩張圖可以看出MVC的data和view是打通的,而且是沒有制約的谐丢,很容易引起數(shù)據(jù)的混亂爽航,反觀MVP在data和view中間多了一層Presenter,Presenter負責解析數(shù)據(jù)乾忱,并通知view讥珍,而view需要什么數(shù)據(jù),也是向Presenter請求的饭耳。這樣就不容易引起數(shù)據(jù)混亂串述。
好执解!說了這么多寞肖,接下來我們來了解下flux架構吧纲酗!
什么,前面講了這么多MVC和MVP的東西新蟆,你說接下來要講flux了觅赊,你是再搞我們嗎?
Flux架構
Flux是由facebook推出的架構理念,相比起MVC琼稻,更加簡潔和清晰吮螺。
Flux將應用分成4部分
View:視圖層
Action:view發(fā)出的事件,比如點擊事件等
Dispatcher:用來接收Actions帕翻,執(zhí)行對應的回調(diào)函數(shù)
Store:用來存放應用的狀態(tài)鸠补,一旦應用發(fā)生變動(數(shù)據(jù)變化),就提醒View更新
Flux最大的特點就是數(shù)據(jù)的單向流動,這樣就不會出現(xiàn)數(shù)據(jù)混亂的問題
如圖所示
整體流程
1.用戶訪問View
2.view發(fā)出用戶指定的Action嘀掸,如點擊事件
3.Dispatcher收到Action紫岩,要求store進行相應的更新
4.store更新完之后,發(fā)出"change"事件
5.View收到"change"事件后睬塌,更新頁面泉蝌。
不過上圖中缺少了一部分,數(shù)據(jù)的來源揩晴,再補上一張圖
流程變?yōu)?br>
1.用戶訪問View
2.view從ActionCreator中請求數(shù)據(jù)
3.ActionCreator加載網(wǎng)絡數(shù)據(jù)勋陪,并發(fā)出Action
4.Dispatcher收到Action,要求store進行相應的更新
5.store更新完之后硫兰,發(fā)出"change"事件
6.View收到"change"事件后诅愚,更新頁面。
根據(jù)上述所講的Flux架構劫映。事情的起點用戶訪問view呻粹,
對應的ActionCreator請求數(shù)據(jù)網(wǎng)絡數(shù)據(jù),并發(fā)送對應的Action
編寫ActionCreator和對應的Action
tip:接下來的內(nèi)容涉及RxJava,Retrofit2苏研,請還不知道這些是什么的同學等浊,先去入個門
新建網(wǎng)絡請求類HttpService 和數(shù)據(jù)反序列化類 DateDeserializer,注釋寫的比較詳細摹蘑,所以不多加解釋了筹燕!
public interface HttpService {
String BASE_URL = "http://gank.io/api/";
String DATE_HISTORY = "day/history";
//獲取gank的歷史數(shù)據(jù)
@GET(DATE_HISTORY)
Observable<DateData> getDateHistory();
//獲取某一天的數(shù)據(jù)
@GET("day/{year}/{month}/{day}")
Observable<DayData> getDayGank(@Path("year") int year, @Path("month") int month, @Path("day") int day);
class Factory {
private static OkHttpClient mOkHttpClient;
private static final int CACHE_MAX_TIME = 12 * 60 * 60;
private static final String DATE_PATTERN1 = "yyyy-MM-dd'T'HH:mm:ss.SSSSSS";
private static final String DATE_PATTERN2 = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";
static {
//設置緩存策略
Interceptor interceptor = new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Response response = chain.proceed(request);
if (request.url().toString().startsWith(BASE_URL)) {
int maxTime = CACHE_MAX_TIME;
Date receiveDate = response.headers().getDate("Date");
if (null != receiveDate) {
//設置緩存的到期時候
Calendar calendar = Calendar.getInstance();
calendar.setTime(receiveDate);
int hour = calendar.get(Calendar.HOUR_OF_DAY);
int min = calendar.get(Calendar.MINUTE);
maxTime = 24 * 3600 - hour * 3600 - min * 60;
}
return response.newBuilder()
.header("Cache-Control", "max-age=" + maxTime)
.build();
}
return response;
}
};
File cacheDir = new File(AppUtil.getCacheDir(), "http_reponse");
mOkHttpClient = new OkHttpClient.Builder()
.retryOnConnectionFailure(true) //連接失敗是否重連
.connectTimeout(15, TimeUnit.SECONDS) //超時時間15s
.addNetworkInterceptor(interceptor) //把定義好的攔截器加入到okhttp
.cache(new Cache(cacheDir, 10 * 1024 * 1024))
.build();
}
private static final Gson dateGson = new GsonBuilder()
.registerTypeAdapter(Date.class, new DateDeserializer(DATE_PATTERN1, DATE_PATTERN2)) //定義json的解析樣本
.serializeNulls()
.create();
private static final HttpService mGankService = new Retrofit.Builder()
.client(mOkHttpClient)
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create(dateGson))
.addCallAdapterFactory(RxJavaCallAdapterFactory.create()) //允許以 Observable 形式返回
.build()
.create(HttpService.class);
public static HttpService getGankService() {
return Factory.mGankService;
}
}
}
數(shù)據(jù)反序列化類 DateDeserializer
public class DateDeserializer implements JsonDeserializer {
private List<SimpleDateFormat> mDateFormatList;
public DateDeserializer(String... patterns) {
mDateFormatList = new ArrayList<>(patterns.length);
for(String pattern : patterns) {
mDateFormatList.add(new SimpleDateFormat(pattern, Locale.US));
}
}
@Override
public Object deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
for (SimpleDateFormat dateFormat : mDateFormatList) {
try {
return dateFormat.parse(json.getAsString());
}catch (ParseException e) {
e.printStackTrace();
}
}
return null;
}
}
新建一個TodayGankFragment類,用來和用戶交互衅鹿。
public class TodayGankFragment extends Fragment {
public static final String TAG = TodayGankFragment.class.getSimpleName();
public static TodayGankFragment newInstance() {
return new TodayGankFragment();
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.frag_today, container, false);
return rootView;
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
TodayGankActionCreator creator = new TodayGankActionCreator();
//view從對應的Creator請求數(shù)據(jù)
creator.getTodayGank();
}
}
TodayGankFragment類很簡單撒踪,加載一個簡單的布局,然后從對應的Creator請求數(shù)據(jù)大渤。
那么這個TodayGankActionCreator類是怎么樣的呢制妄!
新建TodayGankActionCreator類,過濾和初步解析http請求的數(shù)據(jù)。
public class TodayGankActionCreator {
//定義數(shù)據(jù)轉(zhuǎn)化模板
private static SimpleDateFormat sDataFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.CHINA);
public void getTodayGank() {
//RxJava處理數(shù)據(jù)
HttpService.Factory.getGankService()
.getDateHistory()
.subscribeOn(Schedulers.io())
.filter(new Func1<DateData, Boolean>() {
@Override
public Boolean call(DateData dateData) {
return (null != dateData && null != dateData.results && dateData.results.size() > 0);//接口請求成功泵三,這邊返回true
}
})
.map(new Func1<DateData, Calendar>() {
@Override
public Calendar call(DateData dateData) {
Calendar calendar = Calendar.getInstance(Locale.CHINA);
try {
calendar.setTime(sDataFormat.parse(dateData.results.get(0))); //設置時間為最新一天耕捞,一般是今天
} catch (ParseException e) {
e.printStackTrace();
calendar = null;
}
return calendar;
}
})
.flatMap(new Func1<Calendar, Observable<DayData>>() {
@Override
public Observable<DayData> call(Calendar calendar) {
return HttpService.Factory.getGankService() //再次請求數(shù)據(jù)衔掸,獲取當天的數(shù)據(jù)
.getDayGank(calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH) + 1, calendar.get(Calendar.DAY_OF_MONTH));
}
})
.map(new Func1<DayData, List<GankItem>>() {
@Override
public List<GankItem> call(DayData dayData) {
//獲取當天的數(shù)據(jù),然后在getGankList方法中執(zhí)行具體的解析工作
return getGankList(dayData);
}
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<List<GankItem>>() {
@Override
public void call(List<GankItem> gankItems) {
//數(shù)據(jù)正常處理之后調(diào)用此方法
}
}, new Action1<Throwable>() {
@Override
public void call(Throwable throwable) {
//數(shù)據(jù)處理過程中報錯時調(diào)用
}
});
}
private List<GankItem> getGankList(DayData dayData) {
if (dayData == null || dayData.results == null) {
return null;
}
//對數(shù)據(jù)進行處理俺抽,解析成你所需要的model
return null;
}
}
附上兩張圖
從兩張圖可以看出敞映,程序已經(jīng)可以正常返回數(shù)據(jù),并以對象的形式磷斧,接下來看具體需要如何形式的model振愿,進一步解析即可。
本章就先到這里弛饭。
本人也只是Android開發(fā)路上一只稍大一點的菜鳥冕末,如果各位讀者中發(fā)現(xiàn)文章中有誤之處,請幫忙指出侣颂,你的批評和鼓勵都是我前進的動力栓霜。
寫在文末:如果讀者朋友有什么問題或者意見可以在評論里指出.
代碼地址為https://github.com/niknowzcd/FluxDemo1