【问题标题】:Save and show picked picture by URI after app restart应用重启后通过 URI 保存并显示选取的图片
【发布时间】:2023-02-18 06:49:49
【问题描述】:

我的想法是在那里呈现个人资料图片并允许用户更改它。 为了保存所选图片,我使用了SharedPreferences(将 Uri 保存为字符串)。问题是每次启动时图像都不会显示。 共享管理器检索到的值似乎是正确的,但 SubComposeAsyncImageContent 没有正确显示图片。

个人资料图片可组合:

@Composable
fun ProfilePicture(
    imageUri: String?,
    size: Dp = 50.dp,
    onClick: (String) -> Unit,
) {

    val launcher = rememberLauncherForActivityResult(
        contract = ActivityResultContracts.GetContent())  { uri: Uri? ->
        onClick(uri.toString())
    }

    if (imageUri != null) {
        Log.e("ProfilePicture", imageUri)
    }

    SubcomposeAsyncImage(
        model = imageUri,
        contentDescription = "",
        modifier = Modifier.clickable { launcher.launch("image/*") }
    ) {
        val state = painter.state
        Log.e("ProfilePicState", "${state}")
        if (state is AsyncImagePainter.State.Loading || state is AsyncImagePainter.State.Error) {
            CircularProgressIndicator()
        } else {
            SubcomposeAsyncImageContent()
        }

    }
}

这个想法是 imageUri 作为参数从配置文件屏幕(包含 ProfilePicture)传递。配置文件屏幕从 viewModel 获取此值,它可以访问 sharedPreferences。

ProfileScreen.kt:

@Composable
fun ProfileScreen(viewModel: ProfileViewModel) {

    var profileUri by rememberSaveable {
        mutableStateOf(viewModel.getProfilePicURI())
    }

    Log.w("ProfileScreen", profileUri)

    Column(
        horizontalAlignment = Alignment.CenterHorizontally,
        modifier = Modifier
            .fillMaxSize()
            .padding(16.dp)
    ) {
        ProfilePicture(
            imageUri = profileUri,
            size = 150.dp,
            onClick = {
                viewModel.onEvent(ProfileEvents.OnUpdateProfilePic(it))
                profileUri = viewModel.getProfilePicURI()
            }
        )
    }
}

最后,viewModel

class ProfileViewModel(val preferenceManager: PreferenceManager): ViewModel() {

    fun getProfilePicURI(): String {
        return preferenceManager.getProfilePic()
    }

    fun onEvent(event: ProfileEvents) {
        when (event) {
            is ProfileEvents.OnUpdateProfilePic -> {
                // update the sharedpreference
                preferenceManager.setProfilePic(event.newUri)
                Log.e("ProfileVM", "uri stored: ${getProfilePicURI()}")

            }
        }
    }
}

如前所述,代码在应用程序中工作,即我可以更改个人资料图片,即使我返回个人资料屏幕也会记住它,但在每次启动时,即使正确的资源似乎是,画家也无法加载图像发送。

日志如下所示:

2022-04-27 09:29:45.174 12502-12502/com.example.insurance W/ProfileScreen: content://com.android.providers.media.documents/document/image%3A96
2022-04-27 09:29:45.182 12502-12502/com.example.insurance E/ProfilePicture: content://com.android.providers.media.documents/document/image%3A96
2022-04-27 09:29:45.258 12502-12502/com.example.insurance E/ProfilePicState: Loading(painter=null)
2022-04-27 09:29:45.274 12502-12502/com.example.insurance E/ProfilePicState: Loading(painter=null)
2022-04-27 09:29:45.278 12502-12502/com.example.insurance E/ProfilePicState: Error(painter=null, result=coil.request.ErrorResult@bfc77785)

使用后退按钮退出应用程序有效,在重新创建时,个人资料图片就在那里。通过任务任务管理器销毁它会触发错误的行为。

当我启动应用程序时,显示个人资料图片的唯一方法是选择不同的图像,即如果我选择之前选择的 img,它不会显示。一旦我选择一个新的,它就会再次显示

【问题讨论】:

  • 你不应该依赖这个 URI 在应用重启后可用,将文件本身复制到应用存储中并存储这个 URI
  • 感谢您的回复,文件本身已经在设备存储中,因为它是从图库中获取的。此外,我认为这种情况正是我们拥有 URI 的原因。最后,我认为这与我的问题没有直接关系,因为文件“存在”但未显示,如果这有意义的话:D
  • 它在那里,但您没有访问它的权限。 Android 只有在用户选择它后才会为您提供访问权限,并且会持续很短的时间 - 直到您的应用程序终止。当您选择上次启动时选择的相同图像时,不会触发重组,因为 URI 相同。如果你将它设置为null,等待下一次用delay重组并用相同的值更新,你会看到它。
  • 我明白了,现在更有意义了。为了获取文件并复制它,我正在尝试以下操作: val file = File(uri.path) val fl = File("Pictures/Insurance/profilePic.jpg") file.copyTo(fl, overwrite = true) Nontheless我得到一个空指针异常。它说该文件不存在,即使我只是从内部存储中选择它并在应用程序中显示它......你知道可能出了什么问题吗?

标签: android kotlin android-jetpack-compose


【解决方案1】:

最新版本的 Android 仅在当前应用程序会话期间提供对文件的访问 - 重新启动后您不再有权读取相同的 URI。

该文件仍然存在:如果您再次选择它,您将获得相同的 URI,并且也可以访问,但在您的情况下,相同的 URI 不会触发重组,这就是您看不到更新的原因。

即使您可以访问 URI,也不能直接访问,您只能从context.contentResolver 获取输入流。这就是 Coil 在显示它时在后台为您所做的,但是要复制它,您必须自己管理它。

输出文件应该在App-specific storage

val context = LocalContext.current
val launcher = rememberLauncherForActivityResult(
    contract = ActivityResultContracts.GetContent()
)  { newUri: Uri? ->
    if (newUri == null) return@rememberLauncherForActivityResult

    val input = context.contentResolver.openInputStream(newUri) ?: return@rememberLauncherForActivityResult
    val outputFile = context.filesDir.resolve("profilePic.jpg")
    input.copyTo(outputFile.outputStream())
    uri = outputFile.toUri()
}

请注意,我在这里使用相同的文件名,因此如果您连续选择两个不同的图像,您将看不到从第一张到第二张的更新,因为输出 URI 仍然相同。如何解决这个问题取决于你,例如在文件名中添加一个 UUID,这样它每次都是一个唯一的 URI,只是不要忘记删除旧的。

【讨论】:

  • 这非常有效。非常感谢您的清晰解释和工作代码 sn-p。
猜你喜欢
  • 2017-02-03
  • 2018-03-13
  • 1970-01-01
  • 1970-01-01
  • 2018-02-05
  • 1970-01-01
  • 2015-08-30
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多