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

Jetpack Compose如何将rememberLauncherForActivityResult封装为通用工具

方案一:封装Compose专用文件选择Hook(推荐)

该方案完全符合Compose生命周期管理规则,无需持有Activity实例,调用灵活,天然支持自定义MIME类型,是Compose场景下的最优解。

封装代码示例:

import android.net.Uri
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember

/**
 * 通用文件选择器封装
 * @return 第一个返回值为选中的文件Uri,第二个为启动选择器的方法,调用时传入对应MIME类型即可
 */
@Composable
fun rememberFilePicker(): Pair<Uri?, (mimeType: String) -> Unit> {
    val selectedUri = remember { mutableStateOf<Uri?>(null) }
    val pickerLauncher = rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { uri ->
        selectedUri.value = uri
    }
    val launchPicker = { mimeType: String ->
        pickerLauncher.launch(mimeType)
    }
    return selectedUri.value to launchPicker
}

原有业务组件改造后的调用示例:

@OptIn(ExperimentalCoilApi::class)
@Composable
fun ContactPagePhoto() {
    // 直接调用封装好的工具,拿到选中的Uri和启动方法
    val (selectedImageUri, launchFilePicker) = rememberFilePicker()
    Box(
        contentAlignment = Alignment.Center,
        modifier = Modifier
            .layoutId("userPhoto")
            .fillMaxHeight(0.4f)
            .fillMaxWidth(1f)
    ) {
        selectedImageUri?.let {
            Image(
                modifier = Modifier
                    .align(Alignment.TopCenter)
                    .fillMaxSize(1f),
                contentDescription = "UserImage",
                contentScale = ContentScale.Crop,
                painter = rememberImagePainter(data = it)
            )
        }
    }
    TextButton(
        onClick = { 
            // 传入图片MIME类型实现选图,后续选其他类型文件直接替换对应MIME即可
            launchFilePicker("image/*") 
        },
        modifier = Modifier.layoutId("changePhoto")
    ) {
        Text(
            text = "Change Photo",
            color = Color.Black
        )
    }
}

不同文件类型的MIME参考:

  • 选PDF:launchFilePicker("application/pdf")
  • 选视频:launchFilePicker("video/*")
  • 选所有类型文件:launchFilePicker("*/*")

该方案的生命周期和调用它的Composable组件完全绑定,组件销毁时会自动回收启动器,不会产生内存泄漏风险。


方案二:非Compose场景下的Activity依赖封装

如果需要在非Compose的业务逻辑中调用,可以封装为依赖ComponentActivity的工具类,注意必须在Activity的onStart生命周期前初始化,否则会抛出注册异常。

封装代码示例:

import android.net.Uri
import androidx.activity.ComponentActivity
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts

class FilePickerHelper(activity: ComponentActivity) {
    private var onFilePicked: ((Uri?) -> Unit)? = null
    private val pickerLauncher: ActivityResultLauncher<String> = activity.registerForActivityResult(
        ActivityResultContracts.GetContent()
    ) { uri ->
        onFilePicked?.invoke(uri)
    }

    fun launch(mimeType: String, onResult: (Uri?) -> Unit) {
        this.onFilePicked = onResult
        pickerLauncher.launch(mimeType)
    }
}

使用示例:

class MainActivity : ComponentActivity() {
    // 必须在onCreate前初始化
    private val filePicker = FilePickerHelper(this)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // 调用选图
        filePicker.launch("image/*") { uri ->
            // 处理选中的Uri
        }
    }
}

该方案仅建议在需要兼容非Compose场景时使用,注意不要在工具类中强持有Activity引用,避免内存泄漏。


内容的提问来源于stack exchange,提问作者Saher Al-Sous

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.05 16:36:03