1. 程式人生 > >用.Net打造一個移動客戶端(Android/IOS)的服務端框架NHM(一)

用.Net打造一個移動客戶端(Android/IOS)的服務端框架NHM(一)

本文的目的

隨著移動網際網路的迅猛發展,關於移動客戶端技術解決方案的討論越來越多,本系列文章將試圖針對移動客戶端開發中的伺服器端開發,提供一個.NET平臺的框架解決方案。

由於本文是探討針對.Net服務端程式設計,所以理論上與手機端平臺無關,但為了方便描述,本文所提供的例子均為Android平臺,服務端程式語言使用C#。

本文的結構

本文將計劃以10篇左右的篇幅,從架構、邏輯、實現三個方面對框架進行闡述,其中最後3篇計劃做一個例子,實現框架的一些基本的功能。雖然本框架在作者的團隊中已經基本成熟,且有一個Android應用已經接近開發完成,但由於一些原因作者不可能將框架開源出來,所以對完全開源有所期待的讀者說聲抱歉,但最後的例子作者將會開源,它將是一個NHM Lite,具備完整的架構和簡潔的功能,方便讀者拓展。

由於作者才疏學淺,在編寫此文的過程中難免疏漏,還請各位指教。

背景

目前的移動客戶端開發,但凡涉及到與伺服器頻繁互動資料的應用,如:微博、人人網、招商銀行等,都要考慮到如何傳輸資料以及如何在手機上展示這樣兩個問題,而對此,當前主要有基於手機API和WebKit這樣兩種技術路線,在進行接下來的內容之前,我們不妨先對這兩種技術進行一下簡單的分析:

(一)手機API(典型應用:微博)

原理:手機端使用手機API,如AndroidAPI,進行開發,服務端只是一個數據提供者,如JSON。手機端接到JSON後將JSON反序列化成物件,進行邏輯處理,再在View層進行展示。當然你也可以不用JSON,用XML、甚至你自己能讀懂的某種字串如commaString(逗號分割的字串),雖然這一切都沒有JSON在JAVA中來的方便,本文就將使用JSON。這種方式,相當於傳統開發中的C/S模式,如圖:

image

優缺點:這種方式的優點在於,手機端開發更為靈活,可以應用手機API提供的所有API,可以對手機進行底層的控制,如:可以使用系統提供的更炫的UI、很方便的使用攝像頭、播放視訊、撥打電話、呼叫聯絡人、傳送簡訊(雖然後三者在android平臺用webkit的方式也可以比較方便的實現,但這裡講的是針對全平臺的思路)……這種方式的缺點也顯而易見,手機端開發週期長、可移植性差、無法跨平臺,即使你之前已經有了很強大的WEB應用,針對手機客戶端這部分WEB端也可能要重新開發

伺服器角色:在這種方式中,WebServer所扮演的是資料提供者的角色,它處理手機客戶端的請求,並將請求通過業務邏輯層的處理生成客戶端要求的JSON回發到客戶端,於是:檢視層僅僅是顯示JSON而已,沒有Jquery、沒有Ajax、甚至沒有HTML

。可能讀者要問了,這樣服務端的顯示豈不是很簡單?即使重新開發也不會很複雜啊!說顯示簡單也許是對的,但說邏輯不復雜,也許就不盡然。雖然檢視很簡單,但你依然要處理:業務邏輯、異常、傳輸加密、登陸註冊、使用者訪問許可權……,還要與你現有網站的資料進行整合(如果有的話),與重新做一個小的WAP網站無太大區別。

本系列文章將使用這一種方式進行闡述,在這個系列的文章完成後,下一個系列作者將會關注“PhoneGap”和“JqueryMobile”,到時將採用下面一種方式進行講解,由於現在為時尚早,且不說“敬請期待”。

(二)Webkit(典型應用:招商銀行)

原理:由於目前的幾大智慧手機平臺都支援WebKit,所以可以遵循Webkit的標準為手機客戶端開發跨平臺的網站應用,這時手機端僅僅是一個包了瀏覽器外殼的簡單程式,這個外殼通過訪問Web伺服器,獲得HTML流,並將HTML用支援WebKit的瀏覽器控制元件解析(如Android的WebView),從而實現介面的展現。另外在Android中,還可以“過載”JavaScript方法,獲得更接近Android原生程式的使用者體驗,如將JS中的alert”轉換”成Android的AlertDailog,再比如解析連結中的"tel: ”呼叫撥號介面,等等。但互動效果依然有限。這種方法相當於傳統開發中的B/S模式,如圖:

image

