Android混淆的一些坑儿

本文探讨了在Android应用中,当一个自定义View同时被宿主和插件依赖时可能导致的ClassCastException问题。详细分析了如何通过调整混淆规则及采用动态生成View的方式避免这一问题。

转载请注明出处:http://blog.youkuaiyun.com/kester_/article/details/52461917

发现问题

一个Library里的自定义View同时被宿主和插件以compile的形式依赖,
在使用时有可能导致ClassCastException。


研究问题

同时被宿主和插件以compile形式依赖,会导致app有两个一样包名的类,
出现转换异常,一开始产生了两个思路:
1. 让这个类被混淆
2. 插件里这个Library以provided形式依赖

考虑到依赖库需要跟随插件即时更新,放弃第2种思路,而项目中因为加了混淆字典,
宿主和插件同时进行混淆的话,这个类在宿主和插件里就是不同包名的类从而解决问题,
所以从第1个思路入手。

防止自定义View被混淆(混淆保护)的代码是

-keepclasseswithmembers class * {  
    public <init>(android.content.Context);  
}  
-keepclasseswithmembers class * {  
    public <init>(android.content.Context, android.util.AttributeSet);  
}  
-keepclasseswithmembers class * {  
    public <init>(android.content.Context, android.util.AttributeSet, int);  
}  

查看了宿主和插件的proguard文件发现,宿主中是有对自定义View进行混淆保护,
但是插件中是没有对自定义View进行混淆保护的,而看到的现象是宿主和插件都对
自定义View进行了混淆保护。

查看proguard文件上面默认备注,

# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in D:\Program Files (x86)\sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
#   http://developer.android.com/guide/developing/tools/proguard.html

# Add any project specific keep options here:

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
#   public *;
#}

可以发现,原来混淆规则不止使用Module里的proguard文件的规则,还需要加上SDK内
的默认混淆文件proguard-android.txt,然后对默认混淆保护文件进行修改验证后发现,
问题依然没有解决,这个自定义View还是被混淆保护了!那究竟还有哪里会影响混淆规则呢?

通过反向索引发现,这个类居然在一个奇怪的文件aapt_rules.txt里使用到了,还是被keep了。
这个奇怪的文件在build目录里,而且项目里也没有生成它的地方,所以可以知道,这是在代码
控制范围外的编译产物,网上对aapt_rules.txt的解释非常少,于是翻了gradle的源码,发现:

if (config.buildType.isMinifyEnabled()) {
    if (config.buildType.shrinkResources && config.useJack) {
        LoggingUtil.displayWarning(Logging.getLogger(this.class), scope.globalScope.project,
                "shrinkResources does not yet work with useJack=true")
    }
    ConventionMappingHelper.map(processResources, "proguardOutputFile") {
        new File("$scope.globalScope.buildDir/
                ${FD_INTERMEDIATES}/proguard-rules/${config.dirName}/aapt_rules.txt")
    }
    } else if (config.buildType.shrinkResources) {
        LoggingUtil.displayWarning(Logging.getLogger(this.class), scope.globalScope.project,
                "To shrink resources you must also enable ProGuard")
    }
}

aapt_rules.txt是在ProcessAndroidResources 处理资源过程产生的,并且

