用.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模式,如圖:
優缺點:這種方式的優點在於,手機端開發更為靈活,可以應用手機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模式,如圖:
優缺點: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的方式,要求儘量優化使用者體驗。
實現:
手機端:
程式結構:
可以看到,程式的結構其實很簡單,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程式。但這些似乎都不屬於本文索要探討的範圍。而且似乎,有點跑題了,第一章讀完,好像都沒有給讀者朋友介紹我們這個框架的基本功能,好吧,先看圖。
通過截圖,大家都可以對該框架的功能進行大略的瞭解,接下來的幾篇,將進入重點,開始研 |