上一篇,介绍了二维码生成的机制,紧接着,我们就要开发手机客户端来识别这个二维码。

二维码,实际上是记录了这个页面的sessionID,目的是为了最后让服务器能通过long polling的机制去通知到这个浏览器。

创建二维码的时候我们采用了nodejs的QRcode库,其实如果换了其他的web服务器,也可以有其他的可选包,例如zxing。

手机上用的比较多的就是zxing库,不过用过的人都知道,zxing库的核心core只是提供二维码的解析,而应用程序本身对摄像头的操作部分必须参考zxing的应用源码。

那个源码比较的复杂,虽然很好理解,但是代码量太大了。如果要分析那部分源码,文章就要写的长篇大论了,所以这一次,我们不用zxing库,而选择一个更为高效实用的android二维码扫描组件:zbar

ZBar不是纯的java代码,而是用了C编译的native library,因此识别的效率上比zxing要高很多。

闲话少说,先看看程序运行的一系列流程吧:

第一步,登录手机软件,我们做测试用,就只需要输入一个用户名,提交到服务器,返回一个token

为什么要做第一步,因为我们实现手机二维码登录的基础原则就是我们的手机客户端必须的登录的,这样才能作为一个凭据

例如微信,假如你不登录是不能扫描的,所以我们的例子模拟一个登录的过程

第二步,登录成功之后,开始扫描,二维码就显示在屏幕上

第三步,扫描完成后,确认是否登录网页

最后,页面提示登录完成

下面开始,由于long polling的过程我已经做好,因此手机软件才能正常运行,而今天我们只说手机客户端,服务器端的内容下一篇再说,所以,我们先假设所有的接口都OK

手机客户端分为三个Activity,分别为登录,扫描,确认

先做第一个activity

eclipse建立项目,为了符合android4的UI规范,我们采用了sherlock actionbar来实现3.x一下版本android系统的actionbar

因此,项目需要引用actionbar lib,sherlock actionbar的库不能直接引用jar包,必须要下载源码并且以lib的方式引用源码

引用完之后,新建一个class,叫做LoginActivity 继承自SherlockActivity

为了要实现在actionbar上的loading进度圈,需要设置窗体的属性

requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);setContentView(R.layout.login);setSupportProgressBarIndeterminateVisibility(false);

第一个activity界面很简单,就是几个按钮,但是需要有一次和服务器的通信,也就是登录的过程如果登录成功,则显示下一步扫描的按钮,第一个activity很简单

全部代码:

