第一個Android應用 掃描寶 欲挑戰傳統掃描槍

前言javascript

      好久沒有上博客園,更是好久沒更新過隨筆了。這個小小的Android應用仍是去年年末開發的,過完年後一直都很忙,也比較懶,今天在整理資料的時候,以爲這個小小的應用算是學習Android開發的一個畢業做業吧,也跟你們分享一下。css

      原本一直在作.net平臺的開發,可是項目的須要要作一個移動終端的版本,可是這個移動終端版本並非用原生開發的,而是基於PhoneGap框架, 開發人員可以使用熟悉的HTML,CSS 和JavaScript 構建跨平臺的移動本地應用,也能夠開發原生插件配合使用。關於PhoneGap的具體介紹,你們能夠google一下,這裏只簡短說一下優勢和不足。html

優勢:java

一、開發效率高,可使用傳統的web技術(html+css+javascript),開發人員沒必要學習原生開發語言;node

二、成本低,一次開發維護一份代碼能夠打包發佈多個平臺,主要是Android和IOS平臺;android

三、能夠開發原生插件,配合一些特殊功能實現,畢竟web能夠作的事情仍是有限的;git

不足:github

一、性能上和原生應用仍是差一點,在IOS上明顯比Android流暢,特別是運行動畫和手勢操做的時候,可是通常的應用型APP仍是足夠了;web

 

     學習Android開發,純粹是我的興趣,在去年年末利用業餘時間,自學了一下,而且如今移動應用開發這麼熱門,多學習一點,瞭解一下仍是好的。對於想入門Android開發的童鞋,我也講一下我學習的過程,供你們參考一下(僅供參考)。我沒有買相關的書籍,由於有一點JAVA的基礎,跳過了JAVA語言的學習,直接到http://developer.android.com/samples/index.html 上,按照裏面的課程把初級和進階的學習了,高級部分只挑選了一部份內容看了下,而後就去 一些Android論壇找一下相關的DEMO例子,最多的仍是有什麼疑問不懂的就直接Google,初學的話基本上你遇到的問題,別人都遇到過,也會有不少的解決方法。正則表達式

      最後就是爲何要開發這樣的一個應用,其實就是結合了生活的實際,有個朋友開了個淘寶小店,小賣家一天就10來單,可是天天點發貨的時候,手工輸入快遞單號很麻煩,還要重複覈對有沒輸錯,因此就產生了開發一個 掃描寶 應用的想法,既能夠知足不想購買市面上的掃描槍,不想手工輸入條形碼的盆友,又能夠把以前學習的知識串聯起來,從新鞏固一下。

 

功能描述

  首先經過一張順序圖來描述一下該應用的功能:

  這就是該應用的主要功能,其實就是傳統掃描槍的功能,將條碼掃描到電腦上,只是這裏利用如今智能手機的便利性和高配置,使用無線局域網,把這該功能簡單化了,還省了買掃描槍的錢。如今市面上通常的掃描槍都要幾十RMB,我以前在朋友那裏用過一款,從淘寶上買的80大洋,有時掃描條碼要幾秒十幾秒,還有的就是掃描不出來,鬱悶到只能手工輸入。假如各位童鞋身邊有這樣需求的朋友,也能夠推薦使用如下這個APP,不用花錢。

  其餘的輔助小功能,在光線很差的狀況下能夠開啓閃光燈,在夜深人靜的時候還在埋頭掃描,能夠關閉提示音,固然後面若是我還繼續給這個應用維護或新增功能的話,就能夠檢查更新,繼續使用新功能了。

 

應用截圖