優缺點:WebKit方式的優缺點與API方式更好相反,廣義的講,WebKit無法提供手機原生UI、對手機硬體的控制能力有限,導致程式互動性較差;由於檢視層依賴於Web服務端,所以程式會更加依賴網路,可能更加耗費流量;但WebKit方式的優點同樣令人著迷:跨平臺、手機端開發週期很短、如果你已經有了很強大的WEB應用,開發WebKit或許就僅僅是做一個新的檢視層那麼簡單。對於最後一點,也許我該多說幾句。對於JSON方式,由於要使用JSON輸出,你就要重新構造一個各種類轉換成必要JSON的邏輯,比如我要在手機客戶端中顯示一個使用者的若干資訊,我需要讓伺服器傳一個User Json給客戶端,那這個JSON就要在伺服器重新構造,那怎麼構造呢?BLL要改、DAL同樣要改,這個不是個簡單的事情!當然,你也可以增加一個序列化物件為JSON的通用靜態方法,但這樣的方式往往並不是萬能。首先通用靜態方法只能處理簡單的物件,當一個物件中包含另一個需要被序列化的物件,那通用方法則未必成功;又比如,你需要一些轉換,比方說username屬性,輸出時改名為uid(為了給客戶端節約點流量嘛),那還是需要為每一個類增加對應的序列化方法,還是要在BLL、DAL動手腳,延長開發週期。

伺服器角色:WebKit方式的伺服器角色,就不僅僅是資料提供者那麼簡單,還需要提供完整的HTML檢視,似乎看來,相比第一種方式,是加了檢視層,但是從某種角度說,新增檢視卻正好實現了對之前業務邏輯的複用,開發反而更為簡單。當然或許你沒有“之前”的專案,一切從零開始,就像我這個專案一樣,我想說,那也不錯,從一開始就考慮到這樣兩種實現方式,會讓你有更寬闊的思路設計你的專案架構,以便應對和適應將來可能發生的一些轉變。

比如,招商銀行這個專案,之所以選擇了WebKit方式,正是因為對於網上查詢、繳費這些功能,招行已經有了一個很完善的Web程式,包括了必要的業務邏輯和安全性、證書、加密等機制,現在要開發手機客戶端,要把檢視層放到手機端,想想都是一個太過龐大的工程,但是有了WebKit,就可以把檢視層依然留在Web伺服器,而客戶端看起來又不是那麼山寨。(此專案與作者無關,作者僅作為例子說明文中觀點)

由於本文將採用JSON方式繼續闡述,那我想有必要對WebKit方式進行一個小的梳理,舉一個例子,分析一下工作方式,也讓讀者心中有數。

例1-1:設計一個登陸介面,使用WebKit的方式,要求儘量優化使用者體驗。

0002222333444

實現:

手機端:

程式結構:

捕獲
可以看到,程式的結構其實很簡單,Logo.java最先顯示,然後進入MainTabsActivity.java。MainTabsActivity是一個12宮格的選擇選單,其作用就是讓使用者選擇程式的不同功能。在使用者選擇後,其實就是選擇了網站的不同頁面。
MenuItem.java:
他是選項類,它是12宮格的選擇元素,包括了選項的標題、圖示資源ID、Url。MainTabsActivity中會動態生成一個 List<MenuItem>,當然在真正的專案中,可以考慮對這個列表採用其他的更為合理的儲存方式。

MainTabsActivity.java:
它最主要的工作就是繫結12宮格,關鍵程式碼如下:
?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 GridView gridview = (GridView) findViewById(R.id.GridView); MenuItem item1 = new MenuItem(); item1.setImgId(R.drawable.icon); item1.setTitle(getResources().getString(R.string.main_menu1_title)); item1.setUrl(Configs.getUrl("ShopOwnerLogin.aspx")); titleList.add(item1); MenuItem item2 = new MenuItem(); item2.setImgId(R.drawable.icon); item2.setTitle(getResources().getString(R.string.main_menu2_title)); item2.setUrl(Configs.getUrl("sstate.aspx")); titleList.add(item2); for (MenuItem s : titleList) { HashMap<String, Object> map = new HashMap<String, Object>(); map.put("image", s.getImgId()); map.put("text", s.getTitle()); meumList.add(map); } SimpleAdapter simpleAdapter = new SimpleAdapter(MainTabsActivity.this, meumList, R.layout.menuitem, new String[] { "image", "text" }, new int[] { R.id.image, R.id.text }); gridview.setAdapter(simpleAdapter);

WebViewActivity.java:

