我們先假設(shè)一個場景需求:剛有孩子的爸爸媽媽對用照片、視頻記錄寶寶成長有強烈的意愿,但苦于目前沒有一款專門的手機APP做這件事。A公司洞察到市場需求,要求開發(fā)團隊盡快完成Android客戶端的開發(fā)。以下模擬團隊和工作開展。
-
團隊情況:產(chǎn)品經(jīng)理1人,Android開發(fā)2人,服務(wù)端開發(fā)2人,UI設(shè)計1人。
-
開發(fā)周期:兩個月。
-
工作量:大約50個界面。
-
隱含需求:考慮到用戶群體有可能激增的情況,服務(wù)端需要有一定的并發(fā)能力。
-
前提:原型已設(shè)計完成。
1 服務(wù)端概要設(shè)計
1.1 系統(tǒng)架構(gòu)
先給出服務(wù)端的架構(gòu)圖。
由于服務(wù)端開發(fā)有Java、PHP背景,為了快速完成開發(fā)任務(wù),我們選擇PHP作為服務(wù)端開發(fā)語言,順便也把數(shù)據(jù)庫定為MySQL。考慮后期擴展和數(shù)據(jù)庫訪問性能,擬引入Redis非關(guān)系型數(shù)據(jù)庫。同時為了提高數(shù)據(jù)讀的性能,在云服務(wù)器和數(shù)據(jù)庫之間用上緩存,并為數(shù)據(jù)庫主從備份、讀寫分離。服務(wù)器就不搭建在本地了,管理是一大問題。現(xiàn)在云服務(wù)器一大把,七牛、阿里云、騰訊云、百度云、金山云等等,技術(shù)成熟,而且價格還算公道。在此我們選擇阿里云。為了應(yīng)對可能面臨的并發(fā)問題,云服務(wù)器要考慮負載均衡。項目中可能存在大量的需要上傳和下載照片和視頻,我們選擇阿里云的開放存儲服務(wù),同時為了提升各個地區(qū)的下載體驗,我們引入CDN。客戶端通過API Service和服務(wù)端交換數(shù)據(jù),圖片和視頻的下載直接通過CDN。
1.2 模塊劃分
根據(jù)需求和原型設(shè)計,可能的模塊劃分如下:
-
注冊登錄模塊
-
用戶模塊
-
小孩模塊
-
媒體(圖片+視頻)模塊
-
相冊模塊
-
……
1.3 數(shù)據(jù)交換和API接口
服務(wù)端與客戶端使用JSON交換數(shù)據(jù),使用自定義JSON格式,約定返回code、message,實體封裝在result中,支持單個實體、實體列表、多個實體列表,定義如下:
{"code":500,"message":"系統(tǒng)異常,請稍后重試","result":""}
{
"code":200,
"message":"登錄成功",
"result":{
"user":{
"userId":1,
"nickName":"Leo",
"email":"Leo@xxx.com",
"gender":0
}
}
}
{
"code":200,
"message":"SUCCESS",
"result":{
"album":{
"kid":{
"kidId":1,
"nickName":"LEE",
"gender":1,
"birthday":"2014-3-6",
......
},
"media":{
"mediaId":123,
"mediaType":1,
"createdTime":193743546746,
"mediaDescription":"這是小孩次出去春游",
......
}
}
}
}
主要API接口設(shè)計如下:
http://api.xxx.com/service/v1.0/user/login
http://api.xxx.com/service/v1.0/user/third-login
http://api.xxx.com/service/v1.0/user/register
http://api.xxx.com/service/v1.0/user/logout
http://api.xxx.com/service/v1.0/user/info/update
http://api.xxx.com/service/v1.0/album/upload
http://api.xxx.com/service/v1.0/album/update
http://api.xxx.com/service/v1.0/album/delete
......
也許你看到了,API做了二級域名映射,同時為了服務(wù)端后期API版本的升級管理,在URL中加上了版本標識V1.0。命名方面我盡量做到restful的風格。對了,此處沒有使用Https。為了解決數(shù)據(jù)傳輸?shù)陌踩易隽它c特別的處理:對請求體和響應(yīng)結(jié)果進行RSA加密(如果服務(wù)端返回的數(shù)據(jù)稍稍過大,這個RSA嚴重影響客戶端解密,后來我換成了AES),所有請求為POST請求,所以API URL后面沒有帶參數(shù),你也看不到任何請求相關(guān)的信息。
1.4 數(shù)據(jù)庫設(shè)計
根據(jù)需求和原型設(shè)計,數(shù)據(jù)庫的設(shè)計大概需要兩周時間。其實一周基本搞定了,但為了考慮充分,留出一周時間來檢驗和調(diào)整。數(shù)據(jù)庫E-R圖略。
2 Android客戶端
2.1 基本結(jié)構(gòu)
Android本身就是MVC,所以我不打算引入MVP或MVVM。我的理念是職責分層,快速推出Android 1.0。主要的包結(jié)構(gòu)如下:
工程的搭建和包的劃分有各種各樣的,適合自己的就行了。
2.2 功能劃分
注冊登錄,個人信息,我的小孩,相冊管理,消息通知,系統(tǒng)設(shè)置等等。
2.3 引入的第三方技術(shù)
重復(fù)發(fā)明輪子是不可取的。有些模塊根本沒必要自己寫。以下是引入的第三方庫,以及優(yōu)勢說明。
2.3.1 網(wǎng)絡(luò)請求庫android-async-http
-
在匿名回調(diào)中處理請求結(jié)果
-
在UI線程外進行http請求
-
文件斷點上傳
-
智能重試
-
默認gzip壓縮
-
支持解析成Json格式
-
可將Cookies持久化到SharedPreferences
2.3.2 云巴推送
-
專注于為需要實時數(shù)據(jù)交換的產(chǎn)品提供完美解決方案
-
基于發(fā)布者/訂閱者(publisher/subscriber)模式,集成簡單
-
對比了百度云推送、騰訊信鴿推送,云巴效果更好
-
原極光推送CTO創(chuàng)辦的
2.3.3 xUtils(只使用其中的DbUtils和ViewUtils)
-
Android中的ORM框架,一行代碼就可以進行增刪改查
-
支持事務(wù),默認關(guān)閉
-
可通過注解自定義表名、列名、外鍵、性約束、NOT NULL約束、CHECK約束等(需要混淆的時候請注解表名和列名)
-
Android中的IOC框架,完全注解方式就可以進行UI,資源和事件綁定
-
新的事件綁定方式,使用混淆工具混淆后仍可正常工作
2.3.4 友盟統(tǒng)計
-
國內(nèi)專業(yè)的移動應(yīng)用統(tǒng)計分析平臺
-
統(tǒng)計和分析流量來源、內(nèi)容使用、用戶屬性和行為數(shù)據(jù)
-
Crash log跟蹤
2.3.5 云通訊驗證碼
2.3.6 高德地圖定位
2.3.7 異步圖片加載庫Android-Universal-Image-Loader
-
多線程下載圖片,圖片可以來源于網(wǎng)絡(luò),文件系統(tǒng),項目文件夾assets中以及drawable中等
-
支持隨意的配置ImageLoader,例如線程池,圖片下載器,內(nèi)存緩存策略,硬盤緩存策略,圖片顯示選項以及其他的一些配置
-
支持圖片的內(nèi)存緩存,文件系統(tǒng)緩存或者SD卡緩存
-
支持圖片下載過程的監(jiān)聽
-
根據(jù)控件(ImageView)的大小對Bitmap進行裁剪,減少Bitmap占用過多的內(nèi)存
-
較好的控制圖片的加載過程,例如暫停圖片加載,重新開始加載圖片,一般使用在ListView,GridView中,滑動過程中暫停加載圖片,停止滑動的時候去加載圖片
-
提供在較慢的網(wǎng)絡(luò)下對圖片進行加載
2.3.8 阿里云OSS Android客戶端SDK
-
提供文件(圖片、視頻等等)上傳
-
大文件分塊上傳
-
刪除操作(不推薦在客戶端使用)
2.3.9 組件內(nèi)通訊EventBus
-
基于發(fā)布者/訂閱者(publisher/subscriber)模式
-
簡化了應(yīng)用程序內(nèi)各組件間、組件與后臺線程間的通信
2.3.10 Android本地數(shù)據(jù)庫加密庫SQLCipher
-
基于SQLite擴展的開源數(shù)據(jù)庫,在SQLite的基礎(chǔ)之上增加了數(shù)據(jù)加密功能
-
SQLCipher對Android SDK中所有與數(shù)據(jù)庫相關(guān)的API都制作了一份鏡像,使得開發(fā)者可以像操作普遍的數(shù)據(jù)庫文件一樣來操作SQLCipher
2.4 基礎(chǔ)組件封裝
2.4.1 基礎(chǔ)回調(diào)接口
publicinterfaceDataCallback {voidonSuccess(Object result);voidonFailure(Object result);
}
2.4.2 網(wǎng)絡(luò)訪問
先看一下登錄的序列圖:
HttpManager類負責調(diào)用AsyncHttpWrapper中的post方法,和對服務(wù)端返回的數(shù)據(jù)解密、JSON轉(zhuǎn)對象、回調(diào)上層;AsyncHttpWrapper則負責請求體的封裝加密和其它的校驗參數(shù)封裝。看一下HttpManager類的post方法:
publicvoidpost(Context context, String url, RequestParams params,finalString modelName,finalDataCallback callback) {
AsyncHttpWrapper.post(context, url, params,newAsyncHttpResponseHandler() {
@OverridepublicvoidonSuccess(intstatusCode, Header[] headers,byte[] responseBody) {try{if(modelName !=null) {
handleResponse(responseBody, callback, modelName);
}else{
String response=newString(responseBody);//解密response =AES128.getInstance().decrypt(AppUtil.decodeReplace(response));//JSON轉(zhuǎn)對象BaseMessage message =AppUtil.getMessage(response);if(callback !=null) {//如果自定義code是200if(Coder.CODE_200.equals(message.getCode())) {
callback.onSuccess(message.getMessage());
}else{
callback.onFailure(newServerError(message.getCode()));
}
}
}
}catch(JSONException e) {
LogUtil.e(e);
callback.onFailure("服務(wù)端返回的數(shù)據(jù)不能解析成JSON");
}catch(Exception e) {
LogUtil.e(e);
callback.onFailure(e);
}
}
@OverridepublicvoidonFailure(intstatusCode, Header[] headers,byte[] responseBody, Throwable error) {if(callback !=null) {
callback.onFailure(error);if(responseBody !=null) {
String s=newString(responseBody);
LogUtil.e(s);
}
}
}
});
}
AsyncHttpWrapper中的post方法
publicstaticvoidpost(Context context, String url, RequestParams params, AsyncHttpResponseHandler responseHandler) {//設(shè)置請求頭部信息generateHeader(context);//加密請求參數(shù)String encryParams =AES128.getInstance().encrypt(params.toString());
RequestParams requestParams=newRequestParams();
requestParams.put("param", AppUtil.encodeReplace(encryParams));
client.post(context, url, requestParams, responseHandler);
}
privatestaticAsyncHttpClient client =newAsyncHttpClient();
2.4.3 Adapter封裝
為了加快開發(fā)速度,重用代碼,Adapter的使用有技巧。每次在getView中查找控件id、利用ViewHolder、賦值,后返回convertView,看著都是差不多的代碼。是時候脫離這個苦海了。先看怎么解決共用的ViewHolder問題。
publicstatic<TextendsView> T get(View view,intid) {
SparseArrayCompat<View> viewHolder = (SparseArrayCompat<View>) view.getTag();if(viewHolder ==null) {
viewHolder=newSparseArrayCompat<>();
view.setTag(viewHolder);
}
View childView=viewHolder.get(id);if(childView ==null) {
childView=view.findViewById(id);
viewHolder.put(id, childView);
}return(T) childView;
}
ViewHolder的作用,就是通過convertView.setTag與convertView進行綁定。當convertView復(fù)用時,直接從與之對應(yīng)的ViewHolder(getTag)中拿到convertView布局中的控件,省去了findViewById的時間。上面的代碼就是這樣的原理。
然后就是CommonAdapter了。
publicabstractclassCommonAdapter<T>extendsBaseAdapter {protectedLayoutInflater inflater;protectedContext context;protectedList<T>datas;protectedfinalintitemLayoutId;publicCommonAdapter(Context context, List<T> datas,intitemLayoutId) {this.context =context;this.inflater =LayoutInflater.from(context);this.datas =datas;this.itemLayoutId =itemLayoutId;
}
@OverridepublicintgetCount() {returndatas !=null? datas.size() : 0;
}
@OverridepublicT getItem(intposition) {returndatas.get(position);
}
@OverridepubliclonggetItemId(intposition) {returnposition;
}
@OverridepublicView getView(intposition, View convertView, ViewGroup parent) {finalCommonViewHolder viewHolder =getViewHolder(position, convertView, parent);
convert(viewHolder, getItem(position), position);returnviewHolder.getConvertView();
}publicabstractvoidconvert(CommonViewHolder viewHolder, T item,intposition);
}
好了,不貼代碼了。看不明白了請點擊這里:Android 快速開發(fā)系列 打造萬能的ListView GridView 適配器
2.4.5 其它Utils封裝
如AES128加密類、BitmapUtils、SecurePreferences、StringUtil、ToastUtil、IOUtil等等。
2.5 接口測試
為了保證數(shù)據(jù)交換、加解密正常,首先對某一個接口進行測試,以驗證API Service能正常跑通。比如可以先對登錄進行模擬測試,看是否成功,同時包括異常的測試,服務(wù)端是不是處理了邊界異常,返回給客戶端的都是封裝過的異常信息,而不是拋一個敏感信息給客戶端。提前進行接口測試有助于我們的基礎(chǔ)組件運行沒問題,方便后期其它模塊的快速集成。
2.6 快速開發(fā)
基礎(chǔ)組件封裝好后,除了少量的從網(wǎng)絡(luò)獲取數(shù)據(jù)邏輯和本地數(shù)據(jù)庫的增刪改查,客戶端基本上就是界面的布局工作了。界面開發(fā)基本看熟練程度和自定義View的重用。
本站文章版權(quán)歸原作者及原出處所有 。內(nèi)容為作者個人觀點, 并不代表本站贊同其觀點和對其真實性負責,本站只提供參考并不構(gòu)成任何投資及應(yīng)用建議。本站是一個個人學習交流的平臺,網(wǎng)站上部分文章為轉(zhuǎn)載,并不用于任何商業(yè)目的,我們已經(jīng)盡可能的對作者和來源進行了通告,但是能力有限或疏忽,造成漏登,請及時聯(lián)系我們,我們將根據(jù)著作權(quán)人的要求,立即更正或者刪除有關(guān)內(nèi)容。本站擁有對此聲明的最終解釋權(quán)。