[转载]HTML5 - 搭建移动Web应用

mikel阅读(978)

[转载]HTML5 – 搭建移动Web应用 – gzterrytan – 博客园.

关于HTML5

HTML5具有语义学本地存储设备访问连接性多媒体平面和三维效果性能和集成CSS3八大技术特征。让Web应用进入无插件时代,在功能和性能上逼近桌面应用。促使应用Web化,实现跨平台。

HTML5规范草案将于2012年发布候选推荐版,2022年发布计划推荐版。规范的实现似乎还在遥远的未来,其实不然!当前很多浏览器已经部分支持HTML5,caniuse.com提供了详尽的浏览器支持情况。HTML5规范本身并不多,很多相关规范都被独立出来,由浏览器各自实现。

      

移动Web应用方向

手机上网已经成为最重要的上网方式之一,手机网民已达3亿。移动互联网时代已经开启,发展势头迅猛,成为互联网行业的新战场。

Android和iOS手机的兴起,加速了HTML5在移动设备的普及。与桌面浏览器不同的是,移动操作系统和浏览器随着手机的换代而不断升 级。移动浏览器的不断升级,给HTML5在移动Web方向的发展提供源源不断的动力。也随着设备性能的不断提高,移动Web应用的能力也渐渐逼近客户端应 用。

移动Web应用对比客户端应用的优势:

  1. 更多开发人员有丰富的Web开发经验和工具积累,也形成了成熟的开发社区
  2. 迭代更敏捷,实现持续更新
  3. 跨平台,开发成本比客户端的较低

Web应用宿主选择

Web都有宿主,宿主是运行程序所需要的环境。Web常见的宿主有IE、FF、Chrome这些浏览器。JavaScript也运行在服务器端宿主,如node.js。在移动设备,移动Web可以运行在移动浏览器上,也可以运行在PhoneGap或Titanium等框架宿主上。当然我们也可以根据跨平台需要编写自己框架宿主。

框架宿主优势:

  1. 已形成成熟的社区,便于解决问题
  2. 如PhoneGap等,比浏览器拥有更高权限。可以访问联系人、文件、摄像头、录音等设备
  3. 可以通过模拟器进行测试,减少跨浏览器测试成本

框架宿主劣势

  1. 以客户端形式发布,版本更新难度大,动态发布需要额外代码支持
  2. 系统有可能只支持单进程
  3. 产品之间无法跳转,不容易衔接

PhoneGap兼容性


移动浏览器是系统附带的,不需要发布,随着系统升级而更新。在没有权限要求和高端目标设备的情况下,浏览器宿主作为移动Web应用宿主更为适合。

Android浏览器   Mobile Safari

浏览器宿主优势:

  1. 无需发布,浏览器一般还支持桌面快捷方式
  2. 产品更新维护方便,可以实现持续更新
  3. 移植性高(相对框架宿主的一些自定义接口)

浏览器宿主劣势:

  1. 浏览器兼容不高,对HTML5支持有差异
  2. 性能差异大,必须考虑设备间处理能力的差异
  3. 测试成本大(特别是Android系统设备),无法覆盖所有机型

移动JavaScript框架

在HTML5的支持下,交互集成取代JavaScript浏览器兼容性成了移动Javascript框架的发展方向。其中JQuery Mobile和Sencha Touch最受追捧。

他们主要解决:

  1. 交互(包括UI设计、控件交互、页面切换等)集成
  2. Touch手势

交互集成框架优势:

  1. 交互都符合设备交互特点和设计规范(Android和iOS)
  2. 开发门槛低,适合快速开发
  3. 开发社区已经有丰富的跨平台经验和较全面的测试结果

交互集成框架劣势:

  1. 文件体积较大,效率往往不如原生HTML5
  2. 交互模式固定,难以摆脱
  3. 没有完善的业务框架,还需要自己实现基础业务框架(代码更新、业务模块重用等)

JQuery Mobile 兼容性

除了交互集成框架,我们还可以考虑使用原生HTML5开发自己的移动Javascript框架。自己开发更有针对性和目的性,适合业务扩展和性能优化,可以弥补集成框架的缺陷。

原生HTML5框架优势:

  1. 最适合长期业务扩展
  2. 开发灵活度更高
  3. 可针对目标设备的性能优化

原生HTML5框架劣势:

  1. 暂时只能兼容高端设备(Android2.0+、iOS3.2+性能较佳)
  2. 跨平台测试成本比较高
  3. 框架开发门槛比较高,需要一段时间发展

参考资料

http://www.caniuse.com

http://www.w3.org

《2010年中国手机上网用户行为研究报告》

《2010-2011年中国智能手机市场研究年度报告》

[转载]Android开发——MediaProvider源码分析(2)

mikel阅读(776)

[转载]Android开发——MediaProvider源码分析(2) – 努力吧,专注Android – 博客园.

欲读此文,先读上文:MediaProvider源码分析(1)

———————-START—————————

在 上一篇文章中说到系统当接收到扫描请求广播的时候就会调用scan或者scanFile去扫描手机(手机内存和sdcard)中的媒体文件。这两个方法都 是启动MediaScannerService这个服务来完成扫描任务的。接下来我们来看看MediaScannerService是怎么工作的……

4.MediaScannerService(MSS)

MSS实现了Runnable,所以必然的需要实现run方法了,代码如下:

public void run()

{

// reduce priority below other background threads to avoid interfering

// with other services at boot time.

Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND +

Process.THREAD_PRIORITY_LESS_FAVORABLE);

Looper.prepare();

mServiceLooper = Looper.myLooper();