package com.zbiti.qrcodelogin.activity;import java.util.logging.LogRecord;import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;import com.actionbarsherlock.app.SherlockActivity;
import com.actionbarsherlock.view.Window;
import com.zbiti.qrcodelogin.R;
import com.zbiti.qrcodelogin.util.BaseHttpClient;public class LoginActivity extends SherlockActivity {private Context mContext;private TextView txtInfo;private EditText txtUserName;private Button btnLogin;private Button btnStartScan;private Button btnRelogin;private String token = null;private final static String LOGIN_URL = "http://192.168.111.109:8000/moblogin?";private final static int MSG_LOGIN_FAILED = 0;private final static int MSG_LOGIN_OK = 1;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);mContext = LoginActivity.this;requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);setContentView(R.layout.login);setSupportProgressBarIndeterminateVisibility(false);// reference all used viewtxtInfo = (TextView) findViewById(R.id.txt_info);txtUserName = (EditText) findViewById(R.id.edit_username);btnLogin = (Button) findViewById(R.id.btn_client_login);btnStartScan = (Button) findViewById(R.id.btn_startscan);btnRelogin = (Button) findViewById(R.id.btn_relogin);btnRelogin.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {txtInfo.setText(R.string.login_hint);findViewById(R.id.cont_login).setVisibility(View.VISIBLE);findViewById(R.id.cont_loggedin).setVisibility(View.GONE);}});btnLogin.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {setSupportProgressBarIndeterminateVisibility(true);new Thread(new Runnable() {@Overridepublic void run() {getToken();}}).start();}});btnStartScan.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {Intent intent = new Intent();intent.putExtra("token", token);intent.setClass(mContext, MainActivity.class);startActivity(intent);}});}private void getToken() {String userName = txtUserName.getText().toString().trim();if (!userName.equals("")) {try {token = BaseHttpClient.httpGet(LOGIN_URL + userName).trim();} catch (Exception e) {// TODO Auto-generated catch blocke.printStackTrace();}System.out.println(">>>" + token);}if (token == null)return;if (token.equals("")) {handler.sendEmptyMessage(MSG_LOGIN_FAILED);} else {handler.sendEmptyMessage(MSG_LOGIN_OK);}}private Handler handler = new Handler() {@Overridepublic void handleMessage(Message msg) {if (msg.what == MSG_LOGIN_OK) {// 成功获得tokensetSupportProgressBarIndeterminateVisibility(false);txtInfo.setText(getString(R.string.token_info, token));findViewById(R.id.cont_login).setVisibility(View.GONE);findViewById(R.id.cont_loggedin).setVisibility(View.VISIBLE);} else if (msg.what == MSG_LOGIN_FAILED) {setSupportProgressBarIndeterminateVisibility(false);Toast.makeText(mContext, R.string.login_failed,Toast.LENGTH_SHORT).show();}}};
}

布局文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical" ><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:gravity="center_vertical"android:orientation="horizontal" ><TextViewandroid:id="@+id/txt_info"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_margin="20dp"android:drawableLeft="@drawable/chat"android:drawablePadding="10dp"android:text="@string/login_hint" /></LinearLayout><LinearLayoutandroid:id="@+id/cont_loggedin"android:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="vertical"android:visibility="gone" ><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="vertical"android:paddingLeft="20dp"android:paddingRight="20dp"android:paddingTop="10dp" ><Buttonandroid:id="@+id/btn_startscan"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="@string/start_scan" ></Button><Buttonandroid:id="@+id/btn_relogin"android:layout_marginTop="10dp"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="@string/relogin" ></Button></LinearLayout></LinearLayout><LinearLayoutandroid:id="@+id/cont_login"android:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="vertical" ><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="horizontal"android:paddingLeft="20dp"android:paddingRight="20dp"android:paddingTop="10dp" ><TextViewandroid:layout_width="60dp"android:layout_height="wrap_content"android:text="@string/user_name" /><EditTextandroid:id="@+id/edit_username"android:layout_width="match_parent"android:layout_height="wrap_content" /></LinearLayout><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="horizontal"android:paddingLeft="20dp"android:paddingRight="20dp"android:paddingTop="10dp" ><Buttonandroid:id="@+id/btn_client_login"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="@string/login" ></Button></LinearLayout></LinearLayout></LinearLayout>

代码里的 privatefinalstatic StringLOGIN_URL ="http://192.168.111.109:8000/moblogin?";

这一行是我本地测试用的模拟验证的服务器地址,和生成二维码的页面一样,都是Nodejs生成的,代码我们下一篇解释,这个接口接收手机填写的用户名,并且通过sha1进行加密,将加密过后的字符串返回给手机,手机将这个字符串作为token变量并且会传递下去。

下面开始第二个activity,就是扫描界面

首先引用zbar的包,将zbar相关的包拷贝进libs目录

包含的so文件就是c编写的native code

新建类MainActivity继承自SherlockActivity

实现扫描的代码可以从zbar的例子里整,这里不重复

需要把上一个activity传递的token获取,并往下传递

@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.main);getSupportActionBar().setDisplayHomeAsUpEnabled(true);autoFocusHandler = new Handler();preview = (FrameLayout) findViewById(R.id.cameraPreview);// mCamera = getCameraInstance();Intent intent = getIntent();token = intent.getStringExtra("token");if (token == null || token.equals(""))finish();}