它就是顯示WebView的Activity了,通過過載webview的一些方法,實現了與Android原生UI的互動。

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 mWebView.setWebChromeClient(new WebChromeClient() { @Override public boolean onJsAlert(WebView view, String url, String message, final android.webkit.JsResult result) { new AlertDialog.Builder(WebViewActivity.this) .setTitle( getResources() .getString(R.string.setting_title)) .setMessage(message) .setPositiveButton(android.R.string.ok, new AlertDialog.OnClickListener() { public void onClick(DialogInterface dialog, int which) { result.confirm(); } }).setCancelable(false).create().show(); return true; }; });
?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 mWebView.setWebViewClient(new WebViewClient() { @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { // view.loadUrl(url); if (url.startsWith("tel:")) { Intent myIntentDial = new Intent( "android.intent.action.DIAL", Uri.parse(url)); startActivity(myIntentDial); } else { view.loadUrl(url); } return true; } @Override public void onPageFinished(WebView view, String url) { // TODO Auto-generated method stub super.onPageFinished(view, url); process.setVisibility(View.GONE); } @Override public void onReceivedError(final WebView view, int errorCode, String description, String failingUrl) { // TODO Auto-generated method stub // Toast.makeText(getBaseContext(), // R.string.NoRouteToHostException, Toast.LENGTH_SHORT) // .show(); new AlertDialog.Builder(WebViewActivity.this) .setTitle(getResources().getString(R.string.error)) .setMessage(R.string.NoRouteToHostException) .setCancelable(false) .setPositiveButton( getResources().getString(R.string.ok), new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { if (view.canGoBack()) { view.goBack(); } else { WebViewActivity.this.finish(); } } }) .show(); super.onReceivedError(view, errorCode, description, failingUrl); } @Override public void onLoadResource(WebView view, String url) { return; } });

此外,還可以通過傳入不同的cmd引數來讓webview讀取對應的固定資訊:

?
1 2 3 4 5 6 7 8 9 10 switch (cmd) { case ServiceConst.WEB_GET_ABOUT: String abouthtml = ResourcesUtils.getFromRaw(this, R.raw.about); mWebView.loadData(abouthtml, "text/html", "UTF-8"); break; case ServiceConst.WEB_GET_URL: mWebView.loadUrl(getIntent().getStringExtra("url")); break; }

伺服器端:

webkit的伺服器端就是徹底的網站程式了,這裡僅貼出.aspx檔案,供讀者參考:

?
1 2 3 <script type="text/javascript"> $(function () { $("#Login").click(function () {
?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 $.ajax({ type: "POST",                   //提交方式 url: "/ajax/LoginHandler.ashx",   //提交的handler data: GenInputParams(),           //處理頁面引數的JS方法        beforeSend: function (XMLHttpRequest) { $('#Login').attr('disabled', true); ; $('#Login').text("正在登入..."); }, success: function (msg) { $('#Login').attr('disabled', false); ; if (msg == "success") { alert("登入成功"); window.location.reload();                         } }, error: function (xhr, msg, e) { var emsg = eval('(' + xhr.responseText + ')'); $('#tipsTypeDiv').text(emsg.ErrCode); $('#Login').attr('disabled', false); ; alert(emsg.ErrMsg); } }); }); }); </script> <asp:Panel ID="pnGuest" runat="server"> 使用者名稱: <input type="text" id="username" class="inp" /><br/> 密碼: <input type="password" id="password" class="inp" /><br/> <div> <input type="hidden" id="method" value="shopowner" class="inp"/><input type="button" id="Login" value="登陸" class="inp" /> </div> </asp:Panel> <asp:Panel ID="pnLogged" runat="server"> <div> <asp:Label ID="uname" runat="server" Text="Label"></asp:Label>,歡迎您</div> <asp:Button  ID="btnExit" runat="server" Text="退出" onclick="btnExit_Click" class="inp" /> </asp:Panel>

小結

在這一章中,我們主要探討了當前主流手機客戶端的兩種實現方式,當然,在這個問題上,還有更多可以探討。如:廣義上說WebKit不能控制手機底層的硬體,如攝像頭,但第三方框架如:PhoneGap已經可以實現對攝像頭、GPS模組等操作,也可以開發出互動性不錯的App程式。但這些似乎都不屬於本文索要探討的範圍。而且似乎,有點跑題了,第一章讀完,好像都沒有給讀者朋友介紹我們這個框架的基本功能,好吧,先看圖。

nhm
通過截圖,大家都可以對該框架的功能進行大略的瞭解,接下來的幾篇,將進入重點,開始研