【Android 美颜相机】第二天:Android-GPUImage Sample模块源码解析

Android-GPUImage Sample模块源码解析

Android-GPUImage是对标iOS GPUImage的Android端GPU滤镜库,其Sample模块是学习库使用的核心入口,涵盖「图片编辑」和「相机实时滤镜」两大核心场景。本文将从目录结构、启动入口、构建配置三个维度,解析Sample模块的整体架构设计。

Sample模块目录结构解析

Sample模块的目录树如下(核心文件已标注):

复制代码
android-gpuimage/sample/
├── build.gradle                // 模块构建配置
├── proguard-project.txt        // ProGuard混淆规则
└── src/main/
    ├── AndroidManifest.xml     // 权限与页面配置
    ├── java/jp/co/cyberagent/android/gpuimage/sample/
    │   ├── GPUImageFilterTools.kt  // 滤镜选择器+滤镜创建工具
    │   ├── activity/              // 核心页面
    │   │   ├── MainActivity.kt    // 入口页面:跳转相册/相机场景
    │   │   ├── GalleryActivity.kt // 图片编辑场景:选图+滤镜+保存
    │   │   └── CameraActivity.kt  // 相机场景:实时滤镜+拍照+相机切换
    │   └── utils/                 // 工具类
    │       ├── Camera1Loader.kt   // 低版本相机(<Android 5.0)适配
    │       ├── Camera2Loader.kt   // 高版本相机(≥Android 5.0)适配
    │       ├── CameraLoader.kt    // 相机适配抽象层
    │       ├── ImageExt.kt        // 图片扩展(本文暂不涉及)
    │       └── ViewExt.kt         // View布局监听工具
    └── res/                      // 资源文件(布局、字符串等)

各核心文件的职责:

文件/目录 核心作用
GPUImageFilterTools.kt 封装滤镜列表、弹窗选择器、滤镜实例化(如Sepia、Sobel边缘检测等)
MainActivity.kt 应用入口,处理相机/存储权限申请,跳转Gallery/Camera页面
GalleryActivity.kt 图片编辑核心页面:选图、滤镜切换、参数调整、保存处理后的图片
CameraActivity.kt 相机实时滤镜核心页面:相机预览、实时渲染、前后置切换、拍照保存
Camera1/2Loader.kt 分版本封装相机预览逻辑,解耦Camera1/Camera2 API差异
ViewExt.kt 简化View布局监听(如doOnLayout),解决GPUImageView尺寸初始化时机问题

MainActivity

MainActivity是Sample的启动页,核心逻辑是「权限校验 + 页面跳转」,我们逐行解析关键代码:

1. 页面初始化与点击事件

kotlin 复制代码
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    // 跳转图片编辑页面(GalleryActivity)
    findViewById<View>(R.id.button_gallery).setOnClickListener {
        startActivity(Intent(this, GalleryActivity::class.java))
    }
    // 跳转相机实时滤镜页面(CameraActivity)
    findViewById<View>(R.id.button_camera).setOnClickListener {
        if (!hasCameraPermission() || !hasStoragePermission()) {
            // 权限未授予时申请:相机+存储
            ActivityCompat.requestPermissions(
                this,
                arrayOf(Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE),
                REQUEST_CAMERA
            )
        } else {
            startActivity(Intent(this, CameraActivity::class.java))
        }
    }
}
  • 「相册按钮」直接跳转GalleryActivity(无需权限,相册选图权限由系统相册处理);
  • 「相机按钮」需先校验CAMERAWRITE_EXTERNAL_STORAGE权限,未授予则触发申请。

2. 权限申请回调

kotlin 复制代码
override fun onRequestPermissionsResult(
    requestCode: Int,
    permissions: Array<String>,
    grantResults: IntArray
) {
    if (requestCode == REQUEST_CAMERA && grantResults.size == 2
        && grantResults[0] == PackageManager.PERMISSION_GRANTED
        && grantResults[1] == PackageManager.PERMISSION_GRANTED
    ) {
        // 双权限授予后跳转相机页面
        startActivity(Intent(this, CameraActivity::class.java))
    } else {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
    }
}
  • 仅当「相机 + 存储」双权限都授予时,才跳转CameraActivity;
  • 权限拒绝则不处理(符合Android权限最佳实践)。

