求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Modeler   Code  
会员   
 
  
 
 
     
   
分享到
第一个Android应用 扫描宝 欲挑战传统扫描枪
 
火龙果软件    发布于 2014-04-21
 

前言

很久没有上博客园,更是很久没更新过随笔了。这个小小的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、盈利模式,还没想到,如果只是靠广告的话利润太低了,而且这样的应用别人很容易仿照复制;

只是这样想想,后续应该不会做太大的更新了,把源码共享出来,靠各位童鞋建设了,或者放到开源社区,大家一起添砖加瓦;

 
相关文章

手机软件测试用例设计实践
手机客户端UI测试分析
iPhone消息推送机制实现与探讨
Android手机开发(一)
 
相关文档

Android_UI官方设计教程
手机开发平台介绍
android拍照及上传功能
Android讲义智能手机开发
相关课程

Android高级移动应用程序
Android系统开发
Android应用开发
手机软件测试
 
分享到
 
 


android人机界面指南
Android手机开发(一)
Android手机开发(二)
Android手机开发(三)
Android手机开发(四)
iPhone消息推送机制实现探讨
手机软件测试用例设计实践
手机客户端UI测试分析
手机软件自动化测试研究报告
更多...   


Android高级移动应用程序
Android应用开发
Android系统开发
手机软件测试
嵌入式软件测试
Android软、硬、云整合


领先IT公司 android开发平台最佳实践
北京 Android开发技术进阶
某新能源领域企业 Android开发技术
某航天公司 Android、IOS应用软件开发
阿尔卡特 Linux内核驱动
艾默生 嵌入式软件架构设计
西门子 嵌入式架构设计
更多...