[原创]用.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宫格,关键代码如下:
		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的互动。

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;
			};
		});

 

		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读取对应的固定信息:

	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文件,供读者参考:

<script type="text/javascript">
    $(function () {
        $("#Login").click(function () {   
            $.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
通过截图,大家都可以对该框架的功能进行大略的了解,接下来的几篇,将进入重点,开始研究我们的Nhm框架,敬请期待吧。

posted on 2011-11-15 14:23  flashpig  阅读(3900)  评论(2编辑  收藏  举报

导航