mServiceHandler = new ServiceHandler();

Looper.loop();

}

在run方法中设置了线程的优先级,优先级比较低,主要为了避免跟其他服务抢夺资源。还有就是利用looper对ServiceHandler的消息进行循环控制。

接着看一下ServiceHandler的实现代码:

private final class ServiceHandler extends Handler

{

@Override

public void handleMessage(Message msg)

{

Bundle arguments = (Bundle) msg.obj;

//获取文件路径

String filePath = arguments.getString("filepath");

try {

if (filePath != null) {

//文件路径不为空,则调用扫面当个文件的方法

IBinder binder = arguments.getIBinder("listener");

IMediaScannerListener listener =

(binder == null ? null : IMediaScannerListener.Stub.asInterface(binder));

Uri uri = scanFile(filePath, arguments.getString("mimetype"));//扫描单个文件

if (listener != null) {

//执行扫描完成方法

listener.scanCompleted(filePath, uri);

}

} else {

//如果文件路径为空,则获取扫面手机内存或者sdcard

String volume = arguments.getString("volume");

String[] directories = null;

//内置卡

if (MediaProvider.INTERNAL_VOLUME.equals(volume)) {

// scan internal media storage

directories = new String[] {

Environment.getRootDirectory() + "/media",

};

}//外置卡

else if (MediaProvider.EXTERNAL_VOLUME.equals(volume)) {

// scan external storage

directories = new String[] {

Environment.getExternalStorageDirectory().getPath(),

};

}

if (directories != null) {

if (Config.LOGD) Log.d(TAG, "start scanning volume " + volume);

//扫描

scan(directories, volume);

if (Config.LOGD) Log.d(TAG, "done scanning volume " + volume);

}

}

} catch (Exception e) {

Log.e(TAG, "Exception in handleMessage", e);

}

stopSelf(msg.arg1);

}

};

以上三个方法是属于Service的生命周期的。当我们调用startService的时候,如果对应的Service还未创建就会调用 onCreate方法===方法。每次startService的时候就调用onStartCommand,所以ServiceHandler就在此发送 消息了。

最后,稍微看一下MSS里面扫描方面。主要是调用MediaScanner对媒体文件进行扫描分析的。至于MediaScanner的实现以后在分析。

private void openDatabase(String volumeName) {

try {

ContentValues values = new ContentValues();

values.put("name", volumeName);

getContentResolver().insert(Uri.parse("content://media/"), values);

} catch (IllegalArgumentException ex) {

Log.w(TAG, "failed to open media database");

}

}

private void closeDatabase(String volumeName) {

try {

getContentResolver().delete(

Uri.parse("content://media/" + volumeName), null, null);

} catch (Exception e) {

Log.w(TAG, "failed to close media database " + volumeName + " exception: " + e);

}

}

//创建扫描器

private MediaScanner createMediaScanner() {

MediaScanner scanner = new MediaScanner(this);

Locale locale = getResources().getConfiguration().locale;

if (locale != null) {

String language = locale.getLanguage();

String country = locale.getCountry();

String localeString = null;

if (language != null) {

if (country != null) {

scanner.setLocale(language + "_" + country);

} else {

scanner.setLocale(language);

}

}

}

return scanner;

}

//扫描目录

private void scan(String[] directories, String volumeName) {

// don't sleep while scanning

mWakeLock.acquire();

ContentValues values = new ContentValues();

values.put(MediaStore.MEDIA_SCANNER_VOLUME, volumeName);

Uri scanUri = getContentResolver().insert(MediaStore.getMediaScannerUri(), values);

Uri uri = Uri.parse("file://" + directories[0]);

sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_STARTED, uri));

try {

if (volumeName.equals(MediaProvider.EXTERNAL_VOLUME)) {

openDatabase(volumeName);

}

MediaScanner scanner = createMediaScanner();

scanner.scanDirectories(directories, volumeName);

} catch (Exception e) {

Log.e(TAG, "exception in MediaScanner.scan()", e);

}

getContentResolver().delete(scanUri, null, null);

sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_FINISHED, uri));

mWakeLock.release();

}

//扫描文件

private Uri scanFile(String path, String mimeType) {

String volumeName = MediaProvider.INTERNAL_VOLUME;

String externalStoragePath = Environment.getExternalStorageDirectory().getPath();

if (path.startsWith(externalStoragePath)) {

volumeName = MediaProvider.EXTERNAL_VOLUME;

openDatabase(volumeName);

}

MediaScanner scanner = createMediaScanner();

//扫描单个文件

return scanner.scanSingleFile(path, volumeName, mimeType);

}

在MediaProvider中还有一个类:MediaThumbRequest,用来创建预览图的,比如视频的预览图,图片的预览图,音频的专辑图片…这些图片的信息也是保存在数据库的,有兴趣的同学可以自己打开数据库看看里面的表。如下图:

media_db_tables

啰哩啰唆的写了两篇文章,希望对大家有所帮助。

其中应该有不少是错误的观点,望大家指正。

———————-END——————————

[转载]Android 事件处理(—)

mikel阅读(1021)

[转载]Android 事件处理(—) – 神舟龙 – 博客园.

(一) 事件使我们在于UI交互式发生的,我们点击一个按键时,可能就已经除非好几个事件,例如我们点击数字键“0”,他会涉及到按下事件,和一个弹起(松开)事件,在我们Android中还可能涉及到触摸屏事件,所以在Android系统中,事件是作为常用的功能之一;

android下,事件的发生是在监听器下进行,android系统可以响应按键事件和触摸屏事件,事件说明如下:

