用.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模式,如图:

用.Net打造一个移动客户端(Android/IOS)的服务端框架NHM(一)_第1张图片

优缺点:这种方式的优点在于,手机端开发更为灵活,可以应用手机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模式,如图:

用.Net打造一个移动客户端(Android/IOS)的服务端框架NHM(一)_第2张图片

优缺点: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的方式,要求尽量优化用户体验。

用.Net打造一个移动客户端(Android/IOS)的服务端框架NHM(一)_第3张图片用.Net打造一个移动客户端(Android/IOS)的服务端框架NHM(一)_第4张图片用.Net打造一个移动客户端(Android/IOS)的服务端框架NHM(一)_第5张图片用.Net打造一个移动客户端(Android/IOS)的服务端框架NHM(一)_第6张图片

实现:

手机端:

程序结构:

用.Net打造一个移动客户端(Android/IOS)的服务端框架NHM(一)_第7张图片
可以看到,程序的结构其实很简单,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程序。但这些似乎都不属于本文索要探讨的范围。而且似乎,有点跑题了,第一章读完,好像都没有给读者朋友介绍我们这个框架的基本功能,好吧,先看图。

用.Net打造一个移动客户端(Android/IOS)的服务端框架NHM(一)_第8张图片
通过截图,大家都可以对该框架的功能进行大略的了解,接下来的几篇,将进入重点,开始研

你可能感兴趣的:(框架,json,.net,String,webkit,手机)