思路如下:
1.实现一个只有程序安装后第一次打开时才会执行的方法,这样方便在配置文件PreferenceManager.getDefaultSharedPreferences(Context)中保存程序的一些信息,比如apk安装时间,这个值之后会和服务器上的apk修改时间做比较来判断是否要更新,如我这个例子里的showOnFirstLaunch(),程序入口活动JumpActivity onCreate时获取程序包信息中的versionCode版本代码号,如果与配置中保存的版本号不一致,表示安装新版后第一次打开程序,则执行之后的代代码片段,如保存当前版本号,设置新版已安装的值为true,多设了这个值的目的是,我们自动更新时虽然下载了新版本,但是没有对其安装,则打开旧版本时提示下载的更新没有被安装,给用户一个安装的选择。
JumpActivity.class
private void showOnFirstLaunch() { setContentView(R.layout.splash); try { PackageInfo info = getPackageManager().getPackageInfo(PACKAGE_NAME, 0); int currentVersion = info.versionCode; prefs = PreferenceManager.getDefaultSharedPreferences(this); int lastVersion = prefs.getInt(PreferencesActivity.KEY_HELP_VERSION_SHOWN, 0); if (currentVersion != lastVersion) {//安装好后第一次启动 //设置版本号 prefs.edit().putInt(PreferencesActivity.KEY_HELP_VERSION_SHOWN, currentVersion).commit(); prefs.edit().putBoolean(PreferencesActivity.KEY_LATEST_VERSION_INSTALL, true).commit(); File apk = new File(apkFile); if(apk.exists())apk.delete(); //显示关于对话框 Intent intent = new Intent(Intent.ACTION_VIEW); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); intent.setClassName(this, HelpActivity.class.getName()); startActivity(intent); finish(); }else {//不是第一次启动 //是否完成上次下载的更新的安装 if(!prefs.getBoolean(PreferencesActivity.KEY_LATEST_VERSION_INSTALL, true)){ new AlertDialog.Builder(JumpActivity.this) .setTitle(R.string.app_update_title) .setCancelable(false) .setMessage(R.string.app_update_not_install) .setPositiveButton(R.string.button_update_ok,new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { File apk = new File(apkFile); if(apk.exists())install();//下载的文件存在则安装 else {//不存在则提示重新下载 Intent update = new Intent(JumpActivity.this,UpdateService.class); startService(update); } } }) .setNegativeButton(R.string.button_update_no,new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { //如果放弃本次更新,将不再进行提示 prefs.edit().putBoolean(PreferencesActivity.KEY_LATEST_VERSION_INSTALL, true).commit(); Log.v(TAG, "129"); launchActivity(); } }) .show(); return; } launchActivity(); } } catch (PackageManager.NameNotFoundException e) { Log.w(TAG, e); } }
private void install() { Intent i = new Intent(); i.setAction(Intent.ACTION_VIEW); i.setDataAndType(Uri.fromFile(new File(apkFile)), "application/vnd.android.package-archive"); startActivity(i); }
这个例子做了一个简单的跳转功能,通过Integer.parseInt(Build.VERSION.SDK) >= 5来分别启动适合不同系统版本的活动,使程序可以兼容跟多的手机。
private void launchActivity(){ if (Integer.parseInt(Build.VERSION.SDK) >= CameraManager.VERSION_CODES_LEVEL) { intent = new Intent(this,BJFILE.class); }else { intent = new Intent(this,EarlyBJFILE.class); } startActivity(intent); finish(); }
2.在BJFILE或EarlyBJFILE活动中重我们使用一个Service来检查首否需要更新应用,当服务器上的apk文件的最后修改时间与程序配置文件中的最后修改时间不同,则表示发现新更新。
UpdateService.class
@Override public void onStart(Intent intent, int startId) { super.onStart(intent, startId); Log.v(TAG, "onStart"); new Thread(){ public void run(){ if(checkUpdate()){//如果有更新,则显示更新界面UpdateActivity,类似一个对话框 Intent update = new Intent(UpdateService.this,UpdateActivity.class); update.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); update.putExtra(VERSITON, newVersion); startActivity(update); }else stopSelf(); } }.start(); } //可能是不存在该请求对象,返回的向应头中没有设置last-modified域,则取到的值为0 private long getLastModified() { try { URL url = new URL(/blog_article/UpdateActivity.updateURL); URLConnection con = (URLConnection) url.openConnection(); Log.v(TAG, "con.getLastModified()"+con.getLastModified()); return con.getLastModified(); } catch (MalformedURLException e) { e.printStackTrace(); Log.v(TAG, "MalformedURLException"); return 1; } catch (IOException e) { e.printStackTrace(); Log.v(TAG, "IOException"); return 2; } } //判断是否需要更新 private boolean checkUpdate(){ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); long latest = prefs.getLong(PreferencesActivity.KEY_LATEST_VERSION, 0); newVersion = getLastModified(); Log.v(TAG, "latest=" + latest); Log.v(TAG, "newVersion=" + newVersion); if(newVersion>latest)return true; else return false; }
3.UpdateActivity的样式为
<style name="BackgroundOnly"> <item name="android:windowBackground">@null</item> <item name="android:windowContentOverlay">@null</item> <item name="android:windowAnimationStyle">@null</item> <item name="android:windowNoTitle">true</item> <item name="android:windowNoDisplay">true</item> <item name="android:windowIsFloating">true</item> </style>
源代码
import java.io.BufferedReader; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.DefaultHttpClient; import android.app.Activity; import android.app.AlertDialog; import android.app.ProgressDialog; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.net.Uri; import android.os.Bundle; import android.os.Environment; import android.os.Handler; import android.os.Message; import android.preference.PreferenceManager; import android.util.Log; public class UpdateActivity extends Activity{ private static String TAG = "UpdateActivity"; public static String updateURL = "http://www.bjnote.com/down4/bjnote.apk";//更新程序位置 private static String updateContentURL="http://www.bjnote.com/down4/bjnote.txt";//更新内容 private StringBuffer sb =null; private ProgressDialog download; private Handler mHandler; protected static final int GUI_STOP_NOTIFIER = 0x108; protected static final int GUI_THREADING_NOTIFIER = 0x109; protected static final int GUI_ERROR_NOTIFIER = 0x110; protected static final int GUI_IO_NOTIFIER = 0x111; protected static final int GUI_INTERRUPTED_NOTIFIER = 0x112; protected static final int GUI_PROGRESS_NOTIFIER = 0x113;//开始进度条的进度值显示 protected static final int GUI_UPDATE_CONTENT_NOTIFIER = 0x114;//有更新文本 private int count = 0; private long total; private DownThread downThread; private UpdateContent updateContent; private SharedPreferences prefs; private long newVersion; private String apkFile = Environment.getExternalStorageDirectory()+"/bjfile.apk"; class DownThread extends Thread{ private boolean cancel=false; private File file; public void run() { HttpClient client = new DefaultHttpClient(); HttpGet get = new HttpGet(updateURL); HttpResponse response = null; FileOutputStream fileOutputStream = null; InputStream is=null; try { response = client.execute(get); if(response.getStatusLine().getStatusCode()!=200) throw new IOException("StatusCode!=200"); HttpEntity entity = response.getEntity(); total = entity.getContentLength(); mHandler.sendEmptyMessage(GUI_PROGRESS_NOTIFIER); is = entity.getContent(); if (is != null) { file = new File(apkFile); fileOutputStream = new FileOutputStream(file); byte[] buf = new byte[1024]; int ch = -1; while ((ch = is.read(buf)) != -1) { if(cancel)throw new InterruptedException(); count+=ch; fileOutputStream.write(buf, 0, ch); mHandler.sendEmptyMessage(GUI_THREADING_NOTIFIER); } if(count==total)mHandler.sendEmptyMessage(GUI_STOP_NOTIFIER); fileOutputStream.flush(); if(fileOutputStream!=null)fileOutputStream.close(); } } catch (ClientProtocolException e) { e.printStackTrace(); mHandler.sendEmptyMessage(GUI_ERROR_NOTIFIER); if(file.exists())file.delete(); } catch (IOException e) { if(file.exists())file.delete(); e.printStackTrace(); mHandler.sendEmptyMessage(GUI_IO_NOTIFIER); } catch (InterruptedException e) { e.printStackTrace(); if(file.exists())file.delete(); mHandler.sendEmptyMessage(GUI_INTERRUPTED_NOTIFIER); } finally{ try { is.close(); } catch (IOException e1) { e1.printStackTrace(); } } } private void setCancel(boolean isCancel){ cancel = isCancel; } } //读取更新内容 class UpdateContent extends Thread{ public void run() { HttpClient client = new DefaultHttpClient(); HttpGet get = new HttpGet(updateContentURL); HttpResponse response = null; BufferedReader bis = null; try { response = client.execute(get); //添加更新提示 sb.append(UpdateActivity.this.getString(R.string.app_update_tip)); if(response.getStatusLine().getStatusCode()!=200){ throw new IOException("StatusCode!=200"); } HttpEntity entity = response.getEntity(); //XXX 可能需要改成其他的编码,Apple默认是gb2312 bis = new BufferedReader(new InputStreamReader(entity.getContent(),"gb2312")); String s=null; if (bis != null) { s=bis.readLine(); while (s!=null) { sb.append("\n"+s); s=bis.readLine(); } mHandler.sendEmptyMessage(GUI_UPDATE_CONTENT_NOTIFIER); bis.close(); } } catch (ClientProtocolException e) { e.printStackTrace(); stopService(); } catch (IOException e) { e.printStackTrace(); stopService(); } } } @Override public void onCreate(Bundle bundle) { super.onCreate(bundle); Log.v(TAG, "onCreate"); mHandler = new Handler() { public void handleMessage(Message msg) { switch(msg.what) { case GUI_THREADING_NOTIFIER: download.setProgress(count); break; case GUI_STOP_NOTIFIER: download.dismiss(); prefs.edit().putLong(PreferencesActivity.KEY_LATEST_VERSION, newVersion).commit(); //表示已下载但还未安装 prefs.edit().putBoolean(PreferencesActivity.KEY_LATEST_VERSION_INSTALL, false).commit(); new AlertDialog.Builder(UpdateActivity.this) .setCancelable(false) .setTitle(R.string.app_update_title) .setMessage(R.string.button_update_finish) .setPositiveButton(R.string.button_ok,new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { install(); } }) .setNegativeButton(R.string.button_exit, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { stopService(); } }) .show(); break; case GUI_IO_NOTIFIER: case GUI_ERROR_NOTIFIER: if(download!=null)download.dismiss(); new AlertDialog.Builder(UpdateActivity.this) .setCancelable(false) .setMessage(R.string.app_update_error) .setPositiveButton(R.string.button_exit,new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { stopService(); } }) .show(); break; case GUI_INTERRUPTED_NOTIFIER: download.dismiss(); new AlertDialog.Builder(UpdateActivity.this) .setCancelable(false) .setMessage(R.string.app_update_cancel) .setPositiveButton(R.string.button_exit,new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { stopService(); //如果放弃本次更新,将不再进行提示 prefs.edit().putLong(PreferencesActivity.KEY_LATEST_VERSION, newVersion).commit(); prefs.edit().putBoolean(PreferencesActivity.KEY_LATEST_VERSION_INSTALL, true).commit(); } }) .show(); break; case GUI_PROGRESS_NOTIFIER: download.setMax((int) total); download.setProgress(0); break; case GUI_UPDATE_CONTENT_NOTIFIER: download(); break; } } }; newVersion = getIntent().getLongExtra(UpdateService.VERSITON, 0); prefs = PreferenceManager.getDefaultSharedPreferences(this); sb = new StringBuffer(); updateContent = new UpdateContent(); updateContent.start(); } private void install() { Intent i = new Intent(); i.setAction(Intent.ACTION_VIEW); i.setDataAndType(Uri.fromFile(new File(apkFile)), "application/vnd.android.package-archive"); startActivity(i); } private void download() { downThread = new DownThread(); new AlertDialog.Builder(this) .setTitle(R.string.app_update_title) .setCancelable(false) .setMessage(sb.toString()) .setPositiveButton(R.string.button_update_ok,new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { download = new ProgressDialog(UpdateActivity.this); download.setMessage(getString(R.string.app_update_warn)); download.setCancelable(false); download.setIndeterminate(false); download.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); download.setButton(getString(R.string.button_cancel), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { downThread.setCancel(true); } }); download.show(); downThread.start(); } }) .setNegativeButton(R.string.button_update_no, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { stopService(); prefs.edit().putLong(PreferencesActivity.KEY_LATEST_VERSION, newVersion).commit(); prefs.edit().putBoolean(PreferencesActivity.KEY_LATEST_VERSION_INSTALL, true).commit(); } }) .show(); } /** * stopSelf,服务关闭自身 */ private void stopService() { Intent stop = new Intent(this,UpdateService.class); stopService(stop); Log.v(TAG, "stopService"); finish(); Log.v(TAG, "finish activity"); } @Override public void onDestroy() { super.onDestroy(); prefs=null; Log.v(TAG, "onDestroy"); } }
基于OPhone 2.0的2D动画实践(二) OPhone平台开发, 2010-10-18 16:53:43 标签 : OPhone2.0 2D 动画
本系列文章主要介绍了OPhone 2.0 SDK提供的两种实现2D动画的方式:帧动画和补间动画。文章的每个知识点都提供了精彩的实例以向读者展示2D动画的具体实现方法。通过对本系列文章的学习,读者可利用2D动画实现非常绚丽的界面效果。
补间动画简介
如果动画中的图像变换比较有规律时,可以采用自动生成中间图像的方式来生成动画。例如,图像的移动、旋转、缩放等。当然,还有更复杂的情况,例如,由正方形变成圆形、圆形变成椭圆形,这些变化过程中的图像都可以根据一定的数学算法自动生成。而我们只需要指定动画的第1帧和最后一帧的图像即可。这种自动生成中间图像的动画被称为补间(Tween)动画。
补间动画的优点是节省硬盘空间。这是因为这种动画只需要提供两帧图像(第1帧和最后一帧),其他的图像都由系统自动生成。当然,这种动画也有一定的缺点,就是动画很复杂时无法自动生成中间图像,例如,由电影画面组件的动画,由于每幅画面过于复杂,系统无法预料下一幅画面是什么样子。因此,这种复杂的动画只能使用帧动画来完成。在本节将介绍OPhone SDK提供的4种补间动画效果:移动、缩放、旋转和透明度。OPhone SDK并未提供更复杂的补间动画。如果要实现更复杂的补间动画,需要开发人员自已编码来完成。
移动补间动画
移动是最常见的动画效果。我们可以通过配置动画文件(xml文件)或Java代码来实现补间动画的移动效果。补间动画文件需要放在res\anim目录中。在动画文件中通过<translate>标签设置移动效果。假设在res\anim目录下有一个动画文件:test.xml,该文件的内容如下:
<translate xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@android:anim/accelerate_interpolator" android:fromXDelta="0" android:toXDelta="320" android:fromYDelta="0" android:toYDelta="0" android:duration="2000" />
从上面的配置代码可以看出,<translate>标签中设置了6个属性,这6个属性的含义如下:
android:interpolator:表示动画渲染器。通过android:interpolator属性可以设置3个动画渲染器:accelerate_interpolator(动画加速器)、decelerate_interpolator(动画减速器)和accelerate_decelerate_interpolator(动画加速减速器)。动画加速器使动画在开始时速度最慢,然后逐渐加速。动画减速器使动画在开始时速度最快,然后逐渐减速。动画加速减速器使动画在开始和结束时速度最慢,但在前半部分时开始加速,在后半部分时开始减速。
android:fromXDelta:动画起始位置的横坐标。
android:toXDelta:动画结束位置的横坐标。
android:fromXDelta:动画起始位置的纵坐标。
android:toYDelta:动画结束位置的纵坐标。
android:duration:动画的持续时间。单位是毫秒。也就是说,动画要在android:duration属性指定的时间内从起始点移动到结束点。
装载补间动画文件需要使用android.view.animation.AnimationUtils. loadAnimation方法,该方法的定义如下:
public static Animation loadAnimation(Context context, int id);
其中id表示动画文件的资源ID。装载test.xml文件的代码如下:
Animation animation = AnimationUtils.loadAnimation(this, R.anim.test);
假设有一个EditText组件(editText),将test.xml文件中设置的补间动画应用到EditText组件上的方式有如下两种:
1. 使用EditText类的startAnimation方法,代码如下:
editText.startAnimation(animation);
2. 使用Animation类的start方法,代码如下:
// 绑定补间动画 editText.setAnimation(animation); // 开始动画 animation.start();
使用上面两种方式开始补间动画都只显示一次。如果想循环显示动画,需要使用如下的代码将动画设置成循环状态。
animation.setRepeatCount(Animation.INFINITE);
上面两行代码在开始动画之前和之后执行都没有问题。
移动补间动画的实例
本例的动画效果是在屏幕上方的EditText组件从左到右循环匀速水平移动。EditText下方的小球上下移动。从上到下移动时加速。从下到上移动时减速。
本例涉及到3个动画渲染器:accelerate_interpolator、decelerate_interpolator和linear_interpolator。其中前两个动画渲染器可以直接作为android:interpolator属性的值,而linear_interpolator虽然在系统中已定义,但由于不是public的,因此,需要自己定义linear_interpolator.xml文件。当然,也可以将系统的linear_interpolator.xml文件复制到Eclipse工程中的res\anim目录下。
在本例中定义了3个动画文件,其中translate_right.xml被应用于EditText组件。translate_bottom.xml(从上到下移动,加速)和translate_top.xml(从下到上移动,减速)被应用于小球(ImageView组件)。这3个动画文件的内容如下:
translate_right.xml <translate xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@anim/linear_interpolator" android:fromXDelta="-320" android:toXDelta="320" android:fromYDelta="0" android:toYDelta="0" android:duration="5000" /> translate_bottom.xml <translate xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@android:anim/accelerate_interpolator" android:fromXDelta="0" android:toXDelta="0" android:fromYDelta="0" android:toYDelta="260" android:duration="2000" /> translate_top.xml <translate xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@android:anim/decelerate_interpolator" android:fromXDelta="0" android:toXDelta="0" android:fromYDelta="260" android:toYDelta="0" android:duration="2000" />
EditText组件的循环水平移动可以直接使用setRepeatMode和setRepeatCount方法进行设置。而小球的移动需要应用两个动画文件。本例采用的方法是在一个动画播放完后,再将另一个动画文件应用到显示小球的ImageView组件中。这个操作需要在AnimationListener接口的onAnimationEnd方法中完成。
运行本例后,单击【开始动画】按钮后,EditText组件从屏幕的左侧出来,循环水平向右移动,当EditText组件完全移进屏幕右侧时,会再次从屏幕左侧出来。同时小球会上下移动。效果如图1所示。
图1 移动补间动画
本例的完整代码如下:
package net.blogjava.mobile; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.view.View.OnClickListener; import android.view.animation.Animation; import android.view.animation.AnimationUtils; import android.view.animation.Animation.AnimationListener; import android.widget.Button; import android.widget.EditText; import android.widget.ImageView; public class Main extends Activity implements OnClickListener, AnimationListener { private EditText editText; private ImageView imageView; private Animation animationRight; private Animation animationBottom; private Animation animationTop; // animation参数表示当前应用到组件上的Animation对象 @Override public void onAnimationEnd(Animation animation) { // 根据当前显示的动画决定下次显示哪一个动画 if (animation.hashCode() == animationBottom.hashCode()) imageView.startAnimation(animationTop); else if (animation.hashCode() == animationTop.hashCode()) imageView.startAnimation(animationBottom); } @Override public void onAnimationRepeat(Animation animation) { } @Override public void onAnimationStart(Animation animation) { } @Override public void onClick(View view) { // 开始EditText的动画 editText.setAnimation(animationRight); animationRight.start(); animationRight.setRepeatCount(Animation.INFINITE); editText.setVisibility(EditText.VISIBLE); // 开始小球的动画 imageView.startAnimation(animationBottom); } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); editText = (EditText) findViewById(R.id.edittext); editText.setVisibility(EditText.INVISIBLE); Button button = (Button) findViewById(R.id.button); button.setOnClickListener(this); imageView = (ImageView) findViewById(R.id.imageview); animationRight = AnimationUtils.loadAnimation(this,R.anim.translate_right); animationBottom = AnimationUtils.loadAnimation(this,R.anim.translate_bottom); animationTop = AnimationUtils.loadAnimation(this, R.anim.translate_top); animationBottom.setAnimationListener(this); animationTop.setAnimationListener(this); } }
缩放补间动画
通过<scale>标签可以定义缩放补间动画。下面的代码定义了一个标准的缩放补间动画。
<scale xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@android:anim/decelerate_interpolator" android:fromXScale="0.2" android:toXScale="1.0" android:fromYScale="0.2" android:toYScale="1.0" android:pivotX="50%" android:pivotY="50%" android:duration="2000" />
<scale>标签和<translate>标签中有些属性是相同的,而有些属性是<scale>标签特有的,这些属性的含义如下:
android:fromXScale:表示沿X轴缩放的起始比例。
android:toXScale:表示沿X轴缩放的结束比例。
android:fromYScale:表示沿Y轴缩放的起始比例。
android:toYScale:表示沿Y轴缩放的结束比例。
android:pivotX:表示沿X轴方向缩放的支点位置。如果该属性值为"50%",则支点在沿X轴中心的位置。
android:pivotY:表示沿Y轴方向缩放的支点位置。如果该属性值为"50%",则支点在沿Y轴中心的位置。
其中前4个属性的取值规则如下:
0.0:表示收缩到没有。
1.0:表示正常不收缩。
大于1.0:表示将组件放大到相应的比例。例如,值为1.5,表示放大到原组件的1.5倍。
小于1.0:表示将组件缩小到相应的比例。例如,值为0.5,表示缩小到原组件的50%。
如果想通过Java代码实现缩放补间动画,可以创建android.view.animation.ScaleAnimation对象。ScaleAnimation类构造方法的定义如下:
public ScaleAnimation(float fromX, float toX, float fromY, float toY,float pivotX, float pivotY)
通过ScaleAnimation类的构造方法可以设置上述6个属性值。设置其他属性的方法与移动补间动画相同。
总结
本文主要两种补间动画:移动补间动画和缩放补间动画,并给出了相应的实例。在下一篇文章中将会介绍另外两种补间动画。
作者介绍
李宁,东北大学计算机专业硕士,拥有超过10年的软件开发经验。曾任国内某知名企业项目经理;目前担任eoeandroid和ophonesdn版主;中国移动开发者社区OPhone专家;51CTO客作专家;CSDN博客专家。曾领导并参与开发了多个大中型项目。目前主要从事Android及其相关产品的研发。从2005年进入写作领域以来,为《程序员》、《电脑编程技巧与维护》、《电脑报》、IT168、天极网等平面媒体和网络媒体撰写了一百多篇原创技术和评论文章。并在个人blog(http://nokiaguy.blogjava.net)上发表了大量的原创技术文章。2007年获《电脑编程技巧与维护》优秀作者。2009年获得OPhone征文大赛二等奖。个人著作:《Android/OPhone开发完全讲义》、《人人都玩开心网:Ext JS+Android+SSH整合开发Web与移动SNS》、《Java Web开发速学宝典》。
1.0 802.11a标准: 5GHZ;支持的最大速度为54Mbps
2.0 802.11b标准:2.4GHZ;支持的最大速度为11Mbps
3.0 802.11g标准:2.4GHZ;支持的最大速度为54Mbps
4.0 802.11E标准
5.0 802.11i 标准
6.0 wap(无线应用协议)