開發思路和關鍵代碼

  Client:

  一、攝像頭掃描二維碼和條形碼

  這部分功能使用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方法。

 1     @Override
 2     public boolean onOptionsItemSelected(MenuItem item) {
 3 
 4         switch (item.getItemId()) {
 5         //閃光燈
 6         case ITEM_LIGHT_OFF:
 7             if (this.isLightVisiable) {
 8                 cameraManager.setTorch(false);
 9                 Toast.makeText(this, "閃光  已關", Toast.LENGTH_LONG).show();
10                 item.setTitle("閃光  開");
11             } else {
12                 cameraManager.setTorch(true);
13                 Toast.makeText(this, "閃光  已開", Toast.LENGTH_LONG).show();
14                 item.setTitle("閃光  關");
15             }
16             this.isLightVisiable = !this.isLightVisiable;
17             break;
18         //提示音
19         case ITEM_VOLUE_ON:
20             if (this.isBeepVolue) {
21                 Toast.makeText(this, "提示音   已關", Toast.LENGTH_LONG).show();
22                 item.setTitle("提示音 開");
23             } else {
24                 Toast.makeText(this, "提示音   已開", Toast.LENGTH_LONG).show();
25                 item.setTitle("提示音 關");
26             }
27             this.isBeepVolue = !this.isBeepVolue;
28             break;
29         //幫助
30         case ITEM_HELP:
31             Intent intent = new Intent(this, AboutActivity.class);
32             startActivity(intent);
33             break;
34         //檢查更新
35         case ITEM_CHECK_UPDATE:
36             UpdateManager updateManager = new UpdateManager(CaptureActivity.this);
37             updateManager.checkUpdate();
38             break;
39         //退出
40         case ITEM_EXIT:
41             dialog_Exit(this);
42             break;
43         }
44 
45         return false;
46     }

    提示音的開啓和關閉時用全局變量isBeepVolue標識,經過菜單選項控制,在ZXing的CaptrueActivity類的方法handleDecode作判斷。

 1 public void handleDecode(Result rawResult, Bitmap barcode, float scaleFactor) {
 2         inactivityTimer.onActivity();
 3         lastResult = rawResult;
 4         ResultHandler resultHandler = ResultHandlerFactory.makeResultHandler(
 5                 this, rawResult);
 6 
 7         if (this.isBeepVolue) {
 8             beepManager.playBeepSoundAndVibrate();
 9         }
10         handleDecodeInternally(rawResult, resultHandler, barcode);
11     }

 

  二、掃描二維碼與服務端創建鏈接

  這部份內容主要是socket網絡編程的知識,具體的鏈接方式這裏就不介紹了,Google裏面有不少,也比較簡單。這裏介紹一下具體流程。首先,在電腦上啓動服務端,生成二維碼(後面再細講),用手機掃描二維碼並Decode,在handleDecodeInternally方法判斷掃描結果是否是正確的服務器地址,再與服務器作TCP鏈接。

 1 // Put up our own UI for how to handle the decoded contents.
 2     private void handleDecodeInternally(Result rawResult,
 3             ResultHandler resultHandler, Bitmap barcode) {
 4         // statusView1.setText();
 5         String resultStr = resultHandler.getResult().getDisplayResult();
 6         if (!this.isContentServer) {
 7             if (resultStr != null && resultStr.length() > 0) {
 8                 if (this.socketThread != null) {
 9                     this.socketThread.interrupt();
10                     this.socketThread = null;
11                 }
12                 if (this.socketThread == null) {
13                     String ip = "";
14                     int port = 8099;
15 
16                     String[] results = resultStr.split(":");
17                     if (results.length == 3) {
18                         if (results[0].equals("barcodeServer")) {
19                             if (this.isIPAdress(results[1])) {
20                                 ip = results[1];
21                                 try {
22                                     port = Integer.parseInt(results[2]);
23                                     this.socketThread = new SocketThread(
24                                             this.mHandler, new ProgressHandle(
25                                                     this), ip, port);
26                                     this.socketThread.start();
27                                 } catch (Exception en) {
28                                     showErrorDialog("請掃描正確的服務器二維碼");
29                                 }
30 
31                             } else {
32                                 showErrorDialog("請掃描正確的服務器二維碼");
33                             }
34                         } else {
35                             showErrorDialog("請掃描正確的服務器二維碼.");
36                         }
37                     } else {
38                         showErrorDialog("請掃描正確的服務器二維碼");
39                     }
40                 }
41             } else {
42                 showErrorDialog("鏈接服務器失敗");
43             }
44         } else {
45             if (this.socketThread != null) {
            //發送條形碼到服務端
46 this.socketThread.SendMessage(resultStr); 47 ClipboardInterface.setText(resultStr, this); 48 Toast.makeText(this, "已經添加到剪切板", Toast.LENGTH_LONG).show(); 49 } else { 50 showErrorDialog("已斷開服務器"); 51 ((TextView) findViewById(R.id.status_server)).setText("已斷開服務器"); 52 this.isContentServer = false; 53 } 54 } 55 56 new Thread() { 57 public void run() { 58 try { 59 Thread.sleep(3000); 60 Message msg = new Message(); 61 msg.obj = 1; 62 wiatHandler.sendMessage(msg); 63 } catch (InterruptedException e) { 64 e.printStackTrace(); 65 } 66 67 } 68 }.start(); 69

   創建鏈接後,掃描條形碼發送到服務端也是在此方法中進行,其實就是客戶端與服務端的相互發送和接收信息,和局域網實時聊天實例原理是同樣的。

  三、檢查更新

  在Android和IOS兩個平臺中有比較大的區別,Android比較亂有不少的應用市場,可是IOS基本上就只有AppStore,因此Android應用的話能夠在應用市場上作版本更新控制,也能夠在應用內部提供檢查更新功能,把版本更新文件放置在本身的服務器上。本應用是使用第二種方式,下面介紹一下實現的過程。

  首先將應用安裝包APK和版本控制文件XML(主要包括versionCode,appName,appUrl屬性),放置到web服務器上,我這裏只是簡單地放在了博客園的文件管理裏。在檢查版本更新時先從服務器上把版本控制文件XML獲取下來(Android4.2以後不容許在主線程進行HttpConnection的操做,如今網上找的相關資料大部分都比較舊的直接在主線程上下載,4.2以上的須要開啓新的線程異步下載,由於這個的提示錯誤不是很明顯,因此說明一下,稍微注意一下就能夠了),拿到versionCode與本地安裝的應用versionCode比較,服務器的版本號比本地的新時提示下載更新。

 1     public void checkUpdate() {
 2         new DownloadWebpageText(this).execute("http://files.cnblogs.com/lijie198871/barcodeClientUpdate.xml");
 3     }
 4 
 5 @SuppressWarnings("rawtypes")
 6     private class DownloadWebpageText extends AsyncTask {
 7         UpdateManager  updateManager = null;
 8         
 9         public DownloadWebpageText(UpdateManager um){
10             this.updateManager = um;
11         }
12         
13         @Override
14         protected Object doInBackground(Object... params) {
15             // TODO Auto-generated method stub
16             try {
17                 return downloadUrl(params[0].toString());
18             } catch (Exception en) {
19                 return en.getMessage();
20             }
21         }
22 
23         @SuppressWarnings("unchecked")
24         @Override
25         protected void onPostExecute(Object result) {
26             // TODO Auto-generated method stub
27             super.onPostExecute(result);
28 
29             this.updateManager.getVersionCode(Integer.parseInt(result.toString()));
30 //            ((TextView) findViewById(R.id.networkResult)).setText(result
31 //                    .toString());
32         }
33 
34         private String downloadUrl(String myurl) throws IOException {
35             InputStream is = null;
36             HttpURLConnection httpConn = null;
37             
38             try {
39                 URL url = new URL(myurl);
40                 httpConn = (HttpURLConnection) url.openConnection();
41                 httpConn.setReadTimeout(10000);
42                 httpConn.setConnectTimeout(15000);
43                 httpConn.setRequestMethod("GET");
44                 httpConn.setDoInput(true);
45 
46                 httpConn.connect();
47                 int resultCode = httpConn.getResponseCode();
48                 if (resultCode == 200) {
49                     is = httpConn.getInputStream();
50 
51                     PraseXmlService pxmlService = new PraseXmlService();
52                     HashMap<String,String> map = pxmlService.praseXml(is);
53                     return map.get("versionCode");
54                 } else {
55                     return "錯誤代碼:" + resultCode;
56                 }
57 
58             } finally {
59                 if (is != null) {
60                     is.close();
61                 }
62                 if (httpConn != null) {
63                     httpConn.disconnect();
64                 }
65             }
66         }
67         
68         private String readIt(InputStream is, int len) throws IOException {
69             Reader reader = new InputStreamReader(is, "UTF-8");
70             char[] charBuffer = new char[len];
71             reader.read(charBuffer);
72 
73             return new String(charBuffer);
74         }
75     }
從服務器上獲取版本控制文件,並讀取版本號
 1 public class PraseXmlService {
 2     public HashMap<String,String> praseXml(InputStream inStream) throws Exception
 3     {
 4         HashMap<String,String> updateMap = new HashMap<String,String>();
 5         
 6         DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
 7         DocumentBuilder builder = factory.newDocumentBuilder();
 8         Document document = builder.parse(inStream);
 9         Element element = document.getDocumentElement();
10         
11         NodeList nodeList = element.getChildNodes();
12         int nodeCount = nodeList.getLength();
13         if(nodeList!=null && nodeCount>0){
14             for(int i=0;i<nodeCount;i++){
15                 Node node = nodeList.item(i);
16                 if(node.getNodeType() == Node.ELEMENT_NODE){
17                     Element childElement = (Element)node;
18                     if("versionCode".equals(childElement.getNodeName())){
19                         updateMap.put("versionCode", childElement.getFirstChild().getNodeValue());
20                     }else if("versionName".equals(childElement.getNodeName())){
21                         updateMap.put("versionName", childElement.getFirstChild().getNodeValue());
22                     }else if("url".equals(childElement.getNodeName())){
23                         updateMap.put("url", childElement.getFirstChild().getNodeValue());
24                     }
25                 }
26             }
27         }
28         return updateMap;
29     }
30 }
解析XML文件
 1 private int getAppVersionCode() {
 2         int appVersionCoode = 0;
 3         PackageManager pm = this.context.getPackageManager();
 4         try {
 5             appVersionCoode = pm.getPackageInfo("com.lijie.client.android", 0).versionCode;
 6         } catch (Exception e) {
 7             e.printStackTrace();
 8         }
 9 
10         return appVersionCoode;
11     }
12 
13     private boolean isUpdate(int newVersionCode) {
14         int appVersionCode = this.getAppVersionCode();
15         
16         if (newVersionCode > appVersionCode) {
17             return true;
18         } else {
19             return false;
20         }
21         
22     }
23 
24     private void getVersionCode(int newVersionCode){
25         if (this.isUpdate(newVersionCode)) {
26             this.showNoticeDialog();
27         } else {
28             Toast.makeText(this.context, "當前已是最新版本", Toast.LENGTH_LONG).show();
29         }
30     }
版本號比較

 

  Server:使用.net framework2.0

  一、生成服務器地址二維碼

  首先獲取服務器電腦IP,並隨機生成端口號,拼接成服務器地址,如:192.168.1.123:8023 。一樣使用ZXing的.net framew2.0版本將地址生成二維碼。

  1 private void CreateBarcode()
  2         {
  3             serverIp = GetLocalIP();
  4 
  5             Random random = new Random();
  6             while (true)
  7             {
  8                 int port = random.Next(8088, 20480);
  9                 if (!this.isPortUsed(port))
 10                 {
 11                     serverPort = port.ToString();
 12                     break;
 13                 }
 14             }
 15 
 16             EncodingOptions options = null;
 17             BarcodeWriter writer = null;
 18             options = new QrCodeEncodingOptions
 19             {
 20                 DisableECI = true,
 21                 CharacterSet = "UTF-8",
 22                 Width = this.picBarCode.Width,
 23                 Height = this.picBarCode.Height
 24             };
 25             writer = new BarcodeWriter();
 26             writer.Format = BarcodeFormat.QR_CODE;
 27             writer.Options = options;
 28             Bitmap bitmap = writer.Write("barcodeServer:" + this.serverIp + ":" + this.serverPort);
 29             this.picBarCode.Image = bitmap;
 30         }
 31 
 32 
 33 /// <summary>
 34         /// 獲得本機IP
 35         /// </summary>
 36         private string GetLocalIP()
 37         {
 38             NetworkInterface[] interfaces = NetworkInterface.GetAllNetworkInterfaces();
 39             foreach (NetworkInterface ni in interfaces)
 40             {
 41                 foreach (UnicastIPAddressInformation ip in
 42                     ni.GetIPProperties().UnicastAddresses)
 43                 {
 44                     if (ip.Address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
 45                     {
 46                         return ip.Address.ToString();
 47                     }
 48                 }
 49             }
 50 
 51             //本機IP地址
 52             string strLocalIP = "";
 53             //獲得計算機名
 54             string strPcName = Dns.GetHostName();
 55             //獲得本機IP地址數組
 56             IPAddress[] ipAddress = Dns.GetHostAddresses(strPcName);
 57             //遍歷數組
 58             foreach (var IPadd in ipAddress)
 59             {
 60                 //判斷當前字符串是否爲正確IP地址
 61                 if (IsRightIP(IPadd.ToString()))
 62                 {
 63                     //獲得本地IP地址
 64                     strLocalIP = IPadd.ToString();
 65                     //結束循環
 66                     break;
 67                 }
 68             }
 69 
 70             //返回本地IP地址
 71             return strLocalIP;
 72         }
 73 
 74         /// <summary>
 75         /// 判斷是否爲正確的IP地址
 76         /// </summary>
 77         /// <param name="strIPadd">須要判斷的字符串</param>
 78         /// <returns>true = 是 false = 否</returns>
 79         public static bool IsRightIP(string strIPadd)
 80         {
 81             //利用正則表達式判斷字符串是否符合IPv4格式
 82             if (Regex.IsMatch(strIPadd, "[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}"))
 83             {
 84                 //根據小數點分拆字符串
 85                 string[] ips = strIPadd.Split('.');
 86                 if (ips.Length == 4 || ips.Length == 6)
 87                 {
 88                     //若是符合IPv4規則
 89                     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)
 90                         //正確
 91                         return true;
 92                     //若是不符合
 93                     else
 94                         //錯誤
 95                         return false;
 96                 }
 97                 else
 98                     //錯誤
 99                     return false;
100             }
101             else
102                 //錯誤
103                 return false;
104         }
105 
106         private bool isPortUsed(int port)
107         {
108             bool isUsed = false;
109             IPGlobalProperties ipProperties = IPGlobalProperties.GetIPGlobalProperties();
110             IPEndPoint[] ipEndPoints = ipProperties.GetActiveTcpListeners();
111 
112             foreach (IPEndPoint endPoint in ipEndPoints)
113             {
114                 if (endPoint.Port == port)
115                 {
116                     isUsed = true;
117                     break;
118                 }
119             }
120             return isUsed;
121         }
View Code

   二、與客戶端創建鏈接

  這部分其實也是socket的編程,具體也不介紹了,而後輸出到光標位置,直接使用 SendKeys.Send("內容") 就能夠了。

 

使用方法目前版本須要手機和服務端電腦鏈接在同一個無線局域網 WIFI下)

  一、下載服務器端,http://files.cnblogs.com/lijie198871/BarcodeScannerServer.rar

  二、下載Android手機客戶端到本地電腦再安裝到手機,http://files.cnblogs.com/lijie198871/BarcodeClient.apk

    也能夠直接掃描二維碼安裝,或者直接在 豌豆莢 上搜索 掃描寶,就能夠直接下載安裝了。

  使用UC瀏覽器掃描或其餘掃描工具。

  三、解壓BarcodeScannerServer.rar直接運行 掃描寶Server.exe,出現二維碼;

  四、使用手機運行 掃描寶 應用,掃描服務端的二維碼,與服務器成功鏈接;

  五、將電腦光標定位到任意輸入框,手機隨意找一個條形碼或二維碼 進行掃描,就能夠將內容輸入到電腦上了;

 

 後續版本暢想

  一、當前版本只侷限在WIFI同一個無線局域網下,應該再加上USB的鏈接方式、藍牙等等;

  二、若是要按一個產品的定位去發展的話,應該要開發IOS版本,惋惜蘋果的開發設備太貴,買不起啊,有人資助就行了;

  三、應該加入互聯網的發展方式,支持掃描雲同步,如今只是簡單的掃描條碼到電腦上,應該還有不少適用場景,好比超市的庫存清點、商品信息查看和更新雲同步;快遞員送貨信息及時更新,一個掃描槍成本不高,可是每一個快遞員都帶一個掃描槍,成本就很是高了, 而且手機基本上每一個人都有,並且是愈來愈新款的智能機,裝上一個掃描軟件成本幾乎爲零,只是服務端的成本;固然如今市面上已經有的掃描比價,掃描翻譯等等的功能也能夠集成進去;有朝一日把傳統的掃描槍淘汰掉(貌似想法有點狂妄,可是按目前互聯網的發展態勢,不少傳統行業都在逐步被淘汰,如 拍照開片機、GPS導航等)

  四、盈利模式,還沒想到,若是隻是靠廣告的話利潤過低了,並且這樣的應用別人很容易仿照複製;

  只是這樣想一想,後續應該不會作太大的更新了,把源碼共享出來,靠各位童鞋建設了,或者放到開源社區,你們一塊兒添磚加瓦;

 

源碼下載

  服務端源碼:下載

  客戶端源碼:下載

 

  親,記得點贊「推薦」哦!

 

  本文版權歸做者和博客園共有,歡迎轉載,但未經做者贊成必須保留此段聲明,且在文章頁面明顯位置給出原文鏈接。若有問題,能夠郵件:373008218(at)qq(dot)com  聯繫我,很是感謝。

相關文章
相關標籤/搜索