if (config.isMinifyEnabled()) {
    conventionMapping(jackTask).map("proguardFiles") {
        // since all the output use the same resources, we can use the first output
        // to query for a proguard file.
        BaseVariantOutputData variantOutputData = variantData.outputs.get(0)

        List<File> proguardFiles = config.getProguardFiles(true /*includeLibs*/,
                [getDefaultProguardFile(DEFAULT_PROGUARD_CONFIG_FILE)])
        File proguardResFile = variantOutputData.processResourcesTask.proguardOutputFile
        if (proguardResFile != null) {
            proguardFiles.add(proguardResFile)
        }
        // for tested app, we only care about their aapt config since the base
        // configs are the same files anyway.
        if (testedVariantData != null) {
            // use single output for now.
            proguardResFile =
                    testedVariantData.outputs.get(0).processResourcesTask.proguardOutputFile
            if (proguardResFile != null) {
                proguardFiles.add(proguardResFile)
            }
        }

        return proguardFiles
    }

    jackTask.mappingFile = project.file(
            "${project.buildDir}/${FD_OUTPUTS}/
            mapping/${variantData.variantConfiguration.dirName}/mapping.txt")
}

通过上面代码可以看出,在进行混淆的时候,gradle会把aapt_rules.txt也加入到混淆规则文件proguardFiles中,所以也就是这里导致了插件中自定义View无法被混淆。


解决问题

官方答复说是这个问题在最新版的gradle中已经去掉了,对于这个系统级别的BUG形式存在的规则,只好妥协了,既然无法从代码上让这个类混淆(想想也是,xml中需要使用的自定义View如果被混淆了在R文件中索引资源可能都出问题了),就把这个自定义View在代码中进行动态生成了,只要它不在布局文件中,就不会在编译资源时被加入到aapt_rules.txt 的混淆保护中了,验证通过,aapt_rules.txt中没有出现这个自定义View的混淆规则了,mapping.txt也没有发现该类被混淆保护了,问题解决。


概括结论

在使用Android Studio进行编译混淆时,gradle会使用SDK默认proguard-android.txt,Module中
proguard-rules.pro,再加上资源编译产物aapt_rules.txt三个文件作为混淆保护规则处理。布局文件
中自定义View如果不想被混淆保护,就从布局文件中去掉吧,在代码中动态生成也是OK的。

<think>我们正在讨论的是相机录制视频时遇到的问题:录制自动结束并显示录制时间过短,同时关注MediaCodec配置信息是否为传递的参数。 根据之前的错误讨论,我们知道MediaCodec在配置阶段(CONFIGURING)可能因为参数问题(如分辨率、帧率等)报错(BAD_VALUE)。现在用户的问题场景是:录制自动结束且时间过短,这可能是由于配置问题导致录制无法正常持续。 结合用户提供的引用: 引用[1]提到可以使用MediaRecorder+Camera自定义录制。 引用[2]描述了录制启动和停止的流程,包括MediaRecorder的配置、准备和启动。 引用[3]是关于相机参数(如分辨率、摄像头方向等)的记录,这些参数可能会传递给MediaRecorder。 引用[4]提到了MediaCodec解码器的帧率以及SurfaceFlinger的工作机制,这关系到视频帧的渲染。 引用[5]则提到MediaCodec在编码时的一些,特别是关于YUV格式的处理。 用户的问题:相机录制时打印的MediaCodec配置信息是否为传递的参数? 回答思路: 1. 首先说明在相机录制过程中,MediaCodec的配置信息通常是由MediaRecorder根据我们传递的参数(如分辨率、码率、帧率等)以及设备支持的格式自动选择的。 2. 然后分析录制自动结束且时间过短的可能原因,其中配置参数的不兼容(如分辨率超出支持范围)是一个重要因素(参考之前BAD_VALUE的错误)。 3. 最后给出调试建议:如何查看MediaCodec的配置信息以及如何确保参数在设备支持范围内。 详细回答: 在Android中使用MediaRecorder进行视频录制时,我们通过调用MediaRecorder的setVideoSize()、setVideoFrameRate()、setVideoEncodingBitRate()等方法设置参数。这些参数会被传递给底层的MediaCodec(编解码器)。但是,MediaCodec实际使用的配置参数可能与我们传递的参数不完全一致,因为系统会根据设备的硬件能力和当前可用资源进行适配。因此,打印的MediaCodec配置信息可能是经过调整后的参数。 录制自动结束且显示时间过短的问题,常见原因包括: - 配置参数超出了设备支持的范围(如过高分辨率、帧率或比特率),导致编解码器初始化失败(如之前提到的BAD_VALUE错误)或录制过程中崩溃。 - 在录制过程中出现错误(如I/O错误、存储空间不足等)导致录制提前终止。 - 相机资源被其他应用占用或释放不当。 针对配置参数问题,我们可以通过以下步骤调试: 1. 在调用MediaRecorder的prepare()方法之前,确保设置的所有参数都在设备支持的范围内。可以通过CameraData(如引用[3])记录相机支持的预览和录制分辨率,并选择支持的分辨率进行录制。 2. 在MediaRecorder配置完成后,我们可以尝试获取MediaCodec的信息(通过日志或调试信息)来确认实际使用的配置。但注意,MediaRecorder的API并没有直接提供获取底层MediaCodec配置的方法。我们可以通过adb logcat查看系统日志,过滤MediaCodec相关的日志信息,通常系统会输出编解码器的配置详情。 3. 参考引用[5]中提到的,注意视频格式(如YUV格式)的处理,避免因格式转换问题导致错误。 具体操作: - 在调用MediaRecorder的prepare()方法之前,使用CameraParameters获取相机支持的视频录制分辨率、帧率范围等,并确保我们设置的值在这些范围内。 - 在日志中搜索MediaCodec的配置信息:运行应用时,使用adb logcat过滤日志标签“MediaCodec”或“MediaRecorder”,例如: ``` adb logcat | grep -iE "MediaCodec|MediaRecorder" ``` 这些日志可能会显示编解码器的配置细节,如mime类型、分辨率、帧率、颜色格式等。 示例代码片段(设置MediaRecorder参数): ```java // 假设我们已经从CameraParameters中获取了支持的录制分辨率列表 List<Camera.Size> supportedSizes = parameters.getSupportedVideoSizes(); // 选择一个支持的分辨率,例如第一个 Camera.Size videoSize = supportedSizes.get(0); mediaRecorder.setVideoSize(videoSize.width, videoSize.height); // 设置帧率,同样需要检查支持的范围 List<int[]> supportedFpsRanges = parameters.getSupportedPreviewFpsRange(); // 选择一个合适的范围,例如第一个 int[] fpsRange = supportedFpsRanges.get(0); mediaRecorder.setVideoFrameRate(fpsRange[1]/1000); // 注意单位,getSupportedPreviewFpsRange返回的是千分之一帧率 // 其他设置:编码、比特率等 mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264); mediaRecorder.setVideoEncodingBitRate(bitRate); ``` 注意事项:不同设备对参数的支持情况差异很大,务必在运行时动态获取设备支持的能力。 如果问题仍然存在,可以考虑以下措施: - 降低参数(如分辨率、帧率、比特率)以避免超出设备处理能力。 - 检查存储权限和存储空间。 - 确保相机资源正确释放,没有其他应用占用相机。 相关问题: 1. 如何动态获取Android设备相机支持的视频录制参数? 2. MediaRecorder在配置过程中有哪些常见错误? 3. 如何通过日志分析MediaCodec的配置和运行错误? 4. 相机录制视频时自动停止的可能原因有哪些? 请根据上述思路组织回答。 </think>### MediaCodec配置信息与相机录制异常分析 根据您的场景描述(相机录制自动结束且显示录制时间过短),结合MediaCodec的工作原理和提供的引用信息,以下是逐步分析: #### 1. **MediaCodec配置信息与参数传递的关系** - **配置信息本质**: 相机录制时打印的MediaCodec配置信息**不是原始传递参数**,而是经过系统层处理的**实际生效配置**。Android系统会根据以下因素自动调整参数: $$实际配置 = f(传递参数, 设备能力, 系统资源状态)$$ 其中 $f$ 表示系统内部的适配逻辑(参考引用[4]的VSYNC机制)[^4]。 - **参数转换过程**: 如引用[2]所述,当调用`startRecordVideo()`时: ```java // 伪代码示例 mediaRecorder.setVideoSize(width, height); // 开发者传递的参数 mediaRecorder.setVideoFrameRate(fps); mediaRecorder.prepare(); // 系统在此阶段适配实际配置 ``` 在`prepare()`调用后,系统会: 1. 检查硬件支持能力(如引用[3]的`CameraData`分辨率限制)[^3] 2. 根据当前资源状态动态调整参数(如引用[4]的帧率同步机制)[^4] 3. 生成MediaCodec实际使用的配置并打印日志 #### 2. **录制自动结束的可能原因** 基于您描述的"自动结束+时间过短"现象,结合MediaCodec配置问题,常见原因包括: | 原因类型 | 具体表现 | 相关引用 | |---------|---------|---------| | **参数超限** | 分辨率/帧率超出芯片能力(如1080p@60fps在低端设备) | [^3][^5] | | **格式不兼容** | YUV格式错误触发MediaCodec内部交换(如NV21/NV12混淆) | [^5] | | **资源冲突** | SurfaceFlinger队列满载导致帧丢弃(引用[4]的缓冲机制) | [^4] | | **配置错误** | MediaRecorder未正确设置`orientation`等关键参数 | [^3] | #### 3. **诊断与解决方案** **步骤1:验证实际配置** 通过`adb logcat`过滤日志: ```bash adb logcat | grep -E "MediaCodec|configure" ``` 观察输出中的关键字段: - `width/height`:是否与`CameraData`记录一致[^3] - `color-format`:是否为设备支持的YUV格式(如`COLOR_FormatYUV420Flexible`) - `frame-rate`:是否低于预设值 **步骤2:参数兼容性处理** 参考引用[5]的解决方案[^5]: ```java // 确保使用设备兼容的YUV格式 if (Build.MANUFACTURER.equals("特定问题厂商")) { convertNV21ToNV12(rawData); // 避免MediaCodec内部交换 } // 动态适配分辨率 Camera.Size optimalSize = getSupportedVideoSize(); // 引用[3]的方法[^3] mediaRecorder.setVideoSize(optimalSize.width, optimalSize.height); ``` **步骤3:资源监控** 添加资源检测逻辑: ```java // 录制前检查资源 if (isSurfaceFlingerOverloaded()) { // 参考引用[4]的队列机制[^4] reduceFrameRate(30); // 降级到30fps } ``` #### 4. **典型问题场景还原** 以您描述的"自动结束"为例,可能的错误链: 1. 传递参数 → 1920x1080@60fps 2. 设备实际支持 → 1080p@30fps(芯片限制[^3]) 3. MediaCodec配置时自动降级 → 但YUV格式不兼容触发BUG[^5] 4. 首帧编码失败 → 录制立即终止 → 显示"时间过短" > ️ 此场景下日志显示的MediaCodec配置可能是`1080p@30fps`,但原始参数已被系统修改。 通过精确比对**传递参数**和**日志中的生效配置**,可定位是参数超限还是系统适配错误导致的问题
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值