3. 权限校验工具方法

kotlin 复制代码
private fun hasCameraPermission(): Boolean {
    return ContextCompat.checkSelfPermission(
        this,
        Manifest.permission.CAMERA
    ) == PackageManager.PERMISSION_GRANTED
}

private fun hasStoragePermission(): Boolean {
    return ContextCompat.checkSelfPermission(
        this,
        Manifest.permission.WRITE_EXTERNAL_STORAGE
    ) == PackageManager.PERMISSION_GRANTED
}
  • 封装权限校验逻辑,提高代码复用性;
  • 注意:Android 10+已废弃WRITE_EXTERNAL_STORAGE强制申请,Sample未做高版本适配,实际开发需补充MediaStore保存逻辑。

Sample模块构建与配置解析

1. build.gradle配置

groovy 复制代码
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-android'

android {
    compileSdkVersion COMPILE_SDK_VERSION as int
    defaultConfig {
        minSdkVersion MIN_SDK_VERSION as int
        targetSdkVersion TARGET_SDK_VERSION as int
        versionCode = VERSION_CODE as int
        versionName = VERSION_NAME
    }
    buildTypes {
        release {
            minifyEnabled true
            shrinkResources true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation project(':library') // 依赖核心库模块
    implementation 'androidx.appcompat:appcompat:1.2.0'
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
}
  • 核心依赖:project(':library') 关联Android-GPUImage的核心库,是Sample能使用滤镜API的基础;
  • 构建类型:Release包开启混淆和资源压缩,符合生产环境要求。

2. AndroidManifest.xml配置

xml 复制代码
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="jp.co.cyberagent.android.gpuimage.sample">

    <!-- 核心权限 -->
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.CAMERA" />
    <!-- 相机硬件特性声明 -->
    <uses-feature android:name="android.hardware.camera" />
    <uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />

    <application ...>
        <activity android:name=".activity.MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name=".activity.GalleryActivity" />
        <activity
            android:name=".activity.CameraActivity"
            android:screenOrientation="portrait"  // 强制竖屏,简化旋转适配
            android:theme="@style/AppTheme.NoActionBar" />
    </application>
</manifest>
  • 权限声明:明确申请相机和存储权限;
  • 相机页面强制竖屏:避免横屏导致的预览方向错乱,降低适配成本;
  • 硬件特性:声明依赖相机,但自动对焦为非必需(兼容低配设备)。

3. ProGuard混淆规则(proguard-project.txt)

Sample的混淆规则仅保留基础配置,核心原则是「不混淆Shader相关类、不混淆相机/OpenGL核心API」:

txt 复制代码
# 无额外keep规则,依赖默认的proguard-android.txt
# 实际项目中需补充:
# -keep class jp.co.cyberagent.android.gpuimage.** { *; }
# 避免滤镜类、GPUImageView被混淆

GalleryActivity

GalleryActivity是Android-GPUImage Sample中「图片编辑」场景的核心页面,完整覆盖「选图 → 滤镜切换 → 参数调整 → 保存图片」全流程。

GalleryActivity的核心业务流程:

复制代码
启动相册选择器 → 选择图片(返回Uri)→ 加载图片到GPUImageView → 
选择滤镜(如Sepia、Sobel边缘检测)→ 调整滤镜参数(SeekBar)→ 
保存处理后的图片到相册

页面初始化与UI交互逻辑

1. 核心变量与布局初始化

kotlin 复制代码
class GalleryActivity : AppCompatActivity() {
    // 滤镜参数调整器:封装不同滤镜的参数范围映射
    private var filterAdjuster: FilterAdjuster? = null
    // GPUImageView:核心渲染视图(封装OpenGL渲染、滤镜应用)
    private val gpuImageView: GPUImageView by lazy { findViewById<GPUImageView>(R.id.gpuimage) }
    // 参数调整SeekBar:控制滤镜强度(如对比度、亮度)
    private val seekBar: SeekBar by lazy { findViewById<SeekBar>(R.id.seekBar) }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_gallery)

        // 1. SeekBar监听:实时调整滤镜参数
        seekBar.setOnSeekBarChangeListener(object : OnSeekBarChangeListener {
            override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
                filterAdjuster?.adjust(progress) // 调整参数
                gpuImageView.requestRender()    // 触发重新渲染
            }
            override fun onStartTrackingTouch(seekBar: SeekBar?) {}
            override fun onStopTrackingTouch(seekBar: SeekBar?) {}
        })

        // 2. 选择滤镜按钮:弹出滤镜选择弹窗
        findViewById<View>(R.id.button_choose_filter).setOnClickListener {
            GPUImageFilterTools.showDialog(this) { filter ->
                switchFilterTo(filter) // 切换滤镜
                gpuImageView.requestRender()
            }
        }

        // 3. 保存图片按钮:保存处理后的图片
        findViewById<View>(R.id.button_save).setOnClickListener { saveImage() }

        // 4. 启动相册选择器
        startPhotoPicker()
    }
}