l onClick(View v) 一个普通的点击按钮事件

l boolean onKeyMultiple(int keyCode,int repeatCount,KeyEvent event)用于在多个事件连续时发生,用于按键重复,必须重载@Override实现

l boolean onKeyDown(int keyCode,KeyEvent event) 用于在按键进行按下时发生

l boolean onKeyUp(int keyCode,KeyEvent event 用于在按键进行释放时发生

l onTouchEvent(MotionEvent event)触摸屏事件,当在触摸屏上有动作时发生

l boolean onKeyLongPress(int keyCode, KeyEvent event)当你长时间按时发生(疑问?)

(二) 首先我们建立一个android项目,当项目建立好之后,直接在默认的main.xml文件中拖放一个button 按钮,其他的不需要在这里做什么了,然后就可以到命名好的.java文件中进行先关代码的书写;

1. 对要使用的控件进行引用,当然你也可以用到的时候再在相关类控件添加引用

import android.app.Activity;

import android.os.Bundle;

import android.view.KeyEvent;

import android.view.MotionEvent;

import android.view.View;

import android.widget.Button;

import android.widget.Toast;

2. 获得相关对象,设置控件监听器

Button button=(Button) findViewById(R.id.button1);

//设置监听

button.setOnClickListener(new Button.OnClickListener()

{

@Override

public void onClick(View v) {

// TODO Auto-generated method stub

DisplayToast(事件触发成功);

}

});

请注意这里末尾使用的是分号“;这里就是获得button的实例,然后对他进行监听,当用户点击时就会发生onClick事件,这里还用到一个方法,就是显示一个短消息,在屏幕停留几秒钟就会自动消失,其方法如下:

public void DisplayToast(String str)

{

Toast.makeText(this, str, Toast.LENGTH_SHORT).show();

}

当然你也可以设置显示长点,即Toast.LENGTH_SHORT改为Toast.LENGTH_LONG

3. 当按键按下是发生的事件

public boolean onKeyDown(int keyCode,KeyEvent event)

{

switch(keyCode)

{

case KeyEvent.KEYCODE_0:

DisplayToast(“你按下数字键0″);

break;

case KeyEvent.KEYCODE_DPAD_CENTER:

DisplayToast(“你按下中间键“);

break;sss

case KeyEvent.KEYCODE_DPAD_DOWN:

DisplayToast(“你按下下方向键“);

break;

case KeyEvent.KEYCODE_DPAD_LEFT:

DisplayToast(“你按下左方向键“);

break;

case KeyEvent.KEYCODE_DPAD_RIGHT:

DisplayToast(“你按下右方向键“);

break;

case KeyEvent.KEYCODE_DPAD_UP:

DisplayToast(“你按下上方向键“);

break;

case KeyEvent.KEYCODE_ALT_LEFT:

DisplayToast(“你按下组合键alt+←”);

break;

}

return super.onKeyDown(keyCode, event);

}

这里所有的keyCode都囊括了,这只是几个比较典型的例子,效果如下:







4. 当按键弹起时发生的事件,代码如下:

public boolean onKeyUp(int keyCode,KeyEvent event)

{

switch(keyCode)

{

case KeyEvent.KEYCODE_0:

DisplayToast(松开数字键0″);

break;

case KeyEvent.KEYCODE_DPAD_CENTER:

DisplayToast(松开中间键);

break;

case KeyEvent.KEYCODE_DPAD_DOWN:

DisplayToast(松开下方向键);

break;

case KeyEvent.KEYCODE_DPAD_LEFT:

DisplayToast(松开左方向键);

break;

case KeyEvent.KEYCODE_DPAD_RIGHT:

DisplayToast(松开右方向键);

break;

case KeyEvent.KEYCODE_DPAD_UP:

DisplayToast(松开上方向键);

break;

case KeyEvent.KEYCODE_ALT_LEFT:

DisplayToast(松开组合键alt+←”);

break;

}

return super.onKeyUp(keyCode, event);

}

效果与上图类似,只是文字不一样

5. 触摸屏事件,当用手或者用笔在触摸屏上做动作是发生,相关代码如下:

public boolean onTouchEvent(MotionEvent event)

{

int iAction=event.getAction();

if(iAction==MotionEvent.ACTION_MOVE)

{

DisplayToast(你在触摸屏上进行了滑动);

}

else

{

return false;

}

return super.onTouchEvent(event);

}

6. 连续点击按键时发生的事件

Publicboolean onKeyMultiple(int keyCode,int repeatCount,KeyEvent event)

{

Return super.onKeyMultiple(keyCode, repeatCount, event);

}

整体效果还不错,又向android迈进一步!!! 源码下载

[转载]好房别让中介抢了-抢房源信息的小程序

mikel阅读(956)

[转载]好房别让中介抢了-抢房源信息的小程序 – 调调儿 – 博客园.

马上又得换房子了,房子好贵啊,房租又涨了,房东涨也就算了,又让中介扒一层皮,悲剧!!

刘淇都说了,乱就乱在中介

但是没办法,只好试试看能否赶在中介前面抢到一手的房源。

好房别让中介抢了,

好白菜别让XX拱了,

好XX别让XX XX了

这个小程序代码借自 :

抢火车票利器:分享一个抓取火车票转让信息的小程序

只是换了winform的皮。

只要在xml中配置一些url就可以监控那些房源信息了

<?xml version="1.0" encoding="utf-8" ?>
<searcher>
    <item name="赶集网" domain="http://bj.ganji.com/" encoding="utf-8" keys="" top="2" exclude="经纪人推广">
        <pattern><![CDATA[<dt><a class="list_title" href="(.*?)" target="_blank">(.*?)</a>[\s\S]+?<span class="list_word">(?<cn>[\s\S]*?)</span>[\s\S]+?class="room">(\S+)?<[\s\S]+?class="price">(\d+)]]></pattern>
        <url desc="北苑.大屯一居"><![CDATA[http://bj.ganji.com/fang1/chaoyangbeiyuan-datun/h1/]]></url>
        <url desc="霍营,立水桥,天通苑一居"><![CDATA[http://bj.ganji.com/fang1/huoying-lishuiqiao-tiantongyuan/h1/]]></url>
    </item>
    <item name="58同城网" domain="" encoding="utf-8" keys="" exclude="" top="2">
        <pattern><![CDATA[<tr><td class="t" style="padding-left:10px;"><a href="(.*?)" target="_blank" class="t">(.*?)</a>(?<cn>[\s\S]*?)</td>[\s\S]*?<b>(\d+)</b>[\s\S]*?"tc" >(\S+?)</td>]]></pattern>
        <url desc="北苑一居"><![CDATA[http://bj.58.com/bjbeiyuan/zufang/i1/]]></url>
        <url desc="大屯一居"><![CDATA[http://bj.58.com/datun/zufang/i1/]]></url>
        <url desc="立水桥一居"><![CDATA[http://bj.58.com/lishuiqiao/zufang/i1/]]></url>
        <url desc="霍营一居"><![CDATA[http://bj.58.com/huoying/zufang/i1/]]></url>
        <url desc="天通苑一居"><![CDATA[http://bj.58.com/tiantongyuan/zufang/i1/]]></url>
    </item>
    <item name="百姓网" domain="http://beijing.baixing.com" encoding="utf-8" exclude="" keys="" top="2">
        <pattern><![CDATA[<td nowrap="nowrap" style="text-align:left;">(?:\S*?)</td><td style="text-align:left"><a href="(.*?)">([\S\s]*?)</a>\s*\((?<cn>[\S\s]*?)</span>\)</td><td nowrap=\\"nowrap\\">(\d+)]]></pattern>
        <url desc="北苑一居"><![CDATA[http://beijing.baixing.com/zhengzu/?%E7%A7%9F%E6%88%BF%E7%B1%BB%E5%9E%8B=%E6%95%B4%E5%A5%97&areaName=chaoyang&areaName2=beiyuan&%E6%88%BF%E5%9E%8B=%E4%B8%80%E5%AE%A4]]></url>
        <url desc="大屯一居"><![CDATA[http://beijing.baixing.com/zhengzu/?%E7%A7%9F%E6%88%BF%E7%B1%BB%E5%9E%8B=%E6%95%B4%E5%A5%97&%E6%88%BF%E5%9E%8B=%E4%B8%80%E5%AE%A4&areaName=chaoyang&areaName2=datun]]></url>
        <url desc="立水桥一居"><![CDATA[http://beijing.baixing.com/zhengzu/?%E7%A7%9F%E6%88%BF%E7%B1%BB%E5%9E%8B=%E6%95%B4%E5%A5%97&%E6%88%BF%E5%9E%8B=%E4%B8%80%E5%AE%A4&areaName=changping&areaName2=lishuiqiao]]></url>
        <url desc="霍营一居"><![CDATA[http://beijing.baixing.com/zhengzu/?%E7%A7%9F%E6%88%BF%E7%B1%BB%E5%9E%8B=%E6%95%B4%E5%A5%97&%E6%88%BF%E5%9E%8B=%E4%B8%80%E5%AE%A4&areaName=changping&areaName2=huoying]]></url>
        <url desc="天通苑一居"><![CDATA[http://beijing.baixing.com/zhengzu/?%E7%A7%9F%E6%88%BF%E7%B1%BB%E5%9E%8B=%E6%95%B4%E5%A5%97&%E6%88%BF%E5%9E%8B=%E4%B8%80%E5%AE%A4&areaName=changping&areaName2=tiantongyuan]]></url>
    </item>
</searcher>

运行开来后就最小化掉,右下角会弹框的。

按下Search..

image

搜出来的都弹在右下角了,点击可打开链接,瞅瞅有多贵。。。

image

OK.

源码:.net4.0+VS2010环境


[转载]ASP.NET 购物车功能分析及实现代码下载

mikel阅读(1150)

[转载]购物车功能分析 – intcry的博客 – 博客园.

最近学习做购物车,所以把我在学习购物车功能实现的一些体会整理下来。
购物车是电子商务平台的一种重要功能,指的是应用于网店的在线购买功能,它类 似于超市购物时使用的推车或篮子,可以暂时把挑选商品放入购物车、删除或更改购买数量,并对多个商品进行一次结款,是网上商店里的一种快捷购物工具。网站 购物车的作用和现实中的超市购物车一样,随意添加,删除,修改商品数量,方便购物者选好商品后一次性付款结账。
购物车的功能应该包括以下几项:
把商品添加到购物车(订购)
删除购物车中已订购的商品、
修改购物车中某一商品的订购数量
清空购物车
显示购物车中商品的清单及数量、价格
购物车实现方式一般主要通过cookie,session或结合数据库;
从我分析国内大型商务网站,如淘宝,当当等,当用户未登录的时候是使用 cookie存数据的,当用户登录了之后,都存到了与用户对应的数据库。当一个顾客把它选中的产品加入购物车后,产品就会暂时存储在网站购物车里,顾客可 以继续选择其它产品,一直到付款提交订单。在未提交订单前购物车内的商品可以保存一定的时间如30、60天等。
现在一些网上说购物车是个临时存储数据的模块,可以将其存放在Session 对象中,个人感觉用session存购物信息只能说是学校学习老师让练习时做的,只能说用它做个玩玩,实际开发中基本上是不会用它的,如果大家都将其存储 在Session对象中,成千上万个用户一同购物的话,想必服务器必将承受巨大的负载,影响服务器的性能。而且Session是有有效期的, 根据服务器的设置不同而不一样长, 如果你在购物的过程中Session超时了, 那么购物车中的东西就会全没了。
而使用数据库存取,在这种购物流程中涉及到对数据库表的频繁操作,尤其是用户 每选购一次商品,都要与数据库进行连接,当用户很多的时候就加大了服务器与数据库的负荷。那么用户选择商品的时候就很有可能频繁更新数据库,增加数据库的 负担。所以个人感觉,在商业网站的购物车设计上,最好使用cookie+数据库方式来存取数据。而且放到购物车里的商品,一般都是有购买意向的商品,但并 不一定会成为真实的订单,这时候,保留这份数据,对数据挖掘、业务分析有至关重要的作用,所以如果有条件,最好把这些数据也存入数据库的。
当然,使用cookie也是有缺点的,cookie是由服务器产生,存储在客户端的一段信息。大多数浏览器支持最大为 4096 字节的 Cookie。由于这限制了 Cookie 的大小,最好用 Cookie 来存储少量数据,或者存储用户 ID 之类的标识符。
浏览器还限制站点可以在用户计算机上存储的 Cookie 的数量。大多数浏览器只允许每个站点存储 20 个 Cookie;如果试图存储更多 Cookie,则最旧的 Cookie 便会被丢弃。有些浏览器还会对它们将接受的来自所有站点的 Cookie 总数作出绝对限制,通常为 300 个。
基于cookie的购物车要求用户浏览器必须支持并设置为启用cookie,否则购物车则失效;
在使用cookie存数据的时候,一个重要的问题就是cookie不能存数组,对象。只能存字符串。所以在存数据的时候需要把购物车中的数据转换成字符串形式存入cookie中,需要的时候再还原加载入购物车。
以下是我使用DataTable虚拟表存入cookie的方法:
public static DataTable dt;   
/// <summary>   
/// 创建虚拟表   
/// </summary>    
protected void BuildDataTables()   
{   
    dt = new DataTable("Product");//新建一张商品表,表名为"Product"   
    dt.Columns.Add(new DataColumn("ProductID"));    //商品编号   
    dt.Columns.Add(new DataColumn("ProductName"));  //商品名称   
    dt.Columns.Add(new DataColumn("quantity"));     //购买数量   
    dt.Columns.Add(new DataColumn("totalPrice"));   //商品总价   
    dt.Columns.Add(new DataColumn("Price"));        //商品单价   
    dt.PrimaryKey = new DataColumn[] { dt.Columns["ProductID"] }; //设置主键为ProductID   
  
}    
/// <summary>   
/// 把虚拟表中的商品编号和购买数量转换成字符串并写入cookie   
/// </summary>    
protected void WriteInCookie()   
{   
    string str = "";   
    foreach (DataRow row in dt.Rows)   
    {   
        str = row["ProductID"] + "|" + row["quantity"] + "," + str;   
    }   
    str = str.TrimEnd(',');   
  
    //往购物车中添加商品   
    HttpCookie aCookie = null;   
    if (HttpContext.Current.Request.Cookies["ShoppingCart"] == null)   
    {   
        //如果Cookies中不存在ShoppingCart,则创建   
        aCookie = new HttpCookie("ShoppingCart");   
    }   
    else  
    {   
        //如果Cookies中存在ShoppingCart,则清空   
        aCookie = HttpContext.Current.Request.Cookies["ShoppingCart"];   
        aCookie.Value = null;   
  
    }   
    aCookie.Value = str;   
    //设置cookie有效期为1天   
    aCookie.Expires = DateTime.Now.AddDays(1);   
    HttpContext.Current.Response.Cookies.Add(aCookie);   
}   
/// <summary>   
/// 从cookie中取出数据填充到虚拟表中   
/// </summary>   
protected void ReadCookie()   
{   
    if (HttpContext.Current.Request.Cookies["ShoppingCart"] != null)   
    {   
        dt = null;   
        BuildDataTables();   
        //如果Cookies中存在ShoppingCart,则取出数据添加到Datatable中   
        HttpCookie aCookie = HttpContext.Current.Request.Cookies["ShoppingCart"];   
  
        string cart = aCookie.Value;   
  
        string[] arr = cart.Split(',');   
  
        for (int i = 0; i < arr.Length; i++)   
        {   
            string[] str = arr[i].Split('|');   
            AddDatatable(Convert.ToInt32(str[0]), Convert.ToInt32(str[1]));   
        }   
    }   
}   
/// <summary>   
/// 往虚拟表中添加数据   
/// </summary>   
/// <param name="ProductID">商品编号</param>   
/// <param name="Quantity">购买数量</param>   
public void AddDatatable(int ProductID, int Quantity)   
{   
    //按照产品编号查询所有产品信息   
    product prod = new ProductInfo().GetProductByProductId(ProductID)[0];   
  
    //新建一行数据   
    DataRow dr = dt.NewRow();   
    dr["ProductID"] = ProductID;   
    dr["ProductName"] = prod.productName;   
    dr["quantity"] = Quantity;   
    dr["Price"] = prod.price;   
    dr["totalPrice"] = Quantity * prod.price;   
    //将这一行数据添加到虚拟表中   
    dt.Rows.Add(dr);   
}            

[原创]修改ASP.NET MVC站点默认首页不使用HomeController

mikel阅读(1021)

最近由于业务需要,网站首页不再只想HomeController的Index而是需要只想StoreController的Index,结果觉得修改Global.asax.cs的MapRoute就行了!可是调试还是访问HomeController的Index

百思不得其解啊!

结果打开Default.aspx.cs一看,傻眼了!

public partial class _Default : Page
{
public void Page_Load(object sender, System.EventArgs e)
{
Response.Redirect("~/Home");
}
}

我勒个去!~~~~不带这样儿的,重定向啊!

直接改之!如下:

public partial class _Default : Page
{
public void Page_Load(object sender, System.EventArgs e)
{
Response.Redirect("~/Store");
}
}

感想就是简单问题复杂化,害得我还去翻了翻ASP.NET MVC的源码!丢死人列!

[转载]html5全接触(二)--BounceBall小游戏简易教程

mikel阅读(1757)

[转载]html5全接触(二)–BounceBall小游戏简易教程 – 叶落为重生每片落下的叶子都是为了下一次的涅槃…^_^ – 博客园.

最近一段时间都比较忙,好久没更新博客了,遵循着“时间就像那啥,挤挤总会有的”的原则,承接着上一篇html5先关的博文,继续我们的趣味html5之旅。

前一段时间很流行用html5写小游戏,当了解了一些常用的api之后,你会发现,写一些简单的小游戏自娱自乐也不会那么困难,当然,做逻辑和界面复杂的游戏除外。以下会提供一个弹球小游戏的简单教程,希望感兴趣的朋友能在编码中找到一点乐趣。

<!– 注:以下demo木有神马高深的东东,大牛们觉得无味请略过。同时,由于砖块厚度与弹球的纵向变换单元的比例不协调,故没做砖块的侧向碰撞监测.. –>

既然是教程,咱们还是一步一步来:(代码可以直接在textarea里看到,源码就不贴了)
【step 1】画个小球

【step 2】为了方便扩展以及养成良好的编码习惯,我们稍微做点封装和结构化;同时增加画矩形的方法rect和清除画布的方法clear

【step 3】让小球动起来,因为canvas是画布,想让小球动起来,最直接的想法莫过于通过定时器setInterval之类,每次先清除画布,然后重绘一个小 球,定位到路径的下一点即可,视觉上连贯起来就动起来了。咱们还是上代码(我这个是低配版,所以每次的位置变化量我都写死的)

【step 4】做边界碰撞的反弹效果,也很简单,判断下小球的坐标x y,监测坐标与canvas高宽,在临界点让对应的变化量做反转即可,比如碰撞上下边界,就让dy = -dy即可。思路化为代码:


【step 5】绘制弹板,并且绑定键盘事件,让挡板可以左右移动。绘制的方法很简单,直接用canvas的rect方法,前面已经写好了,绑定键盘事件,咱们直接绑 在左右按钮上,监测keydown,keyup事件,判断keyCode,然后每次重绘的时候让挡板移位即可。


【step 6】修改碰撞监测,小球落在挡板内可以继续,落在挡板外游戏结束。只需要修改撞击下边界的条件。即当球的y坐标大于画布总高度减去挡板厚度时,判断此时球 的x坐标是否在挡板范围内。如果是那么小球弹回,游戏继续,否则游戏结束。游戏结束也很简单,直接清掉计数器。


【step 7】经过以上阶段,下面可以开始画砖块了,也很简单,定义一个二维的矩阵,根据行列数,算好每个砖块的宽度,通过之前写好的rect把这个二维矩阵转换成小方块就行了。

initBricks : function (row, col) {
B.row
= row;
B.col
= col;
B.w
= W/col – 1;
B.h = 15;
B.pad
= 1;
B.bricks
= new Array(row);

for (var i=0; i<row; i++) {
B.bricks[i]
= new Array(col);
for (var j=0; j<col; j++) {
B.bricks[i][j]
= 1;
}
}
},
drawBricks :
function () {
for (var i=0; i<B.row; i++) {
for (var j=0; j<B.col; j++) {
B.bricks[i][j]
=== 1 && this.rect(j*(B.w+B.pad) + B.pad, i*(B.h+B.pad)+B.pad, B.w, B.h);
}
}
},


【step 8】砖块碰撞。这里只做粗糙的碰撞监测,而且由于砖块厚度太低,dy都比砖块厚度更大,所以侧向的碰撞这里没做考虑。考虑纵向的碰撞的话,简单点,可以实 时监控小球的y坐标,当满足小球坐标在砖块区并且砖块存在,那么就表示小球有和砖块碰撞,那么清除当前碰撞砖块,小球反向即可。

hitBrick : function (x, y) {
var rh = B.h + B.pad,
cw
= B.w + B.pad,
row
= Math.floor(y/rh),
col = Math.floor(x/cw);
if (y < B.row*rh && row >= 0 && col >= 0 && B.bricks[row][col] === 1) {
dy
= dy;
B.bricks[row][col]
= 0;
}
},


好了,其实到这里差不多可以结束了,算是一个最最基础的打砖块小游戏,当然,感兴趣的同学可以自己再加其他的元素,增加其可玩性,比如我最开始那个 加了暂停功能,加了颜色,加了结束画面。甚至你可以自己去加砖块道具,设置不同关卡,增加不同砖块属性,增加各种玩法来增强其可玩性。
仅仅作为教程,我觉得基本目的达到了。由于篇幅原因,有些具体的细节没法一一讲清楚。请包涵。如果对canvas的api不是很熟悉的同学可以先去查阅下资料。

[转载]Android开发——MediaProvider源码分析(1)

mikel阅读(1187)

[转载]Android开发——MediaProvider源码分析(1) – 努力吧,专注Android – 博客园.

转载请注明出处。

————–START————

MediaProvider包括五个类:

  • com.Android.providers.media.MediaProvider
  • com.Android.providers.media.MediaScannerCursor
  • com.Android.providers.media.MediaScannerReceiver
  • com.android.providers.media.MediaScannerService
  • com.android.providers.media.MediaThumbRequest

1.MediaProvider

此类继承ContentProvider,实现一个内容提供者。主要用于创建媒体库的数据库表。有自己创建过ContentProvider的同学相信都比较清楚的。

特别说明一下在MediaProvider中有个广播接收者,代码如下:

private BroadcastReceiver mUnmountReceiver = new BroadcastReceiver() {

@Override

public void onReceive(Context context, Intent intent) {

if (intent.getAction().equals(Intent.ACTION_MEDIA_EJECT)) {

// Remove the external volume and then notify all cursors backed by

// data on that volume

detachVolume(Uri.parse("content://media/external"));

sFolderArtMap.clear();

MiniThumbFile.reset();

}

}

};

此接收者是用来接收Sdcard卸载的广播。当Sdcard从手机中分离出来的时候,Sdcard中的媒体文件相对应的数据库将无法操作。

private void detachVolume(Uri uri) {

//判断是否是同一个进程

if (Process.supportsProcesses() &amp;&amp; Binder.getCallingPid() != Process.myPid()) {

throw new SecurityException(

"Opening and closing databases not allowed.");

}

//此方法只是操作Sdcard的媒体数据库,不支持手机内存的媒体数据库

String volume = uri.getPathSegments().get(0);

if (INTERNAL_VOLUME.equals(volume)) {

throw new UnsupportedOperationException(

"Deleting the internal volume is not allowed");

} else if (!EXTERNAL_VOLUME.equals(volume)) {

throw new IllegalArgumentException(

"There is no volume named " + volume);

}

synchronized (mDatabases) {

DatabaseHelper database = mDatabases.get(volume);

if (database == null) return;

try {

// touch the database file to show it is most recently used

File file = new File(database.getReadableDatabase().getPath());

file.setLastModified(System.currentTimeMillis());

} catch (SQLException e) {

Log.e(TAG, "Can't touch database file", e);

}

//移除数据库

mDatabases.remove(volume);

database.close();

}

getContext().getContentResolver().notifyChange(uri, null);

if (LOCAL_LOGV) Log.v(TAG, "Detached volume: " + volume);

注意移除数据库并非删除数据库文件(*.db),mDatabases是一个HashMap<String,DatabaseHelper>,移除的含义是暂时无法操作,也可以说说是查询返回的数据都是空的。

2.MediaScannerCursor

一个自定义游标,用来查询媒体文件的扫描状态。主要有一个volume字段,用来区分是内置媒体数据库还是Sdcard的媒体数据库。

3.MediaScannerReceiver

此类实现广播接收者。接收到广播的时候对手机的媒体文件进行扫描。

public class MediaScannerReceiver extends BroadcastReceiver

{

private final static String TAG = "MediaScannerReceiver";

@Override

public void onReceive(Context context, Intent intent) {

String action = intent.getAction();

Uri uri = intent.getData();

String externalStoragePath = Environment.getExternalStorageDirectory().getPath();

//系统启动完毕

if (action.equals(Intent.ACTION_BOOT_COMPLETED)) {

// scan internal storage

scan(context, MediaProvider.INTERNAL_VOLUME);

} else {

if (uri.getScheme().equals("file")) {

// handle intents related to external storage

String path = uri.getPath();

if (action.equals(Intent.ACTION_MEDIA_MOUNTED/*Sdcard挂载广播*/) &amp;&amp;

externalStoragePath.equals(path)) {

scan(context, MediaProvider.EXTERNAL_VOLUME);

} else if (action.equals(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE/*单个文件扫描广播*/) &amp;&amp;

path != null &amp;&amp; path.startsWith(externalStoragePath + "/")) {

scanFile(context, path);

}

}

}

}

扫描分为两种三种情况:

a,启动完毕扫面手机内存中的媒体文件

b.sdcard挂载完毕扫描扩展卡的媒体文件

c,扫描单个文件

应用实例:我们可以发送不同的广播让系统去扫描媒体文件。当需要扫描单个文件的时候需要设置一些参数,如下:


/**

* 扫描文件

*

* @param filePath 文件路径

* @author http://t.sina.com.cn/halzhang

*/

public void scanOneFile(final String filePath) {

Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);

Uri uri = Uri.parse("file://" + filePath);

intent.setData(uri);

sendBroadcast(intent);

}

接着看一下scanscenFile两个方法:

private void scan(Context context, String volume/*内置卡或者外置卡*/) {

Bundle args = new Bundle();

args.putString("volume", volume);

context.startService(

new Intent(context, MediaScannerService.class).putExtras(args));

}

private void scanFile(Context context, String path/*文件路径*/) {

Bundle args = new Bundle();

args.putString("filepath", path);

context.startService(

new Intent(context, MediaScannerService.class).putExtras(args));

}

两个方法都是启动MediaScannerService去扫描媒体文件的。

关于MediaScannerSerive且听下回分解。

——————-END————–

[转载]Android 使用AsyncTask 后监听异步加载完毕的动作

mikel阅读(899)

[转载]Android 使用AsyncTask 后监听异步加载完毕的动作 – Terry_龙 – 博客园.

AsyncTask 的使用方法网上有很多例子,使用起来也非常的方便。这里就不详细说具体的使用方法了,同学可以Google 一下,很多。

场景模拟

当我们在加载一个列表的时候,比如GridView ,这时候我们考虑到不阻塞UI的做法,一般会使用线程Thread 、Timer 或者使用AsyncTask ,而这些操作都是在在后台另外开一个线程给我们找数据,具体得到的数据需要使用Handler 去更新UI,AsyncTask 也是一样使用到的Handler 只是它将Handler 封装在了onPostExecute 执行操作中。而这一操作可能会产生一个问题,比如你有一个列表更新数据库使用到的是AsyncTask 异步操作的方式更新UI,而你的需求是当我一进来这个列表就统计这个列表的数据的数量或者让某一行数据的状态为选中状态。 传统做法是直接new 一个AsyncTask 类让它execute(); 之后会再操作UI。想法是对的,但有一个问题我们要注意到,因为它是异步加载数据的方式,而你的数据量比较多或许查找数据需要一定的时间的时候,这时使用 AsyncTask 执行异步加载后更新UI再操作UI对象,可能会报空指针。

这个问题的产生是,我们都知道代码的执行是自上而下执行,当你使用异步加载数据的时候,代码让你去执行异步操作就不管了(多线程),而继续会往下执行代 码,你下面的代码就是操作列表里面的UI,这时可想而知,异步加载数据还没有结束还没有对你的UI进行更新,这些你的列表应该是空的,而操作一个空的列表 就会报空指针。

分析问题

使用过AsyncTask 的同学都知道一个异步加载数据最少要重写以下这两个方法:

  • doInBackground   后台执行,比较耗时的操作都可以放在这里。注意这里不能直接操作UI。
  • onPostExecute   相当于Handler 处理UI的方式,在这里面可以使用在doInBackground 得到的结果处理操作UI。

有必要的话你还得重写以下这三个方法,但不是必须的:

  • onProgressUpdate   可以使用进度条增加用户体验度。
  • onPreExecute           这里是最终用户调用Excute时的接口
  • onCancelled             用户调用取消时,要做的操作

根据上面的思路,可以看出,最终数据加载并显示出来这一系列的操作都在onPostExecute  这个方法里面,那么如何监听所有UI都己经在onPostExecute   里面处理完成了,再去执行我们自己要操作呢?

解决问题

这里给出我自己解决这一问题的思路,有更好想法的朋友欢迎跟贴共同探讨。

首先创建一个接口

private interface isLoadDataListener {
public void loadComplete();
}

声明这一接口变量

private isLoadDataListener loadLisneter;

给接口赋值,得到接口对象

public void setLoadDataComplete(isLoadDataListener dataComplete) {
this.loadLisneter = dataComplete;
}

之后在AsyncTask 的onPostExecute处理UI完成后调用该接口,下面给出一个我以前项目使用到的AsyncTask 类:

class loadGridAsyncTask extends AsyncTask<Integer, Integer, AppsAdapter> {

private int poindex;

public loadGridAsyncTask(int positionindex) {
this.poindex = positionindex;
}

@Override
protected AppsAdapter doInBackground(Integer… params) {
// TODO Auto-generated method stub
// mAppsModel.clear();
Cursor temp = dbHelper.queryPageById(poindex);
loadPage(mApps, temp);
temp.close();
return new AppsAdapter(STB.this, mAppsModel);
}

@Override
protected void onPostExecute(AppsAdapter result) {

gridViewExt itemGrid = (gridViewExt) viewFlipper
.getChildAt(poindex);
itemGrid.setColumnCount(pageColumnCount);
itemGrid.setAdapter(result);
if (loadLisneter != null) {
loadLisneter.loadComplete();
}

}

}

通过上面的代码,我们就得到一个数据加载完成后返回的接口,接下来的问题就是我们利用这个接口来处理我们的UI了,比如让某一UI选中,得到这个列表的UI数量等,看下面的代码:

new loadGridAsyncTask(1).execute();
setLoadDataComplete(
new isLoadDataListener() {

@Override
public void loadComplete() {
// TODO Auto-generated method stub
//这里执行你要的操作,当UI更新完成后会自动调用这里面的代码                                        }
});

此篇文章希望能对入门不久的Android 开发者有帮助。

[转载]Android系统数据共享---Content Provider学习小结

mikel阅读(823)

[转载]Android系统数据共享—Content Provider学习小结 – 宁 静 致 远 – 博客园.

Content Provider作为Android应用程序中的四大组件之一,主要是为了实现在各应用程序之间数据共享,增强应用程序的复用,例如,在开发过程中,需要获取手机中的通讯录信息,这时完全不需要自己重新开发读取数据的整个过程,

而是直接访问系统自带的Content Provider对象来直接获取数据(此例子只是说明有现成的能满足需要的ContentProvider即可随时“拿来”,只要有相应权限, 不用管它是来自哪个应用程序里)。

在Content Provider使用过程中,还需要借用ContentResolver对象作为代理,间接操作ContentProvider来获取数据。 ContentProvider可以看作是对应着一个或多个数据表(类似查询后返回的数据集)。另外重要的概念还有Uri,通过它来找到相应的 ContentProvider、并能定位到对应的数据表或字段。

相关的知识经过整理,特列出下面这张思维导图,以方便自己经常温故,也希望能给大家带来一丝思维上的提示,也算是抛砖引玉了,呵呵

Content Provider知识

夜已经深了,明天又要上班了,面对新的一周,整理思绪,继续前行……