【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适配性更好但性能略低。
相关推荐
阿巴斯甜9 小时前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker10 小时前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq952711 小时前
Andorid Google 登录接入文档
android
黄林晴12 小时前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab1 天前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿1 天前
Android MediaPlayer 笔记
android
Jony_1 天前
Android 启动优化方案
android
阿巴斯甜1 天前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android
张小潇1 天前
AOSP15 Input专题InputReader源码分析
android
_小马快跑_1 天前
Kotlin | 协程调度器选择:何时用CoroutineScope配置,何时用launch指定?
android