核心变量说明:

  • GPUImageView:库提供的自定义View,封装了OpenGL上下文、纹理渲染、滤镜应用等核心逻辑;
  • FilterAdjuster:Sample封装的工具类,适配不同滤镜的参数范围(如0-100的SeekBar进度映射为滤镜所需的0.0-2.0对比度);
  • SeekBar:用于实时调整滤镜参数,进度变化时触发requestRender()重新渲染。

2. 启动相册选择器

kotlin 复制代码
private fun startPhotoPicker() {
    val photoPickerIntent = Intent(Intent.ACTION_PICK)
    photoPickerIntent.type = "image\/*" // 选择所有图片类型
    startActivityForResult(photoPickerIntent, REQUEST_PICK_IMAGE)
}

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    when (requestCode) {
        REQUEST_PICK_IMAGE -> if (resultCode == RESULT_OK) {
            // 相册选图成功:加载图片到GPUImageView
            gpuImageView.setImage(data!!.data)
        } else {
            finish() // 取消选图则关闭页面
        }
        else -> super.onActivityResult(requestCode, resultCode, data)
    }
}
  • 通过Intent.ACTION_PICK调用系统相册,返回图片Uri;
  • 核心API:gpuImageView.setImage(Uri) ------ 加载图片并渲染到View(注意:该方法内部会处理图片解码,建议在子线程调用,Sample未做优化,实际开发需补充)。

滤镜切换与参数调整核心逻辑

1. 滤镜选择工具:GPUImageFilterTools

GPUImageFilterTools是Sample封装的滤镜管理工具,核心功能:

  • 封装所有支持的滤镜列表(如Contrast、Sepia、Sobel Edge Detection等);
  • 弹出AlertDialog供用户选择滤镜;
  • 根据选择的类型创建对应滤镜实例(如GPUImageSepiaToneFilter())。

关键代码片段:

kotlin 复制代码
object GPUImageFilterTools {
    fun showDialog(context: Context, listener: (filter: GPUImageFilter) -> Unit) {
        val filters = FilterList().apply {
            addFilter("Contrast", FilterType.CONTRAST)
            addFilter("Sepia", FilterType.SEPIA)
            addFilter("Sobel Edge Detection", FilterType.SOBEL_EDGE_DETECTION)
            // 更多滤镜...
        }
        val builder = AlertDialog.Builder(context)
        builder.setTitle("Choose a filter")
        builder.setItems(filters.names.toTypedArray()) { _, item ->
            // 创建选中的滤镜实例并回调
            listener(createFilterForType(context, filters.filters[item]))
        }
        builder.create().show()
    }

    // 根据类型创建滤镜实例
    private fun createFilterForType(context: Context, type: FilterType): GPUImageFilter {
        return when (type) {
            FilterType.CONTRAST -> GPUImageContrastFilter(2.0f)
            FilterType.SEPIA -> GPUImageSepiaToneFilter()
            FilterType.SOBEL_EDGE_DETECTION -> GPUImageSobelEdgeDetectionFilter()
            // 其他滤镜实例化逻辑...
        }
    }
}

2. 切换滤镜:switchFilterTo

kotlin 复制代码
private fun switchFilterTo(filter: GPUImageFilter) {
    // 仅当滤镜类型变化时切换(避免重复创建)
    if (gpuImageView.filter == null || gpuImageView.filter.javaClass != filter.javaClass) {
        gpuImageView.filter = filter // 应用新滤镜
        filterAdjuster = FilterAdjuster(filter) // 绑定参数调整器
        // 判断滤镜是否支持参数调整(如Invert反转滤镜无参数,隐藏SeekBar)
        if (filterAdjuster!!.canAdjust()) {
            seekBar.visibility = View.VISIBLE
            filterAdjuster!!.adjust(seekBar.progress) // 初始化参数
        } else {
            seekBar.visibility = View.GONE
        }
    }
}

