前言
很久没有上博客园,更是很久没更新过随笔了。这个小小的Android应用还是去年年底开发的,过完年后一直都很忙,也比较懒,今天在整理资料的时候,觉得这个小小的应用算是学习Android开发的一个毕业作业吧,也跟大家分享一下。
本来一直在做.net平台的开发,但是项目的需要要做一个移动终端的版本,但是这个移动终端版本并不是用原生开发的,而是基于PhoneGap框架,
开发人员能够使用熟悉的HTML,CSS 和JavaScript 构建跨平台的移动本地应用,也可以开发原生插件配合使用。关于PhoneGap的具体介绍,大家可以google一下,这里只简短说一下优点和不足。
优点:
1、开发效率高,可以使用传统的web技术(html+css+javascript),开发人员不必学习原生开发语言;
2、成本低,一次开发维护一份代码可以打包发布多个平台,主要是Android和IOS平台;
3、可以开发原生插件,配合一些特殊功能实现,毕竟web可以做的事情还是有限的;
不足:
1、性能上和原生应用还是差一点,在IOS上明显比Android流畅,特别是运行动画和手势操作的时候,但是一般的应用型APP还是足够了;
学习Android开发,纯粹是个人兴趣,在去年年底利用业余时间,自学了一下,并且现在移动应用开发这么热门,多学习一点,了解一下还是好的。对于想入门Android开发的童鞋,我也讲一下我学习的过程,供大家参考一下(仅供参考)。我没有买相关的书籍,因为有一点JAVA的基础,跳过了JAVA语言的学习,直接到http://developer.android.com/samples/index.html
上,按照里面的课程把初级和进阶的学习了,高级部分只挑选了一部分内容看了下,然后就去 一些Android论坛找一下相关的DEMO例子,最多的还是有什么疑问不懂的就直接Google,初学的话基本上你遇到的问题,别人都遇到过,也会有很多的解决方法。
最后就是为什么要开发这样的一个应用,其实就是结合了生活的实际,有个朋友开了个淘宝小店,小卖家一天就10来单,但是每天点发货的时候,手工输入快递单号很麻烦,还要重复核对有没输错,所以就产生了开发一个
扫描宝 应用的想法,既可以满足不想购买市面上的扫描枪,不想手工输入条形码的盆友,又可以把之前学习的知识串联起来,重新巩固一下。
功能描述
首先通过一张顺序图来描述一下该应用的功能:
这就是该应用的主要功能,其实就是传统扫描枪的功能,将条码扫描到电脑上,只是这里利用现在智能手机的便利性和高配置,使用无线局域网,把这该功能简单化了,还省了买扫描枪的钱。现在市面上一般的扫描枪都要几十RMB,我之前在朋友那里用过一款,从淘宝上买的80大洋,有时扫描条码要几秒十几秒,还有的就是扫描不出来,郁闷到只能手工输入。假如各位童鞋身边有这样需求的朋友,也可以推荐使用以下这个APP,不用花钱。
其他的辅助小功能,在光线不好的情况下可以开启闪光灯,在夜深人静的时候还在埋头扫描,可以关闭提示音,当然后面如果我还继续给这个应用维护或新增功能的话,就可以检查更新,继续使用新功能了。
应用截图
开发思路和关键代码
Client:
1、摄像头扫描二维码和条形码
这部分功能使用Google提供的ZXing开源项目,它提供二维码和条形码的扫描,就不需要自己再去详细研究具体实现了,只要知道怎么用就可以了。假如童鞋需要研究透切,可以下载源码:https://github.com/zxing/zxing
,相信对你的开发水平提高不少。这个开源项目内容很多,但是我这里要用的只是扫描二维码和条形码的功能,所以需要做一些精剪的操作,我也是主要参看以下这两篇博文,http://blog.csdn.net/ryantang03/article/details/7831826另一篇http://www.cnblogs.com/dolphin0520/p/3355728.html
闪光灯的开启和关闭是用全局变量isLightVisiable标识,通过菜单选项控制,ZXing的CameraManager类已经公开了setTorch方法。
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
//闪光灯
case ITEM_LIGHT_OFF:
if (this.isLightVisiable) {
cameraManager.setTorch(false);
Toast.makeText(this, "闪光 已关", Toast.LENGTH_LONG).show();
item.setTitle("闪光 开");
} else {
cameraManager.setTorch(true);
Toast.makeText(this, "闪光 已开", Toast.LENGTH_LONG).show();
item.setTitle("闪光 关");
}
this.isLightVisiable = !this.isLightVisiable;
break;
//提示音
case ITEM_VOLUE_ON:
if (this.isBeepVolue) {
Toast.makeText(this, "提示音 已关", Toast.LENGTH_LONG).show();
item.setTitle("提示音 开");
} else {
Toast.makeText(this, "提示音 已开", Toast.LENGTH_LONG).show();
item.setTitle("提示音 关");
}
this.isBeepVolue = !this.isBeepVolue;
break;
//帮助
case ITEM_HELP:
Intent intent = new Intent(this, AboutActivity.class);
startActivity(intent);
break;
//检查更新
case ITEM_CHECK_UPDATE:
UpdateManager updateManager = new UpdateManager(CaptureActivity.this);
updateManager.checkUpdate();
break;
//退出
case ITEM_EXIT:
dialog_Exit(this);
break;
}
return false;
} |
提示音的开启和关闭时用全局变量isBeepVolue标识,通过菜单选项控制,在ZXing的CaptrueActivity类的方法handleDecode做判断。
public void handleDecode(Result rawResult, Bitmap barcode, float scaleFactor) {
inactivityTimer.onActivity();
lastResult = rawResult;
ResultHandler resultHandler = ResultHandlerFactory.makeResultHandler(
this, rawResult);
if (this.isBeepVolue) {
beepManager.playBeepSoundAndVibrate();
}
handleDecodeInternally(rawResult, resultHandler, barcode);
} |
2、扫描二维码与服务端建立连接
这部分内容主要是socket网络编程的知识,具体的连接方式这里就不介绍了,Google里面有很多,也比较简单。这里介绍一下具体流程。首先,在电脑上启动服务端,生成二维码(后面再细讲),用手机扫描二维码并Decode,在handleDecodeInternally方法判断扫描结果是不是正确的服务器地址,再与服务器做TCP连接。
// Put up our own UI for how to handle the decoded contents.
private void handleDecodeInternally(Result rawResult,
ResultHandler resultHandler, Bitmap barcode) {
// statusView1.setText();
String resultStr = resultHandler.getResult().getDisplayResult();
if (!this.isContentServer) {
if (resultStr != null && resultStr.length() > 0) {
if (this.socketThread != null) {
this.socketThread.interrupt();
this.socketThread = null;
}
if (this.socketThread == null) {
String ip = "";
int port = 8099;
String[] results = resultStr.split(":");
if (results.length == 3) {
if (results[0].equals("barcodeServer")) {
if (this.isIPAdress(results[1])) {
ip = results[1];
try {
port = Integer.parseInt(results[2]);
this.socketThread = new SocketThread(
this.mHandler, new ProgressHandle(
this), ip, port);
this.socketThread.start();
} catch (Exception en) {
showErrorDialog("请扫描正确的服务器二维码");
}
} else {
showErrorDialog("请扫描正确的服务器二维码");
}
} else {
showErrorDialog("请扫描正确的服务器二维码.");
}
} else {
showErrorDialog("请扫描正确的服务器二维码");
}
}
} else {
showErrorDialog("连接服务器失败");
}
} else {
if (this.socketThread != null) {
//发送条形码到服务端
this.socketThread.SendMessage(resultStr);
ClipboardInterface.setText(resultStr, this);
Toast.makeText(this, "已经添加到剪切板", Toast.LENGTH_LONG).show();
} else {
showErrorDialog("已断开服务器");
((TextView) findViewById(R.id.status_server)).setText("已断开服务器");
this.isContentServer = false;
}
}
new Thread() {
public void run() {
try {
Thread.sleep(3000);
Message msg = new Message();
msg.obj = 1;
wiatHandler.sendMessage(msg);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start(); |
建立连接后,扫描条形码发送到服务端也是在此方法中进行,其实就是客户端与服务端的相互发送和接收信息,和局域网实时聊天实例原理是一样的。
3、检查更新
在Android和IOS两个平台中有比较大的区别,Android比较乱有很多的应用市场,但是IOS基本上就只有AppStore,所以Android应用的话可以在应用市场上做版本更新控制,也可以在应用内部提供检查更新功能,把版本更新文件放置在自己的服务器上。本应用是使用第二种方式,下面介绍一下实现的过程。
首先将应用安装包APK和版本控制文件XML(主要包括versionCode,appName,appUrl属性),放置到web服务器上,我这里只是简单地放在了博客园的文件管理里。在检查版本更新时先从服务器上把版本控制文件XML获取下来(Android4.2之后不允许在主线程进行HttpConnection的操作,现在网上找的相关资料大部分都比较旧的直接在主线程上下载,4.2以上的需要开启新的线程异步下载,因为这个的提示错误不是很明显,所以说明一下,稍微注意一下就可以了),拿到versionCode与本地安装的应用versionCode比较,服务器的版本号比本地的新时提示下载更新。
public void checkUpdate() { new DownloadWebpageText(this).execute("http://files.cnblogs.com/lijie198871/barcodeClientUpdate.xml"); }
@SuppressWarnings("rawtypes")
private class DownloadWebpageText extends AsyncTask
{
UpdateManager updateManager = null;
public DownloadWebpageText(UpdateManager um){
this.updateManager = um;
}
@Override
protected Object doInBackground(Object... params)
{
// TODO Auto-generated method stub
try {
return downloadUrl(params[0].toString());
} catch (Exception en) {
return en.getMessage();
}
}
@SuppressWarnings("unchecked")
@Override
protected void onPostExecute(Object result) {
// TODO Auto-generated method stub
super.onPostExecute(result);
this.updateManager.getVersionCode(Integer.parseInt(result.toString()));
// ((TextView) findViewById(R.id.networkResult)).setText(result
// .toString());
}
private String downloadUrl(String myurl) throws
IOException {
InputStream is = null;
HttpURLConnection httpConn = null;
try {
URL url = new URL(myurl);
httpConn = (HttpURLConnection) url.openConnection();
httpConn.setReadTimeout(10000);
httpConn.setConnectTimeout(15000);
httpConn.setRequestMethod("GET");
httpConn.setDoInput(true);
httpConn.connect();
int resultCode = httpConn.getResponseCode();
if (resultCode == 200) {
is = httpConn.getInputStream();
PraseXmlService pxmlService = new PraseXmlService();
HashMap<String,String> map = pxmlService.praseXml(is);
return map.get("versionCode");
} else {
return "错误代码:" + resultCode;
}
} finally {
if (is != null) {
is.close();
}
if (httpConn != null) {
httpConn.disconnect();
}
}
}
private String readIt(InputStream is, int len)
throws IOException {
Reader reader = new InputStreamReader(is, "UTF-8");
char[] charBuffer = new char[len];
reader.read(charBuffer);
return new String(charBuffer);
}
}
从服务器上获取版本控制文件,并读取版本号 |
public class PraseXmlService { public HashMap<String,String> praseXml(InputStream inStream) throws Exception { HashMap<String,String> updateMap = new HashMap<String,String>(); DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); Document document = builder.parse(inStream); Element element = document.getDocumentElement(); NodeList nodeList = element.getChildNodes(); int nodeCount = nodeList.getLength(); if(nodeList!=null && nodeCount>0){ for(int i=0;i<nodeCount;i++){ Node node = nodeList.item(i); if(node.getNodeType() == Node.ELEMENT_NODE){ Element childElement = (Element)node; if("versionCode".equals(childElement.getNodeName())){ updateMap.put("versionCode", childElement.getFirstChild().getNodeValue()); }else if("versionName".equals(childElement.getNodeName())){ updateMap.put("versionName", childElement.getFirstChild().getNodeValue()); }else if("url".equals(childElement.getNodeName())){ updateMap.put("url", childElement.getFirstChild().getNodeValue()); } } } } return updateMap; } }
解析XML文件 |
private int getAppVersionCode() { int appVersionCoode = 0; PackageManager pm = this.context.getPackageManager(); try { appVersionCoode = pm.getPackageInfo("com.lijie.client.android", 0).versionCode; } catch (Exception e) { e.printStackTrace(); }
return appVersionCoode;
}
private boolean isUpdate(int newVersionCode)
{
int appVersionCode = this.getAppVersionCode();
if (newVersionCode > appVersionCode) {
return true;
} else {
return false;
}
}
private void getVersionCode(int newVersionCode){
if (this.isUpdate(newVersionCode)) {
this.showNoticeDialog();
} else {
Toast.makeText(this.context, "当前已经是最新版本",
Toast.LENGTH_LONG).show();
}
}
版本号比较 |
Server:使用.net framework2.0
1、生成服务器地址二维码
首先获取服务器电脑IP,并随机生成端口号,拼接成服务器地址,如:192.168.1.123:8023
。同样使用ZXing的.net framew2.0版本将地址生成二维码。
private void CreateBarcode() { serverIp = GetLocalIP();
Random random = new Random();
while (true)
{
int port = random.Next(8088, 20480);
if (!this.isPortUsed(port))
{
serverPort = port.ToString();
break;
}
}
EncodingOptions options = null;
BarcodeWriter writer = null;
options = new QrCodeEncodingOptions
{
DisableECI = true,
CharacterSet = "UTF-8",
Width = this.picBarCode.Width,
Height = this.picBarCode.Height
};
writer = new BarcodeWriter();
writer.Format = BarcodeFormat.QR_CODE;
writer.Options = options;
Bitmap bitmap = writer.Write("barcodeServer:"
+ this.serverIp + ":" + this.serverPort);
this.picBarCode.Image = bitmap;
}
/// <summary>
/// 得到本机IP
/// </summary>
private string GetLocalIP()
{
NetworkInterface[] interfaces = NetworkInterface.GetAllNetworkInterfaces();
foreach (NetworkInterface ni in interfaces)
{
foreach (UnicastIPAddressInformation ip in
ni.GetIPProperties().UnicastAddresses)
{
if (ip.Address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
{
return ip.Address.ToString();
}
}
}
//本机IP地址
string strLocalIP = "";
//得到计算机名
string strPcName = Dns.GetHostName();
//得到本机IP地址数组
IPAddress[] ipAddress = Dns.GetHostAddresses(strPcName);
//遍历数组
foreach (var IPadd in ipAddress)
{
//判断当前字符串是否为正确IP地址
if (IsRightIP(IPadd.ToString()))
{
//得到本地IP地址
strLocalIP = IPadd.ToString();
//结束循环
break;
}
}
//返回本地IP地址
return strLocalIP;
}
/// <summary>
/// 判断是否为正确的IP地址
/// </summary>
/// <param name="strIPadd">需要判断的字符串</param>
/// <returns>true = 是 false = 否</returns>
public static bool IsRightIP(string strIPadd)
{
//利用正则表达式判断字符串是否符合IPv4格式
if (Regex.IsMatch(strIPadd, "[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}"))
{
//根据小数点分拆字符串
string[] ips = strIPadd.Split('.');
if (ips.Length == 4 || ips.Length == 6)
{
//如果符合IPv4规则
if (System.Int32.Parse(ips[0]) < 256 &&
System.Int32.Parse(ips[1]) < 256 & System.Int32.Parse(ips[2])
< 256 & System.Int32.Parse(ips[3]) <
256)
//正确
return true;
//如果不符合
else
//错误
return false;
}
else
//错误
return false;
}
else
//错误
return false;
}
private bool isPortUsed(int port)
{
bool isUsed = false;
IPGlobalProperties ipProperties = IPGlobalProperties.GetIPGlobalProperties();
IPEndPoint[] ipEndPoints = ipProperties.GetActiveTcpListeners();
foreach (IPEndPoint endPoint in ipEndPoints)
{
if (endPoint.Port == port)
{
isUsed = true;
break;
}
}
return isUsed;
} |
2、与客户端建立连接
这部分其实也是socket的编程,具体也不介绍了,然后输出到光标位置,直接使用
SendKeys.Send("内容") 就可以了。
使用方法
(目前版本需要手机和服务端电脑连接在同一个无线局域网 WIFI下)
1、下载服务器端,http://files.cnblogs.com/lijie198871/BarcodeScannerServer.rar
2、下载Android手机客户端到本地电脑再安装到手机,http://files.cnblogs.com/lijie198871/BarcodeClient.apk
也可以直接扫描二维码安装,或者直接在豌豆荚上搜索扫描宝,就可以直接下载安装了。
使用UC浏览器扫描或其他扫描工具。
3、解压BarcodeScannerServer.rar直接运行 扫描宝Server.exe,出现二维码;
4、使用手机运行 扫描宝 应用,扫描服务端的二维码,与服务器成功连接;
5、将电脑光标定位到任意输入框,手机随意找一个条形码或二维码 进行扫描,就可以将内容输入到电脑上了;
后续版本畅想
1、当前版本只局限在WIFI同一个无线局域网下,应该再加上USB的连接方式、蓝牙等等;
2、如果要按一个产品的定位去发展的话,应该要开发IOS版本,可惜苹果的开发设备太贵,买不起啊,有人资助就好了;
3、应该加入互联网的发展方式,支持扫描云同步,现在只是简单的扫描条码到电脑上,应该还有很多适用场景,比如超市的库存清点、商品信息查看和更新云同步;快递员送货信息及时更新,一个扫描枪成本不高,但是每个快递员都带一个扫描枪,成本就非常高了,
并且手机基本上每个人都有,而且是越来越新款的智能机,装上一个扫描软件成本几乎为零,只是服务端的成本;当然现在市面上已经有的扫描比价,扫描翻译等等的功能也可以集成进去;有朝一日把传统的扫描枪淘汰掉(貌似想法有点狂妄,但是按目前互联网的发展态势,很多传统行业都在逐步被淘汰,如
拍照开片机、GPS导航等)
4、盈利模式,还没想到,如果只是靠广告的话利润太低了,而且这样的应用别人很容易仿照复制;
只是这样想想,后续应该不会做太大的更新了,把源码共享出来,靠各位童鞋建设了,或者放到开源社区,大家一起添砖加瓦; |