最新下载
热门教程
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
Android客户端实现:二维码登录轮询机制全流程解析
时间:2026-05-30 17:30:02 编辑:袖梨 来源:一聚教程网
二维码登录已成为现代应用的主流认证方式,其核心在于如何高效判断用户扫码状态。本文将深入解析Android平台实现二维码登录的完整轮询机制与技术细节。

实现二维码登录状态检测的关键在于轮询机制设计。系统通过count < MaxCount条件控制最大轮询次数为15次(约45秒),超时自动终止流程。
需特别注意生命周期管理:当前IntervalCheckToken作为单例持有Disposable对象,当用户通过返回键退出登录页面时,轮询操作不会自动停止。
用户进入登录页
↓
生成二维码业务URL
↓
Zxing 生成二维码Bitmap → 缩放/裁剪处理 → 页面展示二维码
↓
开启 RxJava 定时轮询(3秒间隔、限制最大轮询次数)
↓
客户端携带 deviceId + cacheKey 轮询请求后端接口
↓
后端返回三种状态: 1. 未扫码/未确认 → 继续轮询
2. 已扫码确认登录 → 停止轮询、本地保存Token、发送登录事件跳转主页
3. 二维码过期 → 停止轮询/刷新新二维码 4. 账号已下线 → 清空登录状态、退出登录
↓
接收EventBus登录事件 → 页面刷新UI、跳转主界面
1、展示二维码
private void showQrCode(String url){
TaskManager.runInConcurrentTaskManger(LoginActivity.this, url, new TaskManager.TaskRunnable<String>() {
Bitmap bitmap; @Override
public void success(String data) {
if (bitmap != null) {
mBinding.deviceCode.setDeviceCode(bitmap);
IntervalCheckToken.getInstance().checkTokenInterval(15); //开启轮询
}
} @Override
public void run(String data) throws AbsException { //将二维码url转为bitmap
bitmap = BitmapUtils.encodeAsBitmap(url, getResources().getDimensionPixelOffset(R.dimen.x220), getResources().getDimensionPixelOffset(R.dimen.x220));
} @Override
public void fail(String data, AbsException exception) { }
});
}
bitmap工具类
public class BitmapUtils { public static void recycle(Bitmap bitmap) {
if (isCanRecycled(bitmap)) {
bitmap.recycle();
}
} public static boolean isCanRecycled(Bitmap bitmap) {
return bitmap != null && !bitmap.isRecycled();
} public static Bitmap cropBitmap(Bitmap target, int width, int height, @BitmapCutEnum int type) {
Bitmap bitmap = scaleBitmap(target, width, height);
switch (type) {
case BitmapCutEnum.CENTER:
return centerCrop(bitmap, width, height);
default:
return target;
}
} public static Bitmap scaleBitmap(Bitmap target, int width, int height) {
int targetWidth = target.getWidth();
int targetHeight = target.getHeight();
float scaleWidth = width * 1f / targetWidth;
float scaleHeight = height * 1f / targetHeight;
float scale = Math.max(scaleWidth, scaleHeight);
Matrix matrix = new Matrix();
matrix.postScale(scale, scale);
return Bitmap.createBitmap(target, 0, 0, targetWidth, targetHeight, matrix, true);
} private static Bitmap centerCrop(Bitmap target, int width, int height) {
Bitmap scaleBitmap = scaleBitmap(target, width, height);
Bitmap destBitmap = scaleBitmap;
int scaleWidth = scaleBitmap.getWidth();
int scaleHeight = scaleBitmap.getHeight();
if (scaleWidth > width) {
destBitmap = Bitmap.createBitmap(scaleBitmap, (scaleWidth - width) / 2, 0, width, height);
} else if (scaleHeight > height) {
destBitmap = Bitmap.createBitmap(scaleBitmap, 0, (scaleHeight - height) / 2, width, height);
}
return destBitmap;
} public static Bitmap getBitmapHttp(String http) {
try {
URL url = new URL(http);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setDoInput(true);
connection.connect();
InputStream is = connection.getInputStream();
Bitmap bitmap = BitmapFactory.decodeStream(is);
is.close();
return bitmap;
} catch (Exception exception) { } return null;
} /**
* 创建黑白二维码
*
* @param contents
* @return
* @throws WriterException
*/
public static Bitmap encodeAsBitmap(String contents, int width, int height) {
return encodeAsBitmap(contents, width, height, 0, ErrorCorrectionLevel.Q);
} /**
* 创建黑白二维码
*
* @param contents
* @return
* @throws WriterException
*/
public static Bitmap encodeAsBitmap(String contents, int width, int height, int margin, ErrorCorrectionLevel level) {
MultiFormatWriter barcodeWriter = new MultiFormatWriter();
//com.google.zxing.EncodeHintType:编码提示类型,枚举类型
Map hints = new HashMap();
//EncodeHintType.MARGIN:设置二维码边距,单位像素,值越小,二维码距离四周越近
hints.put(EncodeHintType.MARGIN, margin);
/*设置字符编码类型*/
hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
/*设置误差校正*/
hints.put(EncodeHintType.ERROR_CORRECTION, level);
BitMatrix matrix = null;
try {
matrix = barcodeWriter.encode(contents, BarcodeFormat.QR_CODE, width, height, hints);
} catch (WriterException e) {
throw new RuntimeException(e);
}
int[] pixels = new int[width * height];
for (int y = 0; y < height; y++) {
int offset = y * width;
for (int x = 0; x < width; x++) {
pixels[offset + x] = matrix.get(x, y) ? BLACK : WHITE;
}
}
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
bitmap.setPixels(pixels, 0, width, 0, 0, width, height);
return bitmap;
} public static Bitmap base64ToBitmap(String base64Data) {
byte[] bytes = Base64.decode(base64Data, Base64.DEFAULT);
return BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
}
}
2、开启轮询
public class IntervalCheckToken {
private static final String TAG = IntervalCheckToken.class.getSimpleName();
private static volatile IntervalCheckToken instance = null;
private Disposable disposable; public static IntervalCheckToken getInstance() {
IntervalCheckToken result = instance;
if (result == null) {
synchronized (IntervalCheckToken.class) {
result = instance;
if (result == null) {
instance = result = new IntervalCheckToken();
}
}
}
return result;
} //获取token需要的参数 要先保存起来
public void checkTokenInterval(int MaxCount) {
String cacheKey = PreferenceUtil.fetch(PreferenceKey.PREFERENCE_KEY_CACHEKEY,"");
String deviceId = PreferenceUtil.fetch(PreferenceKey.PREFERENCE_KEY_DEVICE_ID,"");
if(TextUtils.isEmpty(cacheKey) || TextUtils.isEmpty(deviceId)){
DLog.i(TAG, "日志输出------cacheKey或者deviceId未空");
return;
}
dispose();
Observable.interval(DelayConstant.DELAY_500, DelayConstant.DELAY_3000, TimeUnit.MILLISECONDS).doOnNext(count -> {
DLog.i(TAG, "日志输出------轮询第" + count + "次轮询");
try {
if(count < MaxCount){
getToken();
}else {
dispose();
DLog.i(TAG, "日志输出-----"+MaxCount+"次轮询结束");
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}).subscribeOn(Schedulers.io()).subscribe(new Observer<Long>() {
@Override
public void onSubscribe(Disposable mDisposable) {
IntervalCheckToken.this.disposable = mDisposable;
DLog.i(TAG, "日志输出------轮询 onSubscribe Disposable");
} @Override
public void onNext(Long aLong) {
} @Override
public void onError(Throwable e) {
DLog.i(TAG, "日志输出------轮询出错" + e.toString());
} @Override
public void onComplete() {
}
});
} private void getToken() {
Map<String, Object> map = new HashMap<>();
map.put("deviceId", PreferenceUtil.fetch(PreferenceKey.PREFERENCE_KEY_DEVICE_ID, ""));
map.put("cacheKey", AESUtil.decrypt(PreferenceUtil.fetch(PreferenceKey.PREFERENCE_KEY_CACHEKEY, "")));
Launcher.rx(RetrofitUtil.retrofitMicroservices.create(Api.class).pollingToken(map), new Launcher.Receiver<Model<LoginToken>>() {
@Override
public void onSuccess(Model model ) {
if (model.isSuccess()) {
LoginToken loginToken = model.getData();
boolean isLogin = PreferenceUtil.fetch(PreferenceKey.PREFERENCE_KEY_IS_LOGIN, false);
if(!isLogin){ //未登录
if (loginToken.getLoginStatus()==1) { //1登录 0未登录
dispose(); //关闭轮询
PreferenceUtil.put(PreferenceKey.PREFERENCE_KEY_TOKEN, AESUtil.encrypt(loginToken.getToken()));
PreferenceUtil.put(PreferenceKey.PREFERENCE_KEY_USER_ID, AESUtil.encrypt(loginToken.getUser().getId()));
PreferenceUtil.put(PreferenceKey.PREFERENCE_KEY_IS_LOGIN, true); EventBus.getDefault().post(new LoginEvent()); //发送登录完成 消息
}
}else {
if (loginToken.getLoginStatus()==0) {//退出登录 清空缓存信息
dispose();
LoginUtil.loginOut();
}
} }else if(model.getCode()==402){ //二维码已过期
// dispose();
}
} @Override
public void onFail() { }
});
} private void dispose() {
if (disposable != null && !disposable.isDisposed()) {
disposable.dispose();
disposable = null;
}
}}
通过上述完整实现方案,开发者可快速构建安全可靠的二维码登录功能,该方案具备完善的超时控制、状态管理机制,能有效处理各类异常场景。
相关文章
- PHP attributes()函数讲解 05-30
- ThinkPHP5实现JWT Token认证的过程(亲测可用) 05-30
- 字节跳动发布Dolphin-v2多模态文档解析模型 05-30
- Kafka消息传输如何保障安全性及其加密方式解析 05-30
- win7能否玩我的世界基岩版详细介绍 05-30
- 空洞骑士丝之歌DLC预告或于五月亮相 05-30