核心逻辑:

  • 滤镜切换核心API:gpuImageView.filter = filter
  • FilterAdjuster.canAdjust():判断滤镜是否有可调整参数(如Sepia有浓度参数,Invert无),动态控制SeekBar显隐;
  • 避免重复创建:仅当滤镜类型变化时才重新绑定调整器,优化性能。

图片保存逻辑

kotlin 复制代码
private fun saveImage() {
    val fileName = System.currentTimeMillis().toString() + ".jpg"
    // 保存图片到相册:GPUImage/Sample目录下
    gpuImageView.saveToPictures("GPUImage", fileName) { uri ->
        Toast.makeText(this, "Saved: " + uri.toString(), Toast.LENGTH_SHORT).show()
    }
}

核心API:gpuImageView.saveToPictures(folderName, fileName, callback)

  • 内部逻辑:触发OpenGL渲染,将渲染后的纹理数据编码为JPEG,保存到DCIM/GPUImage/目录;
  • 回调参数uri:保存后的图片Uri,可用于预览或分享;
  • 注意:Android 10+需适配Scoped Storage,Sample的saveToPictures已兼容,但需确保应用有存储权限。

关键细节与扩展点

1. 性能优化点
  • gpuImageView.setImage(Uri):内部在主线程解码图片,大图片会导致ANR,建议封装到子线程:

    kotlin 复制代码
    lifecycleScope.launch(Dispatchers.IO) {
        gpuImageView.setImage(uri)
        withContext(Dispatchers.Main) {
            gpuImageView.requestRender()
        }
    }
  • requestRender():仅在参数变化时触发,避免频繁渲染(Sample已做到)。

2. FilterAdjuster核心原理

FilterAdjuster封装了不同滤镜的参数范围映射,例如对比度滤镜的调整逻辑:

kotlin 复制代码
class FilterAdjuster(private val filter: GPUImageFilter) {
    fun adjust(progress: Int) {
        when (filter) {
            is GPUImageContrastFilter -> {
                // 将0-100的进度映射为0.0-4.0的对比度(默认1.0)
                val contrast = progress / 50.0f
                filter.setContrast(contrast)
            }
            // 其他滤镜的参数映射逻辑...
        }
    }
}

CameraActivity

CameraActivity是Android-GPUImage Sample中「相机实时滤镜」的核心页面,相比静态图片编辑,该场景需解决「相机适配、实时预览、旋转矫正、滤镜渲染」四大核心问题。

CameraActivity的核心业务流程:

复制代码
页面初始化 → 相机适配(Camera1/Camera2)→ 绑定GPUImageView → 
相机预览帧回调 → 实时渲染滤镜 → 滤镜切换/参数调整 → 
拍照保存/切换前后置相机

核心变量与页面初始化

1. 核心变量定义
kotlin 复制代码
class CameraActivity : AppCompatActivity() {
    // 核心渲染视图:实时渲染相机预览帧
    private val gpuImageView: GPUImageView by lazy { findViewById<GPUImageView>(R.id.surfaceView) }
    // 滤镜参数调整SeekBar
    private val seekBar: SeekBar by lazy { findViewById<SeekBar>(R.id.seekBar) }
    // 相机适配器:根据系统版本选择Camera1/Camera2
    private val cameraLoader: CameraLoader by lazy {
        if (Build.VERSION.SDK_INT < 21) {
            Camera1Loader(this) // Android 5.0以下
        } else {
            Camera2Loader(this) // Android 5.0及以上
        }
    }
    // 滤镜参数调整器
    private var filterAdjuster: FilterAdjuster? = null
    // ...
}

核心设计亮点:

  • CameraLoader抽象层:解耦Camera1和Camera2 API差异,上层无需关注具体相机实现;
  • GPUImageView:配置为「连续渲染模式」,适配实时预览场景。
2. 页面初始化核心逻辑
kotlin 复制代码
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_camera)

    // 1. SeekBar参数调整监听(同GalleryActivity)
    seekBar.setOnSeekBarChangeListener(object : OnSeekBarChangeListener {
        override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
            filterAdjuster?.adjust(progress)
        }
        override fun onStartTrackingTouch(seekBar: SeekBar) {}
        override fun onStopTrackingTouch(seekBar: SeekBar) {}
    })

    // 2. 选择滤镜按钮(同GalleryActivity)
    findViewById<View>(R.id.button_choose_filter).setOnClickListener {
        GPUImageFilterTools.showDialog(this) { filter -> switchFilterTo(filter) }
    }

    // 3. 拍照按钮:保存当前预览帧
    findViewById<View>(R.id.button_capture).setOnClickListener {
        saveSnapshot()
    }

    // 4. 切换前后置相机按钮
    findViewById<View>(R.id.img_switch_camera).run {
        if (!cameraLoader.hasMultipleCamera()) {
            visibility = View.GONE // 无多相机则隐藏
        }
        setOnClickListener {
            cameraLoader.switchCamera() // 切换相机
            // 矫正预览旋转角度
            gpuImageView.setRotation(getRotation(cameraLoader.getCameraOrientation()))
        }
    }

    // 5. 相机预览帧回调:将相机数据传入GPUImageView
    cameraLoader.setOnPreviewFrameListener { data, width, height ->
        gpuImageView.updatePreviewFrame(data, width, height)
    }

    // 6. GPUImageView配置:旋转矫正 + 连续渲染
    gpuImageView.setRotation(getRotation(cameraLoader.getCameraOrientation()))
    gpuImageView.setRenderMode(GPUImageView.RENDERMODE_CONTINUOUSLY)
}

核心配置说明:

  • RENDERMODE_CONTINUOUSLY:GPUImageView的连续渲染模式(实时滤镜必需,静态图片用RENDERMODE_WHEN_DIRTY);
  • setRotation:矫正相机预览方向(不同相机/设备的预览角度不同,需适配);
  • setOnPreviewFrameListener:相机预览帧回调,将YUV数据传入GPUImageView进行渲染。

相机适配核心:CameraLoader抽象层

Android系统从5.0(API 21)开始引入Camera2 API,替代老旧的Camera1 API。

Sample通过CameraLoader抽象层解耦两者差异:

1. CameraLoader接口定义(核心方法)
kotlin 复制代码
interface CameraLoader {
    fun onResume(width: Int, height: Int) // 相机启动
    fun onPause() // 相机关闭
    fun switchCamera() // 切换前后置
    fun hasMultipleCamera(): Boolean // 是否有多个相机(前后置)
    fun getCameraOrientation(): Int // 获取相机预览方向
    fun setOnPreviewFrameListener(listener: (data: ByteArray, width: Int, height: Int) -> Unit)
}
2. Camera2Loader核心逻辑(关键片段)
kotlin 复制代码
override fun onConfigured(session: CameraCaptureSession) {
    cameraInstance ?: return
    captureSession = session
    val builder = cameraInstance!!.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)
    builder.addTarget(imageReader!!.surface) // 绑定预览Surface
    try {
        // 启动连续预览
        session.setRepeatingRequest(builder.build(), null, null)
    } catch (e: CameraAccessException) {
        Log.e(TAG, "Failed to start camera preview", e)
    }
}
  • Camera2通过CameraCaptureSession实现连续预览,将预览数据输出到ImageReader的Surface;
  • ImageReader监听预览帧,将YUV数据回调给CameraLoader,最终传入gpuImageView.updatePreviewFrame
3. 相机生命周期管理
kotlin 复制代码
override fun onResume() {
    super.onResume()
    // 等待GPUImageView布局完成后启动相机(避免尺寸未初始化导致预览变形)
    gpuImageView.doOnLayout {
        cameraLoader.onResume(it.width, it.height)
    }
}

override fun onPause() {
    cameraLoader.onPause() // 停止相机预览,释放资源
    super.onPause()
}

核心细节:

  • doOnLayout:通过ViewExt.kt的工具方法,确保GPUImageView尺寸初始化完成后再启动相机,避免预览比例错误;
  • 生命周期对齐:onResume启动相机,onPause释放相机,符合Android最佳实践。

实时滤镜渲染核心逻辑

1. 预览帧更新
kotlin 复制代码
cameraLoader.setOnPreviewFrameListener { data, width, height ->
    gpuImageView.updatePreviewFrame(data, width, height)
}
  • updatePreviewFrame:将相机输出的YUV数据转换为OpenGL纹理,结合当前滤镜进行渲染;
  • 内部逻辑:YUV → RGB转换(通过原生库优化)→ 绑定到GPU纹理 → 应用滤镜Shader → 渲染到GPUImageView。
2. 滤镜切换(与GalleryActivity的差异)
kotlin 复制代码
private fun switchFilterTo(filter: GPUImageFilter) {
    if (gpuImageView.filter == null || gpuImageView.filter!!.javaClass != filter.javaClass) {
        gpuImageView.filter = filter // 切换滤镜
        filterAdjuster = FilterAdjuster(filter)
        filterAdjuster?.adjust(seekBar.progress) // 调整参数
    }
}

与GalleryActivity的差异:

  • 无需调用requestRender():因为GPUImageView设置为RENDERMODE_CONTINUOUSLY,会自动连续渲染;
  • 实时性:滤镜切换/参数调整后,预览画面立即更新,无延迟。
3. 拍照保存(快照)
kotlin 复制代码
private fun saveSnapshot() {
    val folderName = "GPUImage"
    val fileName = System.currentTimeMillis().toString() + ".jpg"
    // 保存当前渲染帧为图片
    gpuImageView.saveToPictures(folderName, fileName) {
        Toast.makeText(this, "$folderName/$fileName saved", Toast.LENGTH_SHORT).show()
    }
}
  • saveToPictures在相机场景中,会截取当前GPUImageView的渲染帧(带滤镜),保存为图片;
  • 逻辑与GalleryActivity一致,但数据源是相机预览帧而非静态图片。

旋转适配核心逻辑

kotlin 复制代码
private fun getRotation(orientation: Int): Rotation {
    return when (orientation) {
        90 -> Rotation.ROTATION_90
        180 -> Rotation.ROTATION_180
        270 -> Rotation.ROTATION_270
        else -> Rotation.NORMAL
    }
}
  • 相机预览的原始方向可能是90/180/270度(不同设备/相机不同);
  • gpuImageView.setRotation:通过旋转纹理坐标,矫正预览画面方向,避免画面倒置。

关键扩展与优化点

1. 性能优化
  • 滤镜选择:避免频繁创建滤镜实例(Sample已做判断,仅类型变化时创建);
  • 渲染模式:非实时场景(如拍照后)可切换为RENDERMODE_WHEN_DIRTY,降低GPU功耗;
  • 相机分辨率:CameraLoader可添加分辨率选择逻辑,避免高分辨率导致的性能卡顿。
2. 兼容性适配
  • Android 13+权限:CAMERA权限无需再申请WRITE_EXTERNAL_STORAGE,Sample需补充适配;
  • 相机2兼容性:部分设备Camera2实现有bug,可降级为Camera1;
  • 纹理类型:GPUImageView支持SurfaceView/TextureView,可通过XML属性gpuimage_surface_type配置,TextureView适配性更好但性能略低。
相关推荐
2501_915909062 小时前
Charles 抓不到包怎么办?iOS 调试过程中如何判断请求路径
android·ios·小程序·https·uni-app·iphone·webview
2501_916007472 小时前
iOS和iPadOS文件管理系统全面解析与使用指南
android·ios·小程序·https·uni-app·iphone·webview
廋到被风吹走3 小时前
【数据库】【MySQL】分区表深度解析:架构设计与大数据归档实践
android·数据库·mysql
峥嵘life3 小时前
Android16 EDLA中GMS导入和更新
android·linux·学习
Huanzhi_Lin3 小时前
验证apk签名
android·apk签名·apksigner
独自破碎E4 小时前
【大顶堆+小顶堆】数据流中的中位数
android
双翌视觉4 小时前
机器视觉引导如何实现机械手的动态抓取
人工智能·数码相机
得一录4 小时前
Android AIDL 在智能体和IOT设备中的使用
android·人工智能·物联网·aigc
独行soc5 小时前
2026年渗透测试面试题总结-1(题目+回答)
android·开发语言·网络·安全·web安全·渗透测试·php