现在很多登录功能都使用二维码登录,那么我怎么知道用户已经扫码登录了呢?
可以使用轮询机制,
轮询的退出条件 :代码里通过 count < MaxCount 控制最多 15 次(约 45 秒),超时后自动停止。
生命周期绑定 :当前 IntervalCheckToken 是单例且持有 Disposable,如果用户按返回键退出登录页,轮询不会自动停止。
markdown
用户进入登录页
↓
生成二维码业务URL
↓
Zxing 生成二维码Bitmap → 缩放/裁剪处理 → 页面展示二维码
↓
开启 RxJava 定时轮询(3秒间隔、限制最大轮询次数)
↓
客户端携带 deviceId + cacheKey 轮询请求后端接口
↓
后端返回三种状态: 1. 未扫码/未确认 → 继续轮询
2. 已扫码确认登录 → 停止轮询、本地保存Token、发送登录事件跳转主页
3. 二维码过期 → 停止轮询/刷新新二维码 4. 账号已下线 → 清空登录状态、退出登录
↓
接收EventBus登录事件 → 页面刷新UI、跳转主界面
1、展示二维码
typescript
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工具类
ini
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<EncodeHintType, Object> 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、开启轮询
typescript
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<LoginToken> 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;
}
}
}