BiliRoamingX-integrations项目中评论区图片查看功能的实现与优化

BiliRoamingX-integrations项目中评论区图片查看功能的实现与优化

【免费下载链接】BiliRoamingX-integrations BiliRoamingX integrations powered by revanced. 【免费下载链接】BiliRoamingX-integrations 项目地址: https://gitcode.com/gh_mirrors/bi/BiliRoamingX-integrations

你是否曾经在B站评论区看到有趣的图片,想要保存下来却苦于没有便捷的方法?BiliRoamingX-integrations项目通过巧妙的Hook技术,为B站客户端添加了评论区图片长按保存功能,让图片保存变得触手可及。

功能概述

BiliRoamingX-integrations项目中的评论区图片查看功能主要实现了以下核心特性:

  • 长按保存图片:在评论区图片查看界面长按即可保存图片到本地相册
  • 智能URL处理:自动处理B站特有的图片URL格式,去除冗余参数
  • 权限管理:完善的存储权限申请和处理机制
  • 触觉反馈:保存操作时提供震动反馈,提升用户体验

技术实现原理

核心架构

整个功能基于ReVanced Patcher框架,通过Hook B站客户端的图片查看器组件来实现功能增强。以下是核心架构图:

mermaid

关键代码分析

1. CommentImagePatch - 核心Hook类
public class CommentImagePatch {
    private static final int imageViewId = Utils.getResId("image_view", "id");

    @Keep
    public static void bindClickListener(ImageFragment fragment) {
        if (!Settings.SaveCommentImage.get()) return;
        
        // 解析图片URL
        var arguments = fragment.getArguments();
        var imageItem = arguments.getParcelable("image_item");
        var field = Reflex.findFirstFieldByExactTypeOrNull(
            imageItem.getClass().getSuperclass(), String.class);
        var imageUrl = (String) Reflex.getFieldValue(imageItem, field);
        
        // 处理B站特有URL格式
        if (TextUtils.isEmpty(imageUrl) || !imageUrl.startsWith("http")) return;
        var atIndex = imageUrl.indexOf('@');
        if (atIndex != -1)
            imageUrl = imageUrl.substring(0, atIndex);
        
        // 绑定长按监听器
        var view = fragment.getView();
        var imageView = view.findViewById(imageViewId);
        final var finalImageUrl = imageUrl;
        imageView.setOnLongClickListener(v -> {
            v.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
            KtUtils.saveImage(finalImageUrl);
            return true;
        });
    }
}
2. 图片保存实现 - KtUtils.saveImage
fun saveImage(url: String) {
    fun save(url: String) = runCatching {
        URL(url).openStream().use { input ->
            val picDir = Environment.getExternalStoragePublicDirectory(
                Environment.DIRECTORY_PICTURES)
            val filename = url.substringAfterLast('/')
            val saveDir = File(picDir, "bili").also { it.mkdirs() }
            val imageFile = File(saveDir, filename)
            
            // 文件保存操作
            imageFile.outputStream().use { input.copyTo(it) }
            
            // 更新媒体库
            MediaScannerConnection.scanFile(
                Utils.getContext(), 
                arrayOf(imageFile.absolutePath), 
                null, 
                null
            )
            Toasts.showLongWithId("biliroaming_toast_image_save_success", imageFile.path)
        }
    }.onFailure {
        Logger.error(it) { "image save failed, url: $url" }
        Toasts.showShortWithId("biliroaming_toast_image_save_failed")
    }
    
    // 权限处理
    if (Build.VERSION.SDK_INT > Build.VERSION_CODES.Q) {
        Utils.async { save(url) }
    } else {
        val activity = ApplicationDelegate.requireTopActivity()
        activity.requestPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) { 
            granted, shouldExplain ->
            if (granted) {
                Utils.async { save(url) }
            } else if (shouldExplain) {
                Toasts.showShortWithId("biliroaming_write_storage_failed")
            }
        }
    }
}

