You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何onWindowFocusChanged未触发?clipDataOnResume在onActivityResult偶现空值

问题描述

我尝试用ZXing二维码结合摄像头实现网络共享剪贴板内容,原本用onWindowFocusChanged事件获取剪贴板内容大部分情况正常,但clipDataOnResume成员偶尔会在onActivityResult中为空,此时会弹出Toast提示"clipDataOnResume is null at onActivityResult"。请问该如何避免这个问题?

使用的依赖

implementation("com.google.zxing:core:3.4.0")
implementation("com.journeyapps:zxing-android-embedded:4.1.0")

MainActivity 代码

class MainActivity : AppCompatActivity() {

    var clipDataOnResume: ClipData? = null
    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()

        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
            val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
            insets
        }

        binding.btnScan.setOnClickListener {
            val scanner = IntentIntegrator(this)
            scanner.setOrientationLocked(true)
            scanner.setPrompt("Scan a barcode.")
            scanner.setDesiredBarcodeFormats(IntentIntegrator.QR_CODE)
            scanner.setBarcodeImageEnabled(true)
            scanner.initiateScan()
        }
    }

    fun sendContentTask(url: String, clipboardContent: String): Int {
// Send clipboard content over network
    }

    fun getContentTask(url: String): String {
// Get clipboard content over network
    }

    public override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        val result = IntentIntegrator.parseActivityResult(requestCode, resultCode, data)
        if (result != null) {
            if (result.getContents() == null) {
                Toast.makeText(this, "Cancelled from fragment", Toast.LENGTH_LONG).show()

            } else {
                if (result.getContents().endsWith("post")) {
                    val manager = getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
                    var clipContent = ""
                    if (manager.hasPrimaryClip()) {
                        val clipData = manager.primaryClip
                        val item = clipData!!.getItemAt(0)
                        clipContent = item.text.toString()
                    } else if (clipDataOnResume != null && clipDataOnResume!!.itemCount > 0) {
                        val item = clipDataOnResume!!.getItemAt(0)
                        clipContent = item.text.toString()
                    }

                    if (clipContent == ""){
                        Toast.makeText(this, "clipDataOnResume is null at onActivityResult", Toast.LENGTH_LONG).show()
                    } else {
                        val url = result.getContents();
                        Toast.makeText(this, "Location: " + url, Toast.LENGTH_LONG).show()
                        runBlocking {
                            var result: Deferred<Unit> = async() {
                                withContext(Dispatchers.IO) {
                                    val msg = sendContentTask(url, clipContent)
                                }
                            }
                            result.await()
                        }
                    }
                } else if (result.getContents().endsWith("get")) {
                    val url = result.getContents()
                    Toast.makeText(this, "Location: " + url, Toast.LENGTH_LONG).show()
                    runBlocking {
                        var msg = ""
                        var result: Deferred<Unit> = async() {
                            withContext(Dispatchers.IO) {
                                msg = getContentTask(url)
                            }
                        }
                        result.await()
                        setClipboardContent(msg)
                    }
                }
            }

        }
    }

    override fun onWindowFocusChanged(hasFocus: Boolean) {
        super.onWindowFocusChanged(hasFocus)
        if (hasFocus) {
            val manager = getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
            clipDataOnResume = manager.primaryClip
            if (clipDataOnResume == null) {
                Toast.makeText(this, "clipDataOnResume is null at focus", Toast.LENGTH_LONG).show()
            }
        }
    }

}

解决方案

1. 换用onResume保存剪贴板数据

onWindowFocusChanged触发时机不稳定(系统弹窗、多窗口切换都会干扰),改用onResume方法——Activity回到前台时一定会执行这个生命周期方法,能稳定获取剪贴板内容:

override fun onResume() {
    super.onResume()
    val manager = getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
    clipDataOnResume = manager.primaryClip
}

2. 点击扫描按钮时立即缓存剪贴板

在启动扫码Activity前,直接获取当前剪贴板内容缓存,确保数据是最新且可靠的:

binding.btnScan.setOnClickListener {
    // 点击时立即缓存剪贴板内容
    val manager = getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
    clipDataOnResume = manager.primaryClip
    
    val scanner = IntentIntegrator(this)
    scanner.setOrientationLocked(true)
    scanner.setPrompt("Scan a barcode.")
    scanner.setDesiredBarcodeFormats(IntentIntegrator.QR_CODE)
    scanner.setBarcodeImageEnabled(true)
    scanner.initiateScan()
}

3. 增加多层空值兜底逻辑

在onActivityResult中,即使缓存为空,也再次尝试获取剪贴板,避免直接报错:

if (result.getContents().endsWith("post")) {
    val manager = getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
    var clipContent = ""
    
    // 优先直接获取当前剪贴板
    if (manager.hasPrimaryClip() && manager.primaryClip?.itemCount ?: 0 > 0) {
        val item = manager.primaryClip!!.getItemAt(0)
        clipContent = item.text.toString() ?: ""
    } 
    // 缓存内容兜底
    else if (clipDataOnResume != null && clipDataOnResume!!.itemCount > 0) {
        val item = clipDataOnResume!!.getItemAt(0)
        clipContent = item.text.toString() ?: ""
    } 
    // 最后再尝试一次获取
    else {
        val latestClip = manager.primaryClip
        if (latestClip?.itemCount ?:0 >0) {
            clipContent = latestClip!!.getItemAt(0).text.toString() ?: ""
        }
    }

    if (clipContent.isEmpty()){
        Toast.makeText(this, "无法获取剪贴板内容", Toast.LENGTH_LONG).show()
    } else {
        val url = result.getContents();
        Toast.makeText(this, "地址: " + url, Toast.LENGTH_LONG).show()
        // 替换runBlocking,用lifecycleScope避免阻塞主线程
        lifecycleScope.launch {
            withContext(Dispatchers.IO) {
                sendContentTask(url, clipContent)
            }
        }
    }
}

4. 替换runBlocking避免主线程阻塞

runBlocking会卡死UI线程,改用lifecycleScope启动协程,先添加依赖(如果没有的话):

implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.2")

然后直接用lifecycleScope替换原有的runBlocking代码,保证界面流畅。

内容的提问来源于stack exchange,提问作者Papp Zoltán

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 07:24:56