云校園Android客戶端技術演進之路

打個廣告:云校園是什么?

云校園,作為拓維教育云平臺中主打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的開發環境越來越成熟,作為一個開發者,我們不能故步自封,要跟隨著發展趨勢,站在“巨人”的肩膀上,最大限度的利用新技術和服務,應用到項目中,解決實際問題。

要記住,能偷懶的程序員,才是好程序員。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,606評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,582評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,540評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,028評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,801評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,223評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,294評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,442評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,976評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,800評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,996評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,543評論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,233評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,926評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,702評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,991評論 2 374

推薦閱讀更多精彩內容