3. 配置管理 - Settings类

object Settings {
    @JvmField val SaveCommentImage = BooleanSetting(key = "save_comment_image")
    // ... 其他配置项
}

技术难点与解决方案

难点1:B站图片URL特殊格式处理

B站的图片URL通常包含特殊参数,如 https://example.com/image.jpg@100w_100h,需要去除@后面的参数才能获取原始图片。

解决方案

var atIndex = imageUrl.indexOf('@');
if (atIndex != -1)
    imageUrl = imageUrl.substring(0, atIndex);

难点2:反射获取私有字段

B站的图片数据对象包含私有字段,需要通过反射机制获取真实的图片URL。

解决方案

var field = Reflex.findFirstFieldByExactTypeOrNull(
    imageItem.getClass().getSuperclass(), String.class);
var imageUrl = (String) Reflex.getFieldValue(imageItem, field);

难点3:Android权限适配

不同Android版本对存储权限的处理方式不同,需要做版本适配。

解决方案

if (Build.VERSION.SDK_INT > Build.VERSION_CODES.Q) {
    // Android 10+ 使用Scoped Storage
    Utils.async { save(url) }
} else {
    // 旧版本需要申请存储权限
    activity.requestPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) { 
        granted, shouldExplain ->
        if (granted) Utils.async { save(url) }
    }
}

性能优化策略

1. 异步文件操作

所有文件保存操作都在后台线程执行,避免阻塞UI线程:

Utils.async { save(url) }

2. 资源ID缓存

通过资源名获取资源ID并进行缓存,避免重复查找:

private static final int imageViewId = Utils.getResId("image_view", "id");

3. 错误处理机制

完善的异常捕获和处理机制,确保功能稳定性:

.runCatching {
    // 可能抛出异常的操作
}.onFailure {
    Logger.error(it) { "image save failed" }
    Toasts.showShortWithId("biliroaming_toast_image_save_failed")
}

功能扩展与自定义

配置选项

用户可以通过模块设置开启或关闭此功能:

配置项类型默认值描述
save_comment_imageBooleantrue是否启用评论区图片保存功能

扩展可能性

基于当前架构,可以轻松扩展更多功能:

  1. 图片分享功能:添加分享到其他应用的支持
  2. 图片编辑:集成简单的图片编辑功能
  3. 批量保存:支持多张图片批量保存
  4. 自定义保存路径:允许用户指定图片保存目录

最佳实践与使用建议

开发建议

  1. 遵循Android开发规范:正确处理权限和生命周期
  2. 异常处理:确保所有可能抛出异常的操作都有适当的异常处理
  3. 性能考虑:文件操作在后台线程执行,避免ANR
  4. 用户体验:提供适当的反馈(震动、Toast提示)

使用建议

  1. 确保存储权限:在Android 10以下版本需要授予存储权限
  2. 检查功能开关:在模块设置中确认"保存评论区图片"功能已开启
  3. 文件管理:保存的图片位于 Pictures/bili/ 目录下

总结

BiliRoamingX-integrations项目中的评论区图片查看功能通过巧妙的Hook技术和完善的实现细节,为用户提供了便捷的图片保存体验。该功能不仅技术实现优雅,而且在性能、稳定性和用户体验方面都做了充分的考虑。

关键技术亮点包括:

  • 反射机制获取私有数据
  • B站特有URL格式处理
  • 多版本Android权限适配
  • 异步文件操作避免阻塞
  • 完善的错误处理和用户反馈

这个功能的实现展示了如何在现有应用中通过Patch方式优雅地添加新功能,为Android应用的功能扩展提供了很好的参考范例。

【免费下载链接】BiliRoamingX-integrations BiliRoamingX integrations powered by revanced. 【免费下载链接】BiliRoamingX-integrations 项目地址: https://gitcode.com/gh_mirrors/bi/BiliRoamingX-integrations

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值