在扫描完成的回调里,我们将扫描获得sessionID和token一起往下一个activity传递

PreviewCallback previewCb = new PreviewCallback() {public void onPreviewFrame(byte[] data, Camera camera) {Camera.Parameters parameters = camera.getParameters();Size size = parameters.getPreviewSize();Image barcode = new Image(size.width, size.height, "Y800");barcode.setData(data);int result = scanner.scanImage(barcode);String qrcodeString = null;if (result != 0) {previewing = false;mCamera.setPreviewCallback(null);mCamera.stopPreview();SymbolSet syms = scanner.getResults();for (Symbol sym : syms) {qrcodeString = sym.getData();}}if (qrcodeString != null) {Intent intent = new Intent();intent.setClass(MainActivity.this, ConfirmActivity.class);intent.putExtra("qrcodestring", qrcodeString);intent.putExtra("token", token);startActivity(intent);}}};

在完成扫描的回调里,我们把qrcodestring和token都提交给下一个activity

接着,我们来写第三个activity

仍然创建一个类集成sherlockactivity,类名ConfirmActivity

这个activity在启动的时候,也就意味着,扫描成功了,那么就先通知服务器端,扫描成功,页面也会即时展示出扫描成功,等待手机确认登录的信息

接下来,如果点确认登录,则通知服务器确认登录。

因此我们有2个接口

private final static StringSCANNED_URL ="http://192.168.111.109:8000/scanned?";

privatefinalstatic StringCONFIRMLOGIN_URL ="http://192.168.111.109:8000/confirmed?";

一个是通知服务器已经成功扫描的http接口,一个是通知服务器确认登录的接口。参数都是sessionID,也就是二维码带的信息,和用户token。

package com.zbiti.qrcodelogin.activity;import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;import com.actionbarsherlock.app.SherlockActivity;
import com.actionbarsherlock.view.MenuItem;
import com.zbiti.qrcodelogin.R;
import com.zbiti.qrcodelogin.util.BaseHttpClient;public class ConfirmActivity extends SherlockActivity {private Context mContext;private String sessionID;private String token;private final static String SCANNED_URL = "http://192.168.111.109:8000/scanned?";private final static String CONFIRMLOGIN_URL = "http://192.168.111.109:8000/confirmed?";private final static int LOGIN_SUCCESS=1;private final static int LOGIN_FAIL=0;private Button btnConfirmLogin;private Button btnCancel;private TextView txtInfo;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);mContext = ConfirmActivity.this;setContentView(R.layout.confirm_login);getSupportActionBar().setDisplayHomeAsUpEnabled(true);btnConfirmLogin = (Button) findViewById(R.id.btn_login);btnCancel = (Button) findViewById(R.id.btn_cancel);txtInfo=(TextView)findViewById(R.id.txt_confirm_info);btnConfirmLogin.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {new Thread(new Runnable() {@Overridepublic void run() {notifyConfirmed();}}).start();}});btnCancel.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {finish();}});Intent intent = getIntent();sessionID = intent.getStringExtra("qrcodestring");token = intent.getStringExtra("token");if (sessionID == null || sessionID == null) {finish();}new Thread(new Runnable() {@Overridepublic void run() {notifyScanned();}}).start();}private void notifyConfirmed() {String url = CONFIRMLOGIN_URL + "token=" + token + "&sessionid="+ sessionID;String s = null;try {s = BaseHttpClient.httpGet(url);if(s.equals("confirmed")){handler.sendEmptyMessage(LOGIN_SUCCESS);}else{handler.sendEmptyMessage(LOGIN_FAIL);}} catch (Exception e) {e.printStackTrace();}}private void notifyScanned() {String url = SCANNED_URL + "token=" + token + "&sessionid=" + sessionID;String s = null;try {s = BaseHttpClient.httpGet(url);} catch (Exception e) {e.printStackTrace();}}private Handler handler= new Handler(){@Overridepublic void handleMessage(Message msg) {if(msg.what==LOGIN_FAIL){txtInfo.setText(R.string.pc_login_fail);}else if(msg.what==LOGIN_SUCCESS){btnConfirmLogin.setVisibility(View.GONE);btnCancel.setVisibility(View.GONE);txtInfo.setText(R.string.pc_login_succuess);}}};@Overridepublic boolean onMenuItemSelected(int featureId, MenuItem item) {if (item.getItemId() == android.R.id.home) {finish();}return super.onMenuItemSelected(featureId, item);}
}

