打個廣告:云校園是什么?
云校園,作為拓維教育云平臺中主打7-18歲教育階段的一款教育產品,圍繞學校、老師、家長、學生四個角色,打造精細化教學管理及校園高價值信息收集分發平臺,同時運用本地化家長社區為家長提供海量教育資訊,并為學生提供具有豐富優質學習資源的個性化學習空間,合力構建K12家校共育平臺。
云校園經歷了2年的發展,從最初1.0到現在的5.0版本。App包含功能插件、家校互通、社區、資訊、學習資源、支付、推送、統計等各種功能和模塊。
客戶端的技術架構也隨業務發展演進到現在。讓我們開始吧。
技術選型
1、通用框架
搭建云校園1.0的時候,從零起步,安卓開發只有2人,為了提高開發效率,我們想選一個通用的安卓開發框架。
當時較好的框架有AFinal、XUtils等。
我們最后選擇了XUtils,因為XUtils基于AFinal,包含了很多實用工具,穩定性也提高了不少。
XUtils主要包括4大模塊:
- DbUtils:提供了對象關系映射(ORM)數據庫的功能,可以方便的進行對象存儲
- ViewUtils:通過注解方式進行UI,資源和事件綁定。可以減少代碼量,較少的代碼量意味著更少的Bug和更高的開發效率
- HttpUtils:封裝了網絡請求模塊,相比系統組件HttpClient和HttpUrlConnection更好用
- BitmapUtils:較好的圖片加載模塊,對圖片下載和存儲統一管理
在云校園最開始,XUtils確實發揮了較好的作用,提高了開發效率。這個框架的特點是,功能很全。這是它的優點,同時也是它的缺點。就好像什么都做,但是什么都做不好一樣。
所以,我們后來引入了其他的框架替代了XUtils。DB模塊被ormLite替代,View模塊被Butterknife替代,Http被Volley替代,Bitmap模塊被ImageLoader替代。替換上的每個框架都是該領域的佼佼者,更好更專業的完成了各自領域的工作。
2、ORM框架
ORM的英文是Object Relation Mapping,也就是對象關系映射,它是一種程序技術,用于實現面向對象編程語言里不同類型系統的數據之間的轉換。
我舉一個直觀的例子,存儲一個Book對象到數據庫:
Book book = new Book("good");
book.setIndex(1988);
book.setAuthor("hehe");
liteOrm.save(book);
優點:隱藏了數據訪問的細節,將通用數據庫的操作封裝起來,程序員不用寫SQL語句,能把更多的精力放到業務邏輯上去。同時ORM跟具體數據庫解耦,也能很方便的替換數據庫。
缺點:犧牲了性能,也不能做SQL優化,有一定的學習成本,而且處理復雜的查詢力不從心。
ORM框架已經有很多了,比如OrmLite、SugarORM、GreenDAO、Realm。
OrmLite是Java的Orm框架,Android當然能用了。
SugarORM是Android平臺專用框架,提供簡單易學的API,上手很快。
GreenDAO適用于對性能要求高情況,而且SDK包不到100K。
Realm是一個跨平臺、高性能的框架,支持IOS。
最后我們選擇了老牌的OrmLite,考慮的是集成方便,上手快,而且穩定性不錯。現在Ormlite一直沿用至今,反響不錯。
3、依賴注入框架
DI(依賴注入)其實是一種設計模式,現在已經被頻繁的應用到安卓開發中。
它能夠讓開發者編寫出低耦合的代碼,而且更容易測試。
我們首先看看依賴注入的直觀感受:
class ExampleActivity extends Activity {
@BindView(R.id.user) EditText username;
@BindView(R.id.pass) EditText password;
@BindString(R.string.login_error) String loginErrorMessage;
@OnClick(R.id.submit) void submit() {
// TODO call server...
}
@Override public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.simple_activity);
ButterKnife.bind(this);
// TODO Use fields...
}
}
云校園的依賴注入框架由最開始的XUtils,換成了AndroidAnnotations,最后又替換成ButterKnife和Dagger2。
Butterknife針對Android平臺,提供了View,資源,事件回調等注入。非常方便、易懂。
而Dagger2可以注入各種組件,配合Activity生命周期管理組件生命周期。是MVP模式(后面會講)的天然搭檔。
基于這兩款框架,我們降低了20%的代碼量,而且代碼的耦合性很低。
4、網絡模塊框架
網絡模塊必不可少。
Android系統提供了HttpClient和HttpUrlConnection。HttpClient是Apache的開源實現,而HttpUrlConnection是Android2.2之后的標準實現,而且是官方推薦的。雖然HttpUrlConnection做了不少封裝但是還是不好用。
隨著Android的流行,出現了很多優秀的網絡模塊框架。
比如:android-async-http、Okhttp、Retrofit、Volley。
Retrofit提供了非常好的RESTful封裝和解耦,而且支持rx編程。
async-http能很好的完成了網絡的異步請求與回調,而且HTTP請求發生在UI線程之外。
但是Volley對網絡的封裝得很好。
我們采用Volley,通過自定義Request,我們用面向對象的方式封裝了各個網絡請求業務。
BaseRequest進行統一的公共的參數處理、錯誤處理、公共的返回處理。
外部請求傳入較少的參數,只需要考慮業務邏輯。
引入Volley之初,Volley的底層使用的是Android系統組件:HttpClient+HttpURLConnection,后來發現,OkHttp使用NIO更加高效,于是我們也將Volley的底層切換為OkHttp。
對于協議層,我們沒有采用Json,而是用的Thrift。
Thrift作為跨語言RPC框架有很多優點,相比json,序列化之后的傳輸小了不少,而且安全性更高。
另外Thrift還有一個很好的特點:協議結構清晰,而且有很好的擴展性。
5、圖片加載框架
云校園有大量的圖片顯示需求,而圖片這塊涉及到下載,緩存,異步加載,圖片處理,OOM等問題。特別是OOM這塊,圖片占用了應用大量內存,如果內存管理不好,很容易出現內存溢出,導致應用穩定性問題。
云校園的圖片加載框架從早期的XUtils切換到ImageLoader,滿足了基本的需求。但是ImageLoader的作者已經不再維護這個框架了。我們開始調研新的圖片加載框架。
Picasso比較輕量,但是Glide的默認參數配置更好。
因為每個圖片加載框架的區別很大,為了防止遷移和切換帶來很大的工作量,我們提取了自己的圖片加載組件ImageManager,作為圖片加載框架的適配器。保證了這塊的解耦。
Fresco在4.4及以下版本使用匿名內存來作為內存緩存,能極大的減少OOM。但是缺點是包很大。
綜合考慮后,我們選擇了Google官方的Glide加載框架。
架構
1、解耦
問題:在Activity及各個組件之間進行事件傳遞,大家會用什么方式?
Intent?Handler?BroadCastReceiver?Callback?
這些都不是好的解決方案。
EventBus卻用一種優雅的方式,一種松耦合的方式解決這個問題。
我們看一個直觀的例子:
//A組件內發送事件
eventBus.post(new AnyEventType());
//B組件內接受事件
@Subscribe
public void onEvent(AnyEventType event) {/* Do something */};
怎么樣?是不是迫不及待的想用起來,解決項目里面無處不在的Handler傳遞?
引入EventBus后,每個組件之間的耦合性降低了很多,統一的事件分發管理也讓業務邏輯清晰簡單。
2、架構模式MVP
MVP模式相比傳統的MVC模式,一般大家普遍的認識都是:“MVC的演化版本”、“讓Model和View完全解耦”、“代碼很清晰,不過增加了很多類”。
MVP模式的一個核心點是Model和View不會直接交互,而是由Presenter完成。
我們在項目中引入MVP模式的一個主要原因是:Activity既當View,又當Controller,導致Activity內部代碼龐大,而引入MVP后,Activity只作為View,Controller負責的業務邏輯被封裝到了Presenter里面,代碼更為清晰。
在項目中,復雜的需求我們會優先采用MVP模式。
3、響應式編程(RX)
響應式編程是一種面向數據流和變化傳播的編程方式。
利用響應式編程,我們可以加深代碼抽象的程度,可以很好的解決Callback Hell的問題,而且在線程切換問題上易如反掌。
給大家一個直觀的例子:
假設有這樣一個需求:
界面上有一個自定義的視圖imageCollectorView,它的作用是顯示多張圖片。現在需要程序將一個目錄中每個目錄下的png圖片都加載出來并顯示在imageCollectorView中。
你會怎么做?(注意:由于讀取圖片的程較為耗時,需要放在后臺線程執行,而圖片的顯示則必須在UI線程執行。)
通常做法(Callback Hell):
new Thread() {
@Override
public void run() {
super.run();
for (File folder : folders) {
File[] files = folder.listFiles();
for (File file : files) {
if (file.getName().endsWith(".png")) {
final Bitmap bitmap = getBitmapFromFile(file);
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
imageCollectorView.addImage(bitmap);
}
});
}
}
}
}}.start();
RX做法:
Observable.from(folders)
.flatMap((Func1) (folder) -> { Observable.from(file.listFiles()) })
.filter((Func1) (file) -> { file.getName().endsWith(".png") })
.map((Func1) (file) -> { getBitmapFromFile(file) })
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe((Action1) (bitmap) -> { imageCollectorView.addImage(bitmap) });
基于事件流,已經沒有煩人的回調嵌套了,每一步業務邏輯都非常清晰:讀目錄->讀PNG文件->轉成Bitmap->顯示圖片。
這樣的代碼,真是讓bug無處藏身呀!!!
而且Rx有很多周邊組件可以用,比如rxlifecycle,把Activity生命周期和Rx事件流綁定,當Activity銷毀的時候,可以自動解綁事件流。避免了內存泄漏。
開發者服務
現在的移動應用開發環境已經很成熟了,有很多不錯的開發者服務能夠幫助開發者快速開發。
比如:
- 應用統計:友盟統計、fabric
- 推送:信鴿、個推
- IM:騰訊云、環信
- 分享:ShareSDK、友盟分享
- Crash上報:Crashlytics、Bugly、BugTags
- 云存儲:阿里OSS、七牛云存儲
- 自動更新:友盟更新、訊飛
- 用戶反饋:友盟反饋
- 應用分發:蒲公英、fir.im
基于這些基礎服務,開發者可以把主要精力放在業務邏輯上,做到快速開發。
其他
1、統一Style和主題
我們發現在寫UI界面的時候,每個View控件為了做到高質量的UI還原,需要為每個控件寫很多屬性。而且很多屬性不停的重復。
寫代碼最基本的幾個原則是要避免重復代碼,如果出現超過2次,就應該提取。
所以針對上述情況,我們針對每種控件,抽取出公共的style屬性來。
在寫界面的時候,直接使用:
<TextView
android:id="@+id/title_textview"
style="@style/Text.Title"
android:text="@string/no_login_tip_title"
/>
這樣做的好處是:
- 代碼量少
- 調整UI非常方便,且很容易做主題
- 形成了一整套風格統一的UI,規范了程序員和設計師
2、工具類
很多有用的工具可以幫助程序員。
- leakcanary,用來查應用的內存泄漏
- facebook出品的stetho,用來調試Android應用。包括:查看App的布局,網絡請求,sqlite,preference,一切都是可視化的操作。
- 持續集成
應用的持續集成,自動化的發布版本,能更敏捷的讓產品經理體驗需求。而Jenkins無疑是首選。
Jenkins是免費的持續集成工具,云校園利用Jenkins,定時拉取代碼,編譯,打包,并上傳到分發平臺-蒲公英,并發送郵件。非常完美的完成了持續集成的工作。
總結
Android的開發環境越來越成熟,作為一個開發者,我們不能故步自封,要跟隨著發展趨勢,站在“巨人”的肩膀上,最大限度的利用新技術和服務,應用到項目中,解決實際問題。
要記住,能偷懶的程序員,才是好程序員。