Android — 实现扫码登录功能

Android — 实现扫码登录功能现在大部分网站都有扫码登录功能 搭配相应的 App 就能免去输入账号密码实现快速登录

大家好,欢迎来到IT知识分享网。

现在大部分网站都有扫码登录功能,搭配相应的App就能免去输入账号密码实现快速登录。本文简单介绍如何实现扫码登录功能。

实现扫码登录

之前参与过一个电视App的开发,采用扫码登录,需要使用配套的App扫码登录后才能进入到主页。那么扫码登录该怎么实现呢?大致流程如下:

  1. 被扫端展示一个二维码,二维码包含被扫端的唯一标识(如设备id),并与服务端保持通讯(轮询、长连接、推送)。
  2. 扫码端扫描二维码之后,使用获取到的被扫端的唯一标识(如设备id)调用服务端扫码登录接口。
  3. 服务端接收扫码端发起的扫码登录请求,处理(如验证用户信息)后将登录信息发送到被扫端。

PS: 此为大致流程,具体使用需要根据实际需求进行调整。

接下来简单演示一下此流程。

添加依赖库

添加需要的SDK依赖库,在项目app module的build.gradle中的dependencies中添加依赖:

dependencies { 
    // 实现服务端(http、socket) implementation("org.nanohttpd:nanohttpd:2.3.1") implementation("org.nanohttpd:nanohttpd-websocket:2.3.1") // 与服务端通信 implementation("com.squareup.okhttp3:okhttp:4.12.0") // 扫描解析、生成二维码 implementation("com.github.jenly1314:zxing-lite:3.1.0") } 

服务端

使用NanoHttpD实现Socket服务端(与被扫端通信)和Http服务端(与扫码端通信),示例代码如下:

Socket服务

与被扫端保持通讯,在Http服务接收并处理完扫码登录请求后,将获取到的用户id发送给被扫端。

class ServerSocketClient : NanoWSD(9090) { 
    private var serverWebSocket: ServerWebSocket? = null override fun openWebSocket(handshake: IHTTPSession?): WebSocket { 
    return ServerWebSocket(handshake).also { 
    serverWebSocket = it } } private class ServerWebSocket(handshake: IHTTPSession?) : WebSocket(handshake) { 
    override fun onOpen() { 
   } override fun onClose(code: WebSocketFrame.CloseCode?, reason: String?, initiatedByRemote: Boolean) { 
   } override fun onMessage(message: WebSocketFrame?) { 
   } override fun onPong(pong: WebSocketFrame?) { 
   } override fun onException(exception: IOException?) { 
   } } override fun stop() { 
    super.stop() serverWebSocket = null } fun sendMessage(message: String) { 
    serverWebSocket?.send(message) } } 
Http服务

接收并处理来自扫码端的扫码登录请求,通过设备id和用户id判断被扫端是否可以登录。

const val APP_SCAN_INTERFACE = "loginViaScan" const val USER_ID = "userId" const val EXAMPLE_USER_ID = "" const val DEVICE_ID = "deviceId" const val EXAMPLE_DEVICE_ID = "example_device_id0001" class ServerHttpClient(private var scanLoginSucceedListener: ((userId: String) -> Unit)? = null) : NanoHTTPD(8080) { 
    override fun serve(session: IHTTPSession?): Response { 
    val uri = session?.uri return if (uri == "/$APP_SCAN_INTERFACE" && session.parameters[USER_ID]?.first() == EXAMPLE_USER_ID && session.parameters[DEVICE_ID]?.first() == EXAMPLE_DEVICE_ID ) { 
    scanLoginSucceedListener?.invoke(session.parameters[USER_ID]?.first() ?: "") newFixedLengthResponse("Login Succeed") } else { 
    super.serve(session) } } } 
服务控制类

启动或停止Socket服务和Http服务。

object ServerController { 
    private var serverSocketClient: ServerSocketClient? = null private var serverHttpClient: ServerHttpClient? = null fun startServer() { 
    (serverSocketClient ?: ServerSocketClient().also { 
    serverSocketClient = it }).run { 
    if (!isAlive) { 
    start(0) } } (serverHttpClient ?: ServerHttpClient { 
    serverSocketClient?.sendMessage("Login Succeed, user id is $it") }.also { 
    serverHttpClient = it }).run { 
    if (!isAlive) { 
    start(NanoHTTPD.SOCKET_READ_TIMEOUT, true) } } } fun stopServer() { 
    serverSocketClient?.stop() serverSocketClient = null serverHttpClient?.stop() serverHttpClient = null } } 

被扫端

Socket辅助类

使用OkHttp与服务端进行Socket通信。

class DevicesSocketHelper(private val messageListener: ((message: String) -> Unit)? = null) { 
    private var webSocket: WebSocket? = null private val webSocketListener = object : WebSocketListener() { 
    override fun onMessage(webSocket: WebSocket, bytes: ByteString) { 
    super.onMessage(webSocket, bytes) messageListener?.invoke(bytes.utf8()) } override fun onMessage(webSocket: WebSocket, text: String) { 
    super.onMessage(webSocket, text) messageListener?.invoke(text) } } fun openSocketConnection(serverPath: String) { 
    val okHttpClient = OkHttpClient.Builder() .connectTimeout(120, TimeUnit.SECONDS) .readTimeout(120, TimeUnit.SECONDS) .build() val request = Request.Builder().url(serverPath).build() webSocket = okHttpClient.newWebSocket(request, webSocketListener) } fun release() { 
    webSocket?.close(1000, "") webSocket = null } } 
被扫端示例页面

先展示二维码,接收到服务端的消息后,显示用户id。

class DeviceExampleActivity : AppCompatActivity() { 
    private lateinit var binding: LayoutDeviceExampleActivityBinding private var socketHelper: DevicesSocketHelper? = DevicesSocketHelper() { 
    message -> // 接收到服务端发来的消息,改变显示内容 runOnUiThread { 
    binding.tvUserInfo.text = message binding.ivQrCode.visibility = View.GONE binding.tvUserInfo.visibility = View.VISIBLE } } override fun onCreate(savedInstanceState: Bundle?) { 
    super.onCreate(savedInstanceState) binding = LayoutDeviceExampleActivityBinding.inflate(layoutInflater).also { 
    setContentView(it.root) it.includeTitle.tvTitle.text = "Device Example" } lifecycleScope.launch(Dispatchers.IO) { 
    // 使用设备id生成二维码 CodeUtils.createQRCode(EXAMPLE_DEVICE_ID, DensityUtil.dp2Px(200)).let { 
    qrCode -> withContext(Dispatchers.Main) { 
    binding.ivQrCode.setImageBitmap(qrCode) } } } socketHelper?.openSocketConnection("ws://localhost:9090/") } override fun onDestroy() { 
    super.onDestroy() socketHelper?.release() socketHelper = null } } 