布局如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical" ><ImageViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center_horizontal"android:layout_marginBottom="10dp"android:layout_marginLeft="40dp"android:layout_marginRight="40dp"android:layout_marginTop="10dp"android:src="@drawable/pcs" /><TextView android:id="@+id/txt_confirm_info"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center_horizontal"android:text="@string/confirm_login_label"android:textSize="18sp" /><Button android:id="@+id/btn_login"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_marginTop="20dp"android:layout_marginLeft="40dp"android:layout_marginRight="40dp"android:text="@string/btn_confirm_login" /><Button android:id="@+id/btn_cancel"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_marginLeft="40dp"android:layout_marginRight="40dp"android:text="@string/btn_cancel" /></LinearLayout>

这样一个手机客户端就完成了,其中用到的http请求的过程如下:

public static String httpGet(String url) throws Exception{String result = null;HttpClient client = new DefaultHttpClient(); HttpGet get = new HttpGet(url);HttpResponse response = client.execute(get);if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { InputStream is = response.getEntity().getContent();result=inStream2String(is);}return result;}public static String inStream2String(InputStream is) throws Exception {  ByteArrayOutputStream baos = new ByteArrayOutputStream();  byte[] buf = new byte[1024];  int len = -1;  while ((len = is.read(buf)) != -1) {  baos.write(buf, 0, len);  }  return new String(baos.toByteArray());  } 

string如下

<resources><string name="app_name">二维码登录客户端</string><string name="confirm_login_title">已经扫描,请确认登录</string><string name="confirm_login_label">即将在浏览器上登录系统\n请确认是否是本人操作</string><string name="btn_confirm_login">我确认登录系统</string><string name="btn_cancel">取消</string><string name="user_name">用户名</string><string name="user_password">密 码</string><string name="login">登录</string><string name="relogin">重新登录</string><string name="start_scan">开始扫描</string><string name="login_failed">登录失败</string><string name="login_hint">随便输入用户名,登录之后,服务器会返回一个代表你身份的token。</string><string name="token_info">您已经成功登录,token:\n %1$s</string><string name="pc_login_succuess">网页登录成功</string><string name="pc_login_fail">网页登录失败,可能您扫描的页面已过期!</string>
</resources>

原创文章,转载请注明出处

【为了方便测试,我把客户端打包上传,可以点这里下载 http://download.csdn.net/detail/otangba/4857059 】

测试程序在运行时可以设置服务器地址,服务器我们在下一篇会介绍。

实现手机扫描二维码页面登录,类似web微信-第三篇,手机客户端相关推荐

  1. 实现手机扫描二维码页面登录,类似web微信-第一篇,业务分析

    关于XMPP组件的文章,先休息两天,好歹已经完整的写了一份. 这两天,先实现一套关于web微信扫描二维码页面登录的试验,因为这种模式在我们的很多业务场景里大有前途. 首先介绍一下web微信登录的过程 ...

  2. 随机字符串解决大问题之腾讯网如何实现手机扫描二维码登录qq功能的

    随机字符串解决大问题之腾讯网如何实现手机扫描二维码登录qq功能的 腾讯网(www.qq.com)有一个扫码登录功能很有意思, 点击首页一键登录按钮,就会展现一个二维码,用手机qq扫描此二维码就可以使当 ...

  3. 微信扫描二维码实现自动跳转 微信直接下载App(iOS/Android)的解决方案

    微信扫描二维码实现自动跳转 微信直接下载App(iOS/Android)的解决方案 参考文章: (1)微信扫描二维码实现自动跳转 微信直接下载App(iOS/Android)的解决方案 (2)http ...

  4. 微信扫描二维码快速登录网站

    在近期的一个项目中用到了微信扫描注册.登录网站功能所以整理了下希望对读者有帮助. 首先,你需要有一个没有绑定微信.微信公众平台的邮箱注册成为微信开放平台开发者,在管理中心创建移动应用.或者网站应用获得 ...

  5. HTML 5 手机扫描二维码登陆网页

    首先声明我不是专业做前端的,只是一个java开发者,最近要做一个手机版的网站,但是需求要做类似于微信,扫描网页上的二维码登陆网页版微信,以当时认为这东西必须要APP才能支持,因为所有扫描二维码都是必须 ...

  6. Android扫描二维码 实现 登录网页

    工程代码:ScanQRcode.zip ------------------------------------------------------------------ 1. 扫描二维码登录的实现 ...

  7. H5实现手机扫描二维码识别

    主要依赖于二维码解析库jsQR,它是一个纯javascript的二维码阅读库. 这个库接收原始图像,并将定位.提取和解析其中发现的任何QR码. jsQR 被设计成一个完全独立的库,用于扫描二维码.按照 ...

  8. IOS实现扫描二维码,利用系统API实现,看了这篇IOS扫描功能再也不用求人了。

    如今在国内无论走到哪里,只要是涉及支付的方面都可以使用支付宝或微信扫个二维码进行付款.现在二维码随处可见,二维码支付,二维码点餐,二维码坐公交,二维码做地铁,广告宣传页上, 电视台节目上等等.另外连乞 ...

  9. 我的微信扫描二维码实现登录のJava

    微信登录开发参考网址 微信官方文档の公众号 微信公众平台接口测试账号申请 微信公众平台接口在线调试网址 我的微信登录开发流程 配置一些基本数据 /** * 配置好一些需要用到的数据 */ public ...

最新文章

  1. 【视频】视频方面大神博客总结
  2. java开发经验分享_java开发经验分享(一)
  3. global cache cr request
  4. 基于css和jQuery实现轮播图
  5. Registry新建以及版本
  6. Educational Codeforces Round 23 F. MEX Queries(线段树)
  7. Centos7安装32位库用来安装32位软件程序
  8. vue实现导出excel
  9. 平面关系:平行,垂直,夹角判定
  10. Altium Designer——usb信号线布线注意的问题——应使用差分布线
  11. 制度罚则-- 线下Bug规范
  12. cygwin里安装apt-cyg网络工具及使用
  13. Android音视频点/直播模块开发
  14. 电商api、地图api、视频api、音乐api等各类接口合集,或许对你有用
  15. 法国语言学校c1,法国留学的语言要求是什么?
  16. 这些年盘过的航模、车模、船模
  17. 将qlv格式转换为mp4格式视频
  18. Java编程珠玑(202205)
  19. 健康上链——医疗健康行业数字化转型的关键路径|链塔智库
  20. 网络直播电视之寻找直播地址

热门文章

  1. 元宇宙007 | 沉浸式家庭治疗,让治疗像演情景剧一样!
  2. 字符串相似度比较工具
  3. 精灵图使用——如何利用js循环实现精灵图设置
  4. js循环打印出0~9
  5. 写代码实现堆溢出、栈溢出、永久代溢出、直接内存溢出
  6. 2012情人节语录1
  7. 经纬高坐标系转到东北天坐标系
  8. 对待小动物需要多一份爱心
  9. 揭秘华南地区首个高校电竞专业:不教打游戏 培养全能人才
  10. Gremlin 基础知识