基于安卓的移動課堂應(yīng)用開發(fā)_第1頁
基于安卓的移動課堂應(yīng)用開發(fā)_第2頁
基于安卓的移動課堂應(yīng)用開發(fā)_第3頁
基于安卓的移動課堂應(yīng)用開發(fā)_第4頁
基于安卓的移動課堂應(yīng)用開發(fā)_第5頁
已閱讀5頁,還剩29頁未讀, 繼續(xù)免費閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認領(lǐng)

文檔簡介

本科畢業(yè)設(shè)計(論文)基于安卓的移動課堂應(yīng)用開發(fā)學(xué)院信息工程學(xué)院 專業(yè)通信工程 年級班別2012級()班 學(xué)號 學(xué)生姓名 指導(dǎo)教師原玲2016年5月

基于安卓的移動課堂應(yīng)用開發(fā)基于安卓的移動課堂應(yīng)用開發(fā)姚瑤瑤信息工程學(xué)院摘要隨著計算機技術(shù)的飛速發(fā)展,一方面,各種各樣的類庫、框架層出不窮;另一方面,用戶對于軟件質(zhì)量和功能的要求也越來越高。編程工作并不像有些人聲稱的那樣,門檻變得越來越低,相反,由于系統(tǒng)變得越來越復(fù)雜,對編程人員來說,要求反而變高了。既然系統(tǒng)復(fù)雜度的增加不可避免,作為一個開發(fā)者,我們所能做的,便是尋求某些方法,以應(yīng)對這復(fù)雜的系統(tǒng)、和步步緊逼的交付日期。作為軟件工程師們的經(jīng)驗結(jié)晶——設(shè)計模式,便是我們應(yīng)對軟件復(fù)雜度的有力武器。即便需求總是在改變,但恰如其分的軟件設(shè)計,將賦予軟件足夠的彈性,去應(yīng)用合理的變化。而作為設(shè)計模式一個核心的原則——模塊化,則有助于我們分解復(fù)雜度。復(fù)雜的系統(tǒng),經(jīng)過足夠的合理的抽象、封裝,形成一個個正交的子系統(tǒng),使得開發(fā)人員能夠在同一時間只集中注意于某一個局部,而不是同時處理龐大的細節(jié)。本文根據(jù)軟件工程的一系列原則對安卓平臺的移動課堂應(yīng)用進行設(shè)計,并對Android應(yīng)用中常用的幾種設(shè)計模式進行比較,說明各個模式的優(yōu)劣以及設(shè)計我們的方案時所作的權(quán)衡。關(guān)鍵詞:設(shè)計模式,軟件復(fù)雜度,模塊化AbstractAsthefast-developingofcomputerscience,allkindsofsoftwareframework,librarieswereborntoeaseourdeveloplife.Butattheotherhand,ouruserswantoursoftwaretoprovidemoreandmorefunctionalitiesandexpectthatit’sstable.Thelifeofdevelopmentisnotbecomemucheasyassomebodyclaimedthatit’seasierandeasiersincetherearesomanytoolsthatwecanuse.Butthankstothecomplexityofthesystem,ourdeveloper’sskillbecomesverydemanding.Sincethecomplexityisunavoidable,wehighlyneedatooltohackwiththelargesystemandcomfortourclientsoftheincomingdeliverytime.Designpattern,asthecrystallizationoftheexperienceofourdevelopers,isanextremelypowerfularmforustofightwiththecomplexityofthesystem.Althoughtherequirementisalwayschanging,awell-designedarchitecturewillgiveoursoftwaretheabilitytocopewithsomereasonablerequirementchange.Modularity,asthecoreprincipleofdesignpattern,canhelpustodecomposethecomplexity.Usingsomereasonableandsufficientabstractionandencapsulation,thelargesystemcanbedecomposedtoseveralorthogonalsub-systems,andourdeveloperscannowdiveintosomepartbutdon'tcareothers.Inthispaper,IshowsthatthewaytodesignourAndroidmobilelearningappusingseveralbasicprinciplesofsoftwareengineering.Inaddition,IalsopresentacomparisonbetweenafewpopulardesignpatternswhiledevelopingAndroidappsandexplainourchoiceandwhychooseit..Keywords:DesignPattern,SoftwareComplexity,Modularity目錄TOC\o"1-4"\h\z\u1緒論 11.1背景 11.2什么是設(shè)計模式 11.3模式的作用 22問題 42.1一個維護噩夢 42.2必須解決的問題 53架構(gòu)的選擇 73.1經(jīng)典的MVC模式 73.2一種變體——MVP模式 83.3更進一步——MVVM模式 103.4回歸簡樸——Document-View模式 113.5增強的Document-View 124框架的實現(xiàn) 144.1Document實例的創(chuàng)建 144.2View引用的保存 154.3處理配置改變引起的重復(fù)創(chuàng)建 154.4網(wǎng)絡(luò)框架 164.5總體架構(gòu) 165應(yīng)用的實現(xiàn) 185.1重新實現(xiàn)注冊頁面 185.2文件下載 196總結(jié)與展望 216.1總結(jié) 216.2展望 21參考文獻 22致謝 23附錄A一個簡單的注冊頁面實現(xiàn) 24附錄BGenericActivity 24附錄CAbstractDocument<ViewOps> 25附錄D避免Document的重復(fù)創(chuàng)建 26附錄E下載進度的發(fā)布 271緒論1.1背景從打孔的卡帶到匯編語言,從匯編語言到高級語言再到今天人氣高漲的人工智能,每每有新的技術(shù)出現(xiàn),總是有人預(yù)言程序員這個職業(yè)將死。然而,從六十年代高級編程語言出現(xiàn)到現(xiàn)在,程序員這個職位非但沒有消失,反倒越發(fā)的紅火。在編程所使用的工具越發(fā)強大的趨勢下,很多人忽略了更為重要的一點,應(yīng)用的復(fù)雜度也在急劇攀升。人們對軟件的質(zhì)量和能力有著越來越高的期望。所以,即便是有著越來越強大的工具,編程這個工作也不會消亡。相反,它帶來的是越來越大的挑戰(zhàn)。應(yīng)用越來越復(fù)雜,一個簡單的商業(yè)應(yīng)用,它的復(fù)雜度絕對可以輕易超過大多是程序員的智商。隔離復(fù)雜度,在軟件開發(fā)領(lǐng)域,也自然而然地成為一個不可獲取的技能。而它所使用的最大的工具,便是設(shè)計模式。模式有助于我們利用經(jīng)驗豐富的軟件工程師的智慧,它們記錄了軟件開發(fā)領(lǐng)域已經(jīng)得到充分證明的有效經(jīng)驗,可幫助推廣良好的設(shè)計經(jīng)驗。每個模式都一個特定的問題在特定的環(huán)境中反復(fù)出現(xiàn)的問題,并給出了一個解決方案。利用模式可以打造出具有特定特征的軟件架構(gòu)。1.2什么是設(shè)計模式ChristopherAlexander說,“每個模式描述了一個在我們身邊不斷重復(fù)發(fā)生著的問題,已經(jīng)對該問題的解決方案的核心。如此一來,你就能一次又一次地使用該方案二不必做重復(fù)勞動”。盡管Alexander講的是建筑領(lǐng)域的模式,對于軟件建構(gòu),這個說法也同樣適用。一般而言,一個模式有4個部分:模式的名稱。用一兩個詞來描述模式的問題和效果。對一個模式命名,增加了我們的設(shè)計詞匯。模式名幫助我們思考,幫助我們與他人交流設(shè)計思想和設(shè)計結(jié)果。問題。問題描述了給定背景下反復(fù)出現(xiàn)的問題,以及在什么時候可以使用該模式。它闡述了問題的本質(zhì):必須解決的具體設(shè)計問題是什么?出來對問題的陳述,還可以輔以一系列的作用力的描述。作用力表示問題的各個方面對特定解決方案的影響。解決方案。解決方案描述了該如何解決上述重復(fù)出現(xiàn)的問題,或者說,如何平衡相關(guān)的作用力。它描述了設(shè)計的各個組件之間的關(guān)系和各自的職責(zé)。因為模式就像一個模板,它可用于解決一定條件下重復(fù)出現(xiàn)的不同問題,所以解決方案并不描述一個特定的設(shè)計或者實現(xiàn),而是提供一個更為抽象的概念,并對軟件設(shè)計進行描述。效果。效果描述了應(yīng)用一個模式所能夠達到的效果已經(jīng)該模式所權(quán)衡的問題。它對于如何評價設(shè)計、選擇使用何種模式已經(jīng)選擇該模式的代價具有重要意義。模式的作用模式描述了在特定環(huán)境下反復(fù)出現(xiàn)的問題,并提供了該問題的一個解決方案。模式記錄了已經(jīng)得到充分驗證的既有的設(shè)計經(jīng)驗。模式并非刻意發(fā)明或創(chuàng)造出來的,而是工程師們根據(jù)其自身的經(jīng)驗提煉出來的。模式描述了超越類、實例和組件的抽象。由于模式是用于解決特定一類問題的通用的解決方案,這就決定了其不能描述具體的實現(xiàn),而必須在更高的抽象上描述問題。模式常常描述了多個組件、類或?qū)ο笾g的協(xié)作方式和各自的職責(zé)。通過對象間的協(xié)作,從而解決該問題。模式提供了一種通用的語言,并讓大家對設(shè)計原則有一致的認識。精心選擇的模式名會成為廣外人知的設(shè)計語言的一部分,有助于對設(shè)計問題及其解決方案展開有效的討論。我們不需要對解決方案給出詳盡的描述,只要提及其模式名,熟悉該模式的人便能夠馬上明白程序的基本結(jié)構(gòu)和特征。模式是一種記錄軟件架構(gòu)的手段。模式可描述你在設(shè)計軟件系統(tǒng)時腦海中浮現(xiàn)的構(gòu)思,在別人擴展和修改原始架構(gòu)或修改系統(tǒng)的代碼時,這有助于避免違背這種構(gòu)思。模式有助于創(chuàng)建具有指定特征的軟件。模式提供了功能行為骨架,有助于實現(xiàn)應(yīng)用程序的功能。每一種模式都有描述了使用該模式所能夠達到的效果,這基本上就覺得了應(yīng)用該模式的軟件會成長為什么樣。模式有助于打造復(fù)雜而異質(zhì)的軟件。每個模式都描述了一組預(yù)定義的組件,這些組件扮演的角色以及它們之間的關(guān)系,可用于規(guī)范軟件結(jié)構(gòu)的特定方面。模式猶如建筑構(gòu)件,可用于打造更復(fù)雜的設(shè)計。模式有助于控制軟件的復(fù)雜度。每個模式都為其闡述的問題提供了經(jīng)過實踐考驗的解決之道:需要的組件類型、這些組件扮演的角色、應(yīng)隱藏的細節(jié)、令人矚目的抽象,已經(jīng)各個部分的原理。通過明確各個組件的角色以及它們之間的關(guān)系,在軟件開發(fā)過程中,我們可以僅僅關(guān)注一個單獨的組件,而不需要花費多余的精力去考慮整個系統(tǒng)。2問題2.1一個維護噩夢考慮一下我們正在編寫的Android移動課堂應(yīng)用?,F(xiàn)在,我們需要用戶最先看到的頁面——注冊。對于一個最簡單的注冊頁面,我們需要兩個輸入框,一個按鈕,待用戶輸入用戶名和密碼后,我們獲取對應(yīng)的信息,然后發(fā)送至后臺服務(wù)器。然后,對于很多的新手,不出意外的,寫出的代碼(見附錄A)卻幾乎可以肯定會包含一系列的問題。首先,我們可以肯定的是,那段代碼的邏輯并沒有什么問題——在onCreate方法中初始化View并為按鈕注冊事件監(jiān)聽器。然后,由于Android并不允許在主線程中執(zhí)行網(wǎng)絡(luò)操作,我們新開了一個單獨的線程,用以將注冊信息發(fā)往服務(wù)器。但是,遺憾的是,這段代碼存在著非常嚴重的問題:Activity的引用泄露到的匿名內(nèi)部類的Runnable實例中,這幾乎可以肯定會發(fā)生內(nèi)存泄露,進而發(fā)生臭名昭著的OurOfMemoryError錯誤。為了解決問題1,我們可以將Runnable實現(xiàn)為靜態(tài)的內(nèi)部類,如此一來,便不會意外地將Activity的引用泄露出去。但是,如果一來,為了在服務(wù)器將注冊結(jié)果返回的時候做出反應(yīng),并對UI做出相應(yīng)的改變,我們需要同時使用WeakReference和Handler。用戶每點擊按鈕一次,便新創(chuàng)建一個線程。這必將使應(yīng)用經(jīng)受大量的線程創(chuàng)建和銷毀帶來的開銷。這將使得應(yīng)用和整個系統(tǒng)運行緩慢,而我們的用戶,也將會由于無法忍受這體驗而永遠不再使用這款A(yù)PP。也許,你聽說過Java的ExecutorFramework。沒錯,我們可以利用它減少線程創(chuàng)建、銷毀的開銷。但是,即便使用線程池,問題1和2的描述也依然適用。有經(jīng)驗的開發(fā)者會發(fā)現(xiàn)另外一個非常嚴重的問題。這里,應(yīng)用的UI和邏輯混合在了一起。應(yīng)用的邏輯通常不會發(fā)生太多的改變,而UI卻往往是改動之源。雖然改動UI不一定就會修改到邏輯,如果我們足夠小心,也可以保證在更新UI的同時,不會意外地給邏輯部分的代碼引入bug。但是,既然他們在同一個文件里,而你打開了那個文件進行修改,那么,就存在破壞的可能。最安全的做法是,將邏輯部分隔離開,同一時間,我們只關(guān)注UI或邏輯(所謂的隔離復(fù)雜度),這樣,在改變UI的時候,我們可以保證邏輯部分不會遭到破壞。同時,將UI和邏輯分離,對于應(yīng)用對編寫也有非常大的益處。同一時間,我們需要關(guān)注的東西更少了。注意力的集中,意味著我們可以專注于更細的細節(jié)上,編寫出更好、更健壯的應(yīng)用。現(xiàn)在,跳出這么一個單獨的Activity,讓我們考慮一整個應(yīng)用——我們的Android移動課堂應(yīng)用。不出意外,幾乎每一個頁面,都會有網(wǎng)絡(luò)操作。換句話說,即便根據(jù)上面的討論,我們修復(fù)了附錄A中所展示的代碼的bug,采用這種方法,也必將存在大量的重復(fù)代碼。這嚴重得違反了DRY原則,必將導(dǎo)致一個維護的噩夢。2.2必須解決的問題前面我們說到,設(shè)計模式的組成的問題部分,描述的是一個特定背景下反復(fù)出現(xiàn)的問題,并且,對于該問題的描述不應(yīng)該針對特定的應(yīng)用,它應(yīng)該在更為抽象的層級上描述一個反復(fù)出現(xiàn)的問題。上一小節(jié)我們討論對于一個特定的注冊頁面的面臨的問題。實際上,不只是那一個頁面,我們所開發(fā)的移動課堂APP,幾乎所有的頁面都存在著相似的問題。更進一步講,所有需要和服務(wù)器進行通信的APP都將面對這同一個問題。但凡是一個較長時間的操作,例如網(wǎng)絡(luò),我們都要小心避免泄露Activity的引用。但是,我們也不想在每個命令對象中使用WeakReference。因為,這所有的弱引用,都是為了同一個目的而存在——保存Activity的引用。既然使用他們的目的是一致的,那么,我們就完全有理由用一個復(fù)用的組件來完成這一功能。在上面我們的提到了UI和邏輯分離的原則。雖然“分離”二字很簡單,并且做起來,也似乎不難做到將應(yīng)用的UI和邏輯分開。但是,我們要做的不僅僅是分離,而是一致地分離。我們希望整個應(yīng)用有一致的架構(gòu),我們希望底層組件能夠得到盡可能多的復(fù)用。也就是說,對于類似的頁面,我們希望可以有一種全局性的架構(gòu),不管UI的內(nèi)容和類型,它都能夠勝任。對于UI和邏輯分離的問題,我們希望不僅僅是通過口頭或者文檔的形式進行約束。如果一個對框架的使用存在錯誤的用法,那么,錯誤就一定會發(fā)生。所以,我們希望在選定了框架之后,能夠從語法層面約束使用者。我們需要有強制性的措施,以避免用戶出錯。同時,對于UI和邏輯分離的問題,我們需要引導(dǎo)用戶以分離的思維去考慮應(yīng)用的設(shè)計,并使用框架實現(xiàn)強制用戶進行分離??墒?,世上并沒有銀彈。盡管我們希望構(gòu)件一個全局性的架構(gòu),以期提供整體的一致性。其實我們并沒有辦法實現(xiàn)一個架構(gòu),使得它能夠適用于所有的場景。但是,我們能夠做到的是,盡可能地讓其可擴展。只要擁有足夠的擴展性,它就能夠適應(yīng)更多的應(yīng)用場景,也就能夠解決更多的問題。更進一步,如果在這所有問題中存在著一個子集,它們需要一種更為特化的解決方案,我們可以對原有的基礎(chǔ)框架進行擴展,從而用這個新的特化的框架來決定這些問題。此外,我們也希望它足夠地輕量,它僅僅解決最為核心的、一直重復(fù)著的問題。而不試圖提供過多無謂的功能。一個好的設(shè)計,是已經(jīng)不能夠再減少任何東西。3架構(gòu)的選擇3.1經(jīng)典的MVC模式提起UI和邏輯的分離,相信很多人最先想到的便是MVC(Model-View-Controller)模式了。從Smalltalk時代開始,MVC便被用來創(chuàng)建用戶界面。在MVC模式里,共包含三種類型的對象,模型Model是應(yīng)用對象,視圖View用于向用戶展示,而控制器Controller定義了View對于用戶輸入的響應(yīng)方式。不使用MVC時,用戶界面設(shè)計往往將這些對象混雜在一起,不利于提供設(shè)計的靈活性和復(fù)用性。MVC通過同時使用觀察者模式和策略模式分離視圖和模型,同時提供對不同UI邏輯的支持。同時建立一個“訂閱/通知”協(xié)議,視圖和模型彼此分離。視圖必須保證它的顯示正確地反映了模型的狀態(tài)。一定模型發(fā)送了變化,它也將通知視圖,從而,視圖可以以此作為契機,更新對應(yīng)的UI。模型的變更傳播機制確保應(yīng)用程序的數(shù)據(jù)發(fā)生變化時,所有的觀察者將在正確的時間得到通知。這讓所有依賴于模型的視圖和控制器得以同步。這種方法還可以讓你為一個模型提供不同的多個視圖的表現(xiàn)形式,為能夠為一個模型創(chuàng)建新的視圖而無需重寫模型。在運行階段,可同時打開多個視圖(共享一個model),還可以動態(tài)地打開和關(guān)閉視圖。圖3.1MVC模式而對于View和Controller,則是策略模式的一個例子。一個策略表示一個算法族的一個對象。通過定義一個算法族,我們可以動態(tài)或靜態(tài)地替換對應(yīng)的策略。而策略的使用者——這里的視圖,則完全不受影響。通過將模型、視圖和控制器分離,可更換模型的視圖和控制器,甚至在運行階段都可以替換用戶界面。但是,對于當(dāng)今的移動開發(fā),MVC模式又有一些顯著的不足。首先,MVC三個組件存在著一個環(huán)狀的依賴關(guān)系。也就是說,一個組件接口的變化,必將同時影響另外兩個組件。不僅如此,環(huán)狀依賴也提高了單元測試的難度。同一時間,如果我們想對一個組件進行測試,必須提供兩個mock對象。此外,視圖和控制器的關(guān)系過于緊密??刂破骱鸵晥D雖然分開了,但關(guān)系緊密,這導(dǎo)致無法分別重用它們。不僅如此,視圖和控制器與模型也是緊耦合的。視圖和控制器都直接調(diào)用模型,這意味著修改模型的接口將破壞視圖和控制器的代碼。3.2一種變體——MVP模式由于上述MVC模式的一系列問題,加之其誘人的優(yōu)點,于是,便有了它的一個變體——MVP模式。MVP作為一個面向用戶界面設(shè)計的架構(gòu)模式,被刻意設(shè)計,以充分利用一些自動化單元測試工具并且改善表現(xiàn)邏輯分離的問題。圖3.2MVP模式 在MVP模式中,模型Model組件提供了一個定義用于展示的數(shù)據(jù)的接口,并根據(jù)用戶的動作做出相應(yīng)的處理邏輯。這里的Model和MVC中的Model并沒有什么差異。而表現(xiàn)者Presenter則作為一個中間人的角色而存在,所有的表現(xiàn)邏輯都存在于表現(xiàn)者Presenter中。它從Model中取得數(shù)據(jù),并對它進行格式化,然后在View中展示。所以,Presenter也因此有了另一個名稱——超級控制器。對于最后的View組件,這里我們稱它為被動視圖。視圖并不主動處理UI的更新和用戶動作,它所做的,就是非常單一的功能——展示用戶界面。視圖的更新由Presenter復(fù)制,對于用戶的動作,它也是直接轉(zhuǎn)發(fā)至Presenter中。 實際上,對于視圖所處理的邏輯的多少,每個不同的實現(xiàn)都不盡相同。在一個極端,視圖是一個完全的被動式的存在,將所有的用戶操作都轉(zhuǎn)發(fā)至Presenter中。在這種情況下,當(dāng)用戶觸發(fā)View組件的一個事件回調(diào)時,它不會做任何的處理,而僅僅是調(diào)用presenter的一個沒有參數(shù)也沒有返回值的方法。然后,Presenter通過View組件的接口,取出必需的數(shù)據(jù)。最后,Presenter操作Model組件,得到結(jié)果后又相應(yīng)地更新UI。而在另一方面,某些實現(xiàn)卻運行View組件處理某些特定的事件、操作或命令。這通常更適用于基于web的架構(gòu),這種情況下,View組件通常運行在用戶的瀏覽器中,所以,對于一些交互命令,View組件是一個更好的處理場所。 MVP模式與MVC模式最大的不同在于,MVP并不存在環(huán)狀依賴,取而代之,MVP是鏈狀的依賴關(guān)系。這使得MVP更加地適合于單元測試。同時,由于Presenter的存在,即便Model的接口發(fā)生變化,我們也只需要修改Presenter——我們的View組件,依然可以繼續(xù)使用而不需要做任何的修改。而對于MVC的優(yōu)點,在這里也同樣保持著。我們的UI和邏輯是顯著分離的,組件也可以復(fù)用。如果交換邏輯需要作出修改,也可以通過替換Presenter這個組件而得到所需的效果。同時,這里的Presenter也是作為一個策略而存在的,這意味著,我們可以在運行時動態(tài)地更換表現(xiàn)邏輯。 遺憾的是,MVP也有其缺點。前面我們說MVP是鏈狀依賴的時候,敏銳的你就應(yīng)該意識到,我們的調(diào)用鏈過長了。即使是Model中的一個小小的改變,也要跨過千山萬水才能夠表現(xiàn)在UI上。也就是說,與MVC相比,對于性能很關(guān)心的應(yīng)用,這個模式可能就不太適用了。所幸的是,這種情況發(fā)生的幾率實在太低,所以一般不會是太大的問題。而更為致命的是,作為超級控制器的Presenter,對于UI的所有更新都要經(jīng)手的它,實現(xiàn)中往往存在很多的盲轉(zhuǎn)發(fā)。大多數(shù)時候,我們根本不需要對數(shù)據(jù)進行格式化,只是從Model取出,然后直接應(yīng)用于View組件的更新。編寫這么多的盲轉(zhuǎn)發(fā)代碼,可不會是一件愉快的事。3.3更進一步——MVVM模式前面我們提到MVP模式中,將數(shù)據(jù)顯示到View組件通常是一件比較無趣而繁瑣的工作。而MVVM(Model-View-ViewModel)的出現(xiàn),就是為了解決這么一個問題。MVVM同樣是對圖形用戶界面與應(yīng)用邏輯的分離。它通過一種標(biāo)記語言或GUI代碼,將UI與商業(yè)邏輯或后臺邏輯分離開。MVVM中的ViewModel組件是一個值轉(zhuǎn)換器;這意味著ViewModel的職責(zé)是從將Model類得來的數(shù)據(jù)對象進行轉(zhuǎn)化,使之更易于管理和表現(xiàn)。從這一點看,ViewModel比View組件更加的模型化,并處理著幾乎所有的視圖表現(xiàn)邏輯。ViewModel可以使用中間人模式進行實現(xiàn),它處理View所支持的所有對后端邏輯的訪問控制。Model-View-ViewModel也叫Model-View-Binder,因為對View組件的更新,我們通常通過一些自動化的工具進行,也就是所謂的databinding。圖3.3MVVM模式從組件的依賴關(guān)系上看,MVVM和MVP非常地相似,唯一的不同是,MVP里,我們程序員自己手動執(zhí)行了一個所謂databinding的動作。Model組件也叫domainmodel,與MVC和MVP中的Model一樣,這里用于表現(xiàn)業(yè)務(wù)邏輯,并提供應(yīng)用數(shù)據(jù)的存取。View組件也是,作為一個用戶界面而存在。ViewModel是View組件的一個抽象表示,用于暴露其公開的接口。與MVC中的Controller和MVP中的Presenter不同的是,MVVP有一個binder。在ViewModel中,binder調(diào)節(jié)著View和ViewModel的通信。聲明式的對數(shù)據(jù)、命令和UI的綁定在MVVM模式中是隱式的。Binder將開發(fā)者從手工編寫大量模板代碼解放出來。毫無例外,MVVM也有其缺點。對簡單的應(yīng)用而已,這樣的框架無意過于的厚重。而對于復(fù)雜的交互邏輯,靠程序(binder)進行自動的數(shù)據(jù)綁定又顯得過于的力不從心。使用MVVM時,我們得自行拿捏一個中庸之道。3.4回歸簡樸——Document-View模式MVC存在著環(huán)狀依賴,MVP調(diào)用鏈過長,MVVM對簡單應(yīng)用過于復(fù)雜,對復(fù)雜應(yīng)用支持又不足,那么,我們又應(yīng)該選擇哪一個模式呢?回想前面我們討論過的問題,我們需要的是UI和邏輯分離,需要它足夠的輕量,并具有相當(dāng)?shù)目蓴U展性。它只解決最為迫切,一直重復(fù)發(fā)生著的問題。那么,這樣的架構(gòu)模式存不存在呢?答案是,存在,它就是所謂的Document-View模式。圖3.4Document-View模式Document-View也是屬于MVC變體的一種。這里的View,和MVC中的View并沒有什么顯著的差別,同樣的用于向用戶展示信息和提供交互。而Document則相當(dāng)于Model和Controller的結(jié)合,或者說,MVP模式中的Model和Presenter。使用Document-View模式的最為著名的架構(gòu),應(yīng)該就屬微軟的MFC框架了。默認情況下,MFC的項目生成向?qū)蓛蓚€類——Document類和View類。Document負責(zé)管理數(shù)據(jù)并更新View,View組件則用于向用戶展示數(shù)據(jù)和提供交互功能。這就是我們所需要的最基本的UI和應(yīng)用邏輯分離。但是,如果僅僅是UI和邏輯分離,并不足以使我們選擇這么一個架構(gòu)。選擇Document-View,還有著更多的理由。對于絕大多數(shù)的Android應(yīng)用,一個Model僅僅對應(yīng)一個View,我們并沒有不需要MVC同步更新多個View組件的功能。如果使用MVC,卻要因此忍受它帶來的環(huán)狀依賴??梢哉f,MVC并不適用于Android應(yīng)用的開發(fā)。MVP有著過長的調(diào)用鏈,無論是從View到Model還是從Model到View,我們不想要這些額外的消耗。對于性能非常重要的應(yīng)用而已,中間的Presenter是不能忍受的。此外,很多時候,中間的Presenter并沒有做太大有用的工作,它只是單純地將View和Model的調(diào)用相互轉(zhuǎn)發(fā)而已。對于MVVM,不使用他的原因和上面所述的它的缺點一樣。在簡單的應(yīng)用表現(xiàn)不佳,復(fù)雜的應(yīng)用卻有力不從心。最為重要的一點是,由于它們都是基于UI和邏輯分離的原則所設(shè)計的,四個模式都有其共通的地方。而對于最簡單的Document-View,它提供了最為基本的UI分離的支持,使用它,在合適的場合,我們可以輕易對它進行擴展,以增強為其他模式。假定我們現(xiàn)在的UI交換邏輯過于的復(fù)雜,Document-View已經(jīng)無法滿足我們的需求。此時,我們可以對Document-View進行擴展,使之成為一個MVP模式。需要注意的一點是,盡管此時已經(jīng)變化為MVP模式,但我們還是重用了低層Document-View框架的實現(xiàn)。圖3.5將Document-View擴展為MVP模式3.5增強的Document-View在前面我們描述模式的時候,我們通常都只是說“View”,“Model”等等。即便我們使用這樣的術(shù)語,但是,這并不意味著,各個組件是在和實體的類進行交談。這里“View”,并不特指某個具體的實現(xiàn),它完全可以是一個接口。目前Android的Native應(yīng)用主要是使用Java進行開發(fā)的,利用Java的語言特性,我們完全可以做得更好。圖3.6增強的Document-View這里的I指的是Interface,并且,這里的interface并不指一般意義上的接口,而是指Java語言的一個語法“interface”。組件并不互相直接就進行通信,而是通過引用一句接口。通過針對接口進行編程,我們保留了對組件進行透明替換的能力。雙方通過接口進行引用,使得在利用對方提供的接口時,只能使用接口所提供的抽象,這大大減少了需要同時關(guān)注的問題,很好地隔離了復(fù)雜度。接口提供了一個語言級的支持,可以實現(xiàn)安全、透明的實現(xiàn)替換。我們并不關(guān)心View和Document的實際類型是什么,我們只知道,那個類實現(xiàn)了我們所需的接口。只有實現(xiàn)對應(yīng)的接口,我們就可以對一個組件進行直接的替換,而另一個組件則完全不用更改。甚至,與MVC、MVP等提供的動態(tài)性一樣,我們也可以在運行時動態(tài)地選擇View和Document的實現(xiàn)。定義接口的過程中,可以迫使我們對組件所需要提供的功能進行細致的考慮。通過對業(yè)務(wù)進行分析并制定接口,可以避免在尚不清楚邏輯的情況下過快地進行編程。同時,它也可以幫助我們理清應(yīng)用的邏輯。還應(yīng)注意的是,接口的命名應(yīng)該是描述式的,它表明所完成的功能,而不是如何完成該功能。當(dāng)我們編寫實體類的時候,只需要實現(xiàn)對應(yīng)的接口,此時不再需要理會另一個組件是如何實現(xiàn)的。4框架的實現(xiàn)4.1Document實例的創(chuàng)建Android的UI是基于所謂的Activity實現(xiàn)的,一個Activity就相當(dāng)于一個窗口。所以,很自然的,Activity就是我們Document-View架構(gòu)中View組件的實現(xiàn)者了。由于一個Android應(yīng)用首先啟動的幾乎總是Activity,并且它是由AndroidRuntime啟動的,所以,剩下的我們需要關(guān)心的,表示何時實例化Document的實例,又在哪里進行實例化。每個Activity都對應(yīng)有一個Document,一般情況下,在Activity::onCreate方法中對Document實例化后,我們將其引用放置于內(nèi)部的一個實例域中。而這一系列的步奏,無疑每個Activity都需要。此時,就是拿出繼承這把武器的時候了。通過使用泛型參數(shù),我們不再需要提供一個公共的Document或View接口,而是由Java編譯器幫助我們進行安全的類型轉(zhuǎn)換。也就是說,子類所提供的Document和View接口的類型得以保留。由于我們希望由超類統(tǒng)一對Document進行實例化,在此,我們不得不做出一些折衷。在我們的實現(xiàn)中,我們要求Document類具有一個默認構(gòu)造函數(shù),從而可以利用Class::instance()方法進行實例的創(chuàng)建。具體的實現(xiàn)代碼見附錄B。與此同時,由于我們需要將View引用傳遞給Document,這里我們必須對Document的接口進行一定的約束。也就是說,Document接口有一個公共的祖先BasicDocumentOps接口。publicinterfaceBasicDocumentOps<ViewOps>{voidonCreate();voidonDestroy();voidsetView(ViewOpsview);} 通過增加一個setView方法,我們可以利用它設(shè)置View組件的引用。同時,我們也增加了兩個用于Activity聲明周期回調(diào)的方法。 使用該超類時,子類只需要回調(diào)超類的onCreateDocument方法,GenericActivity即會自動完成Document的創(chuàng)建工作: onCreateDocument(view,RegisterDocument.class);4.2View引用的保存前面我們提到了Activity的View組件的天然候選人,而在一開始我們就也提到了不小心保存Activity的引用會導(dǎo)致內(nèi)存泄露的問題。也就是說,對于傳遞給Document的實例,我們必須使用WeakReference將其保存起來,否則,將很容易發(fā)生內(nèi)存泄露。而這樣的一個處理措施,同樣是所有Document都必須具備的。由于有了上面創(chuàng)建Document時的經(jīng)驗,我們很容易就會想要使用基于繼承的方法來做。每次,這里我們確實用的是繼承,但由于接口由于抽象類。對于Document,我們只是給出一個骨架實現(xiàn),如果客戶需要,他可以完全不使用我們這里所創(chuàng)建的類型體系。具體的代碼見附錄C。理論上,我們好像可以依靠onDestroy回調(diào)來釋放view組件。而實際上,我們并不能這么做。因為我們沒有辦法強制客戶在不需要Document實例的時候,來調(diào)用外面的onDestroy方法。因此,為了防止內(nèi)存泄露,這里我們必須使用WeakReference。此外,由于view引用是通過setView方法而不是在構(gòu)造函數(shù)中設(shè)置的,我們沒有辦法使用final域所提供的內(nèi)存可見性保證,所以這里我們將其設(shè)置為volatile,從而確保Document組件中的后臺線程能夠看到mReference域的變化。4.3處理配置改變引起的重復(fù)創(chuàng)建 默認情況下,當(dāng)手機屏幕旋轉(zhuǎn)時,AndroidRuntime會自動銷毀當(dāng)前的Activity并重建一個。此時,對于我們的Document-View架構(gòu)來說,按照之前的實現(xiàn),勢必將會導(dǎo)致Document組件也重新創(chuàng)建。然而,一般情況下,這種創(chuàng)建確實完全不必要的。 為了避免Document組件的重復(fù)創(chuàng)建問題,我們可以利用Android的Fragment組件。只有設(shè)置fragment.setRetainInstance(true),當(dāng)由于配置改變發(fā)生Activity的銷毀重建時,該fragment并不會被銷毀。然后,通過FragmentManager我們可以重新獲取該對象。只要我們將Document放置于Fragment中,便可以避免Document的重復(fù)創(chuàng)建。 實現(xiàn)代碼見附錄D。4.4網(wǎng)絡(luò)框架基于不重復(fù)制造輪子的原則,我們使用了OkHttp3作為網(wǎng)絡(luò)框架。OKHttp3提供了同步和異步兩種調(diào)用接口:使用同步接口時,客戶需要自己處理并發(fā)問題使用異步接口時,框架根據(jù)傳輸層協(xié)議的成功與否,調(diào)用相應(yīng)的回調(diào)函數(shù)。由于我們并不想自己處理線程,并且其所提供的異步編程接口能夠滿足我們的需求,所以便直接使用了其異步編程接口。這里有一點值得一提的是,回調(diào)是在后臺線程執(zhí)行的。這個做法是必須的。當(dāng)HTTP響應(yīng)可讀時,回調(diào)函數(shù)即會執(zhí)行。這里的一個優(yōu)點是,我們可以對響應(yīng)進行耗時較長的操作,而不會影響應(yīng)用的響應(yīng)度。缺點則是,我們無法直接將結(jié)果發(fā)布至UI線程。為了應(yīng)對這個不足,可能的做法是:使用裝飾器模式,編寫一個裝飾器。裝飾器在這里所做的,便是將回調(diào)轉(zhuǎn)發(fā)至UI線程執(zhí)行。由于回調(diào)執(zhí)行時,讀取操作仍然會阻塞,所以這幾乎不會是什么好方法。應(yīng)用適配器模式,我們重新定義一個回調(diào)接口,在后臺線程讀取完所有的數(shù)據(jù)后,再將數(shù)據(jù)傳遞至非阻塞接口。這個其實也是另一個網(wǎng)絡(luò)框架Volley的做法。但它有一個致命的缺點——不能用于數(shù)據(jù)量巨大的網(wǎng)絡(luò)請求,如文件下載。這個也是Volley沒有提供文件上傳、下載功能的原因。直接發(fā)布結(jié)果至View組件,由View組件自己進行線程同步。這個是我們所采用的方法。原因是,上面兩種做法都預(yù)先假定了View層不是線程安全的。這違反了信息隱藏的原則。在我們的Document-View架構(gòu)中,兩種僅僅是通過接口引用對方,我們不希望對對方的實現(xiàn)有過多的猜測。它們只需要關(guān)系自己如何實現(xiàn)即可。此外,假定View組件不是線程安全的,這個做法本身也值得懷疑。假使某天我們的應(yīng)用移植到了一個命令行界面,那我們還需要對UI做這么多的同步嗎?換句話說,只有View組件了解自己,只有它自己知道如何進行同步,所以,同步操作應(yīng)該交由View組件自己去實現(xiàn)。4.5總體架構(gòu)在OkHttp3的基礎(chǔ)上,整個應(yīng)用實際上形成了一個分層架構(gòu)。實際上,Document-View也僅僅是分層架構(gòu)的一個特例而已。圖4.1整體架構(gòu)5應(yīng)用的實現(xiàn)利用我們前面所實現(xiàn)的Document-View框架,整個應(yīng)用具體非常一致的結(jié)構(gòu)。所以,這里僅僅是選取兩個比較典型的例子對實現(xiàn)部分進行說明。5.1重新實現(xiàn)注冊頁面首先,我們需要定義兩個Document-View的接口:interfaceRegisterViewOpsextendsOnDocumentFail{voidonRegisterSuccess(Stringemail,Stringpassword);voidonRegisterFail();voidonNetworkError();}interfaceRegisterDocumentOpsextendsBasicDocumentOps<RegisterViewOps>{voidonRegister(Stringname,Stringemail,Stringpassword);}這里需要注意的是,方法的名詞應(yīng)該只與應(yīng)用邏輯相關(guān)。我們絕對不應(yīng)該對雙方的實現(xiàn)做出過多的假設(shè)。然后,在相應(yīng)的事件發(fā)生時,調(diào)用對應(yīng)的接口:publicvoidonClick(Viewv){getDocument().onRegister(…)}publicvoidonRegisterSuccess(finalStringemail,finalStringpassword){runOnUiThread(newRunnable(){publicvoidrun(){//dowhateveryouneeded}});}當(dāng)用戶點擊“注冊”按鈕時,我們直接調(diào)用Document的onRegister方法,然后就只是靜靜地等待結(jié)果返回。當(dāng)結(jié)果返回的時候,我們通過Activity的runOnUiThread方法進行線程的同步。而對于Document也是一樣的,它只需要實現(xiàn)兩個接口,其余的一概不需要過問:publicvoidonRegister(Stringname,Stringemail,Stringpassword){//posttheregisterinfousingOkHttp...}publicvoidonResponseSuccess(Responseresponse){RegisterViewOpsview=getView();if(view!=null){view.onRegisterSuccess();}}當(dāng)結(jié)果放回時,Document便回調(diào)View組件的onRegisterSuccess()方法。通過使用我們所搭建的Document-View框架:這里我們不再需要自己顯式連接起Document和View組件不需要擔(dān)心由于配置改變引起的不必要的對象創(chuàng)建不會因為錯誤持有View引用而引發(fā)內(nèi)存泄露用戶界面與邏輯清晰分離使用框架的,幾乎不會產(chǎn)生什么重復(fù)的代碼5.2文件下載以模式語言的說法,,實現(xiàn)文件下載(文件上傳是類似的,這里便不再贅述)時,需要平衡一下幾個作用力:文件下載屬于耗時較長的操作,需要使用startedservice由于我們需要將下載進度體現(xiàn)在UI上,需要使用boundservice網(wǎng)絡(luò)速度的不可預(yù)知的,我們需要將進度“推”給View組件,而不能使用“拉”模型雖然Android的Service組件從語義上分為了startedservice和boundservice,并且有著各自不同的聲明周期,但是二者并不是互斥的。一個Service可以同時既是startedservice,也是boundservice。這正是我們所需要的。使用startedservice時,我們需要在客戶的請求執(zhí)行后,調(diào)用stopSelf()結(jié)束自己。由于可能同時有多個下載任務(wù)在進行,并且完成的順序是不確定的。我們需要一種機制,告訴我們,什么時候service應(yīng)該停止。這里我們使用一個集合來保存當(dāng)前正在下載的所有請求,文件下載完成時,通過判斷集合是否為空,即可決定是否應(yīng)該停止自己。privatefinalObjectmLock=newObject();privatefinalSet<String>mDownloadingSet=newHashSet<>();publicvoidonResponseSuccess(Responseresponse){//...synchronized(mLock){mDownloadingSet.remove(mRemotePath);if(mDownloadingSet.isEmpty()){stopSelf();}}//...}由于網(wǎng)絡(luò)請求的回調(diào)是在后臺線程執(zhí)行的,并且可能同時有多個線程在執(zhí)行下載操作,我們需要對mDownloadingSet進行同步處理。為了將鎖對象封裝起來,這里我們使用了一個私有的鎖對象而不是直接使用Service實例。與此同時,我們將二者均聲明為final,以確保其內(nèi)存可見性。而對于操作的同步,則之間使用Java內(nèi)建的Monitor實現(xiàn)。為了將下載進度發(fā)布至View組件,這里我們需要了觀察者模式。通過跟蹤當(dāng)前已下載的文件大小,我們可以得出下載的百分比。實現(xiàn)代碼見附錄E。6總結(jié)與展望6.1總結(jié)通過使用Document-View框架,整個應(yīng)用有著非常一致的結(jié)構(gòu),這對于應(yīng)用的維護和擴展是至關(guān)重要的。并且,由于框架實現(xiàn)非常的輕量,整個框架所實現(xiàn)的功能都有著高效的利用率。經(jīng)過大量實踐驗證的UI與邏輯分離的原則,使得應(yīng)用的實現(xiàn)非常的輕松,多數(shù)頁面甚至編碼后可以一次運行便成功。但是,其實該框架實現(xiàn)也有著一些不足。由于Java的泛型實現(xiàn)需要向下兼容,引入了所謂的類型擦除。于是,當(dāng)我們實現(xiàn)GenericActivity時,只得通過傳入一個Class<T>對象,并約定Document類的實現(xiàn)必須提供一個默認構(gòu)造函數(shù)。雖然這保證了類型安全,但也就絕對談不上優(yōu)雅。再有一點,雖然我們約定在GenericActivity的子類必須回調(diào)超類的onCreateDocument方法,以實例化Document對象,但是,就目前語言所提供的工具而已,我們并能夠在語言層級約束客戶必須調(diào)用該方法。事實上,在應(yīng)用編寫的過程中,我自己就曾有一次忘記調(diào)用該方法。好的設(shè)計總是有著一些折衷。為了類型安全和代碼的復(fù)用,我選擇了接受上述兩個缺點。6.2展望有些經(jīng)驗豐富的開發(fā)人員將模式視為一種思維工具,他們建議首先考慮這樣的模式,即并非可直接用于開發(fā)的應(yīng)用程序的所屬領(lǐng)域。有時候,可將模式的重要理念推廣到另一個領(lǐng)域,進而得到全新的模式或原有模式的變種。除了編程語言,程序員的效率還依賴于庫、框架和一系列的中間件。合適的模式能夠幫助程序員理解并高效地使用它們。隨著越來越多的開發(fā)人員人數(shù)認識到模式的優(yōu)點,有希望涌現(xiàn)這樣的一系列模式——它們都是軟件經(jīng)驗的結(jié)晶。未來的框架文檔可能包含闡述如何有效使用框架的模式。參考文獻CayS.Horstmann,CaryCornel著,JAVA核心技術(shù)[M],卷一,卷二,第九版·英文版人民郵電出版社2013.07RetoMeier著,佘建偉、趙凱(譯),Android4高級編程[M](第3版)清華大學(xué)出版社2013.04.01JoshuaBloch著,楊春花、俞黎敏譯,EffectiveJava[M]中文版第2版機械工業(yè)出版社2009.01BrianGoetz,TimPeierls,JoshuaBloch,JosephBowbeer,DavidHolmes,DougLea著,童云蘭等譯,Java并發(fā)編程實戰(zhàn)[M],機械工業(yè)出版社2012.2ErichGamma,RichardHelm,RalphJohnson,JohnVlissides著,李英軍、馬曉星、蔡敏、劉建中等譯,設(shè)計模式:可復(fù)用面向?qū)ο筌浖幕A(chǔ)[M],機械工業(yè)出版社2000.9CraigLarman著,李洋、鄭?等譯,UML和模式應(yīng)用[M]第三版,機械工業(yè)出版社2006.5任玉剛著,Android開發(fā)藝術(shù)探索[M],中國工信出版社,電子工業(yè)出版社2015FrankBuschmann,RegineMeunier,HansRohnert,PeterSommerlad,MichealStal著,Pattern-OrientedSoftwareArchitecture[M],Volume1–ASsytemofPatterns,JohnWiley&Sons,Ltd1996DouglasSchmidt,MichaelStal,HansRohnert,FrankBuschmann著,Pattern-OrientedSoftwareArchitecture[M],Volume2–PatternsforConcurrentandNetworkedObjects,JohnWiley&Sons,Ltd2000JamesGosling,BillJoy,GuySteele,GiladBrachaandAlexBuckley著,TheJavaLanguageSpecification[S],JavaSE8Edition.OracleAmerica,Inc.and/oritsaffiliates致謝本設(shè)計(論文)是在我的指導(dǎo)教師原玲副教授的親切關(guān)懷和悉心指導(dǎo)下完成的。他嚴肅的科學(xué)態(tài)度,嚴謹?shù)闹螌W(xué)精神,精益求精的工作作風(fēng),深深地感染和激勵著我。從題目的選擇到最終完成,原玲老師都始終給予我細心的指導(dǎo)和不懈的支持。附錄A一個簡單的注冊頁面實現(xiàn)publicclassRegisterActivityextendsActivityimplementsView.OnClickListener{@OverrideprotectedvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);//initview...}@OverridepublicvoidonClick(Viewv){//on"register"buttonclick//createanewthreadtoperformnetworkrequestnewThread(newRunnable(){@Overridepublicvoidrun(){//performnetworkrequest//andpublishresponsetotheUIthread}}).start();}}附錄BGenericActivitypublicabstractclassGenericActivity<ViewOps,DocumentOpsextendsBasicDocumentOps<ViewOps>>extendsAppCompatActivity{privateDocumentOpsmDocument;protectedvoidonCreateDocument(ViewOpsview,Class<?extendsDocumentOps>documentOpsClass){initDocument(documentOpsClass);

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論