扫描端

扫码页

继承zxing-lite库的BarcodeCameraScanActivity类,简单实现扫描与解析二维码。

class ScanQRCodeActivity : BarcodeCameraScanActivity() { 
    override fun initCameraScan(cameraScan: CameraScan<Result>) { 
    super.initCameraScan(cameraScan) // 播放扫码音效 cameraScan.setPlayBeep(true) } override fun createAnalyzer(): Analyzer<Result> { 
    return QRCodeAnalyzer(DecodeConfig().apply { 
    // 设置仅识别二维码 setHints(DecodeFormatManager.QR_CODE_HINTS) }) } override fun onScanResultCallback(result: AnalyzeResult<Result>) { 
    // 已获取结果,停止识别二维码 cameraScan.setAnalyzeImage(false) // 返回扫码结果 setResult(Activity.RESULT_OK, Intent().apply { 
    putExtra(CameraScan.SCAN_RESULT, result.result.text) }) finish() } } 
扫描端示例页面

提供扫码入口,提供输入框用于输入服务端IP,获取到扫码结果后发送给服务端。

class AppScanExampleActivity : AppCompatActivity() { 
    private lateinit var binding: LayoutAppScanExampleActivityBinding private var serverIp: String = "" private val scanQRCodeLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { 
    if (it.resultCode == Activity.RESULT_OK) { 
    it.data?.getStringExtra(CameraScan.SCAN_RESULT)?.let { 
    deviceId -> sendRequestToServer(deviceId) } } } override fun onCreate(savedInstanceState: Bundle?) { 
    super.onCreate(savedInstanceState) binding = LayoutAppScanExampleActivityBinding.inflate(layoutInflater).also { 
    setContentView(it.root) } OkHttpHelper.init() binding.btnScan.setOnClickListener { 
    // 获取输入的服务端ip(两台设备在同一WIFI下,直接通过IP访问服务端) serverIp = binding.etInputIp.text.toString() if (serverIp.isEmpty()) { 
    showSnakeBar("Server ip can not be empty") return@setOnClickListener } hideKeyboard(binding.etInputIp) scanQRCodeLauncher.launch(Intent(this, ScanQRCodeActivity::class.java)) } } private fun sendRequestToServer(deviceId: String) { 
    OkHttpHelper.sendGetRequest("http://${serverIp}:8080/${APP_SCAN_INTERFACE}", mapOf(Pair(USER_ID, EXAMPLE_USER_ID), Pair(DEVICE_ID, deviceId)), object : RequestCallback { 
    override fun onResponse(success: Boolean, responseBody: ResponseBody?) { 
    showSnakeBar("Scan login ${if (success) "succeed" else "failure"}") } override fun onFailure(errorMessage: String?) { 
    showSnakeBar("Scan login failure") } }) } private fun hideKeyboard(view: View) { 
    view.clearFocus() WindowInsetsControllerCompat(window, view).hide(WindowInsetsCompat.Type.ime()) } private fun showSnakeBar(message: String) { 
    runOnUiThread { 
    Snackbar.make(binding.root, message, Snackbar.LENGTH_SHORT).show() } } } 

示例入口页

提供被扫端和扫码端入口,打开被扫端时同时启动服务端。

class ScanLoginExampleActivity : AppCompatActivity() { 
    private lateinit var binding: LayoutScanLoginExampleActivityBinding override fun onCreate(savedInstanceState: Bundle?) { 
    super.onCreate(savedInstanceState) binding = LayoutScanLoginExampleActivityBinding.inflate(layoutInflater).also { 
    setContentView(it.root) it.includeTitle.tvTitle.text = "Scan Login Example" it.btnOpenDeviceExample.setOnClickListener { 
    // 打开被扫端同时启动服务 ServerController.startServer() startActivity(Intent(this, DeviceExampleActivity::class.java)) } it.btnOpenAppExample.setOnClickListener { 
    startActivity(Intent(this, AppScanExampleActivity::class.java)) } } } override fun onDestroy() { 
    super.onDestroy() ServerController.stopServer() } } 

最后

如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。

欢迎大家一键三连支持,若需要文中资料,直接扫描文末CSDN官方认证微信卡片免费领取↓↓↓(文末还有ChatGPT机器人小福利哦,大家千万不要错过)
Android — 实现扫码登录功能

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/102371.html

(0)

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信