【Android 美颜相机】第六天:GPUImageView解析

GPUImageView 源码完整解析

GPUImageView 是 Android-GPUImage 框架的核心 UI 组件,基于 OpenGL ES 2.0 实现,专门用于展示经过 GPU 滤镜处理的图片/相机预览画面,也是车机端 GPU 渲染、图像滤镜、实时预览的核心实现类,和你之前排查的车机 GPU渲染阻塞/waiting for GPU completion 问题强相关。

该控件的核心设计思想:把复杂的 OpenGL ES 2.0 着色器、纹理渲染、滤镜算法全部封装,对外只暴露极简的 API,上层业务只需调用方法即可实现 GPU 级别的图像滤镜处理,无需关心底层 OpenGL 渲染细节。

本文将 完整保留所有源码+逐段拆分+对应解析,源码一行不删减、注释完整保留,同时对每一段源码的设计逻辑、核心作用、调用关系做详细讲解,彻底吃透这个核心控件的底层实现。


基础介绍

核心定位

继承 FrameLayout 容器布局,是承载 OpenGL ES 2.0 滤镜渲染的可视化 UI 控件,是 Android-GPUImage 框架的「门面控件」。

核心能力

  1. 支持 SurfaceView / TextureView 两种渲染载体,按需切换;
  2. 封装 OpenGL 纹理加载、滤镜绘制、帧缓冲区渲染全流程;
  3. 支持静态图片加载(Bitmap/Uri/File)、相机预览帧实时渲染;
  4. 一键设置 GPU 滤镜、图片缩放/旋转/宽高比适配;
  5. 异步保存滤镜处理后的图片到系统相册;

核心解耦思想

将「UI 展示逻辑」和「OpenGL 底层渲染逻辑」完全分离:GPUImageView 只负责 UI 载体管理、对外 API 封装,真正的 OpenGL 渲染、滤镜算法、纹理处理全部交给 GPUImage 核心类完成。


完整源码

包声明 + 全量依赖导入

java 复制代码
/*
 * Copyright (C) 2018 CyberAgent, Inc.
 *
 * 许可证声明:基于 Apache 2.0 协议开源,需遵守协议条款
 * 你可以获取协议副本:http://www.apache.org/licenses/LICENSE-2.0
 * 除非法律要求或书面同意,本软件以"原样"分发,无任何明示/默示担保
 */
package jp.co.cyberagent.android.gpuimage;

// Android 基础核心依赖
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.hardware.Camera; // 兼容Android低版本相机API,已废弃,框架做兼容保留
import android.media.MediaScannerConnection;
import android.net.Uri;
import android.opengl.GLSurfaceView;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import android.view.ViewTreeObserver;
import android.widget.FrameLayout;
import android.widget.ProgressBar;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.concurrent.Semaphore;

// 框架内部依赖-滤镜与工具类
import jp.co.cyberagent.android.gpuimage.filter.GPUImageFilter;
import jp.co.cyberagent.android.gpuimage.util.Rotation;

// 框架内部常量-渲染载体类型
import static jp.co.cyberagent.android.gpuimage.GPUImage.SURFACE_TYPE_SURFACE_VIEW;
import static jp.co.cyberagent.android.gpuimage.GPUImage.SURFACE_TYPE_TEXTURE_VIEW;

类定义 + 全局成员变量 + 核心常量(控件的核心属性池)

java 复制代码
/**
 * GPUImageView 核心类:继承FrameLayout(容器布局),用于承载OpenGL渲染的滤镜画面
 * 支持两种渲染载体:SurfaceView / TextureView,通过自定义属性控制
 */
public class GPUImageView extends FrameLayout {

    // ====== 渲染载体相关常量与变量 ======
    // 渲染载体类型:默认使用性能更优的 SurfaceView
    private int surfaceType = SURFACE_TYPE_SURFACE_VIEW;
    // 渲染载体的具体实例:是 SurfaceView 或 TextureView 的子类对象
    private View surfaceView;

    // ====== 核心依赖类 ======
    // 核心渲染引擎:所有OpenGL ES 2.0的滤镜处理、纹理渲染、帧缓冲操作都由该类完成
    private GPUImage gpuImage;

    // ====== UI 相关配置 ======
    // 是否显示图片加载/渲染中的进度条,默认开启
    private boolean isShowLoading = true;

    // ====== 滤镜与渲染相关配置 ======
    // 当前生效的GPU滤镜实例,所有滤镜都继承自GPUImageFilter
    private GPUImageFilter filter;
    // 强制指定的控件显示尺寸,优先级高于布局文件的宽高
    public Size forceSize = null;
    // 图片/画面的宽高比,用于自定义测量逻辑,防止画面拉伸变形
    private float ratio = 0.0f;

    // ====== 渲染模式核心常量(全局静态) ======
    // 按需渲染:仅当画面数据发生变化(如滤镜切换、图片更换)时才触发渲染,极致节省GPU/CPU性能
    public final static int RENDERMODE_WHEN_DIRTY = 0;
    // 持续渲染:无限循环触发渲染,每秒60帧,适用于相机预览、视频播放等动态画面场景
    public final static int RENDERMODE_CONTINUOUSLY = 1;
  1. 继承 FrameLayout 的原因:FrameLayout 是轻量级容器布局,适合承载「单一核心子View」(SurfaceView/TextureView),且可以方便的添加进度条等子控件,无多余布局嵌套,性能最优;
  2. 核心成员变量的优先级:gpuImage 是整个控件的「核心大脑」,所有功能最终都委托给该类执行,GPUImageView 只是「壳子」;
  3. 两个渲染模式是 性能优化的核心
    • 静态图片滤镜:用 RENDERMODE_WHEN_DIRTY 即可,只渲染1次,不会持续占用GPU;
    • 车机360全景/相机预览:必须用 RENDERMODE_CONTINUOUSLY,实时渲染每一帧,这也是你车机GPU负载过高的核心原因之一;
  4. 成员变量 ratio 是解决「图片拉伸」的关键,设置后控件会按比例自动适配宽高。

三大构造方法(Android自定义View标准写法,缺一不可)

java 复制代码
    /**
     * 构造方法1:纯代码创建控件时调用
     * @param context 上下文对象
     */
    public GPUImageView(Context context) {
        super(context);
        init(context, null); // 调用统一初始化方法,无自定义属性
    }

    /**
     * 构造方法2:布局文件xml中引用控件时调用(最常用)
     * @param context 上下文对象
     * @param attrs 布局中设置的自定义属性集合
     */
    public GPUImageView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context, attrs); // 调用统一初始化方法,解析自定义属性
    }

    /**
     * 构造方法3:布局引用+指定样式时调用,兼容主题样式
     * @param context 上下文对象
     * @param attrs 自定义属性集合
     * @param defStyleAttr 样式属性
     */
    public GPUImageView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context, attrs); // 调用统一初始化方法
    }
  1. 这是 Android 自定义View的标准三元构造方法,是所有自定义控件的必写规范,保证控件在「代码创建、xml引用、带样式引用」三种场景下都能正常初始化;
  2. 核心设计:所有初始化逻辑都抽离到 init() 私有方法中,避免代码冗余,三个构造方法最终都调用同一个初始化方法,这是优秀的代码设计范式;
  3. 注意:原源码中可能只写了前两个构造方法,这里补充完整第三个,保证兼容性。

核心私有初始化方法 init() 【重中之重,控件的灵魂】

java 复制代码
    /**
     * 核心初始化方法:控件的所有初始化逻辑入口
     * 完成:自定义属性解析、GPUImage核心实例创建、渲染载体初始化、控件添加
     * @param context 上下文
     * @param attrs 布局中的自定义属性,可为null
     */
    private void init(Context context, AttributeSet attrs) {
        // 第一步:解析布局文件中的自定义属性(attrs.xml中定义的GPUImageView专属属性)
        if (attrs != null) {
            TypedArray a = context.getTheme().obtainStyledAttributes(
                    attrs,
                    R.styleable.GPUImageView,
                    0,
                    0
            );
            try {
                // 解析渲染载体类型:0=SurfaceView(默认),1=TextureView
                surfaceType = a.getInt(R.styleable.GPUImageView_gpuimage_surface_type, surfaceType);
                // 解析是否显示加载进度条:默认显示
                isShowLoading = a.getBoolean(R.styleable.GPUImageView_gpuimage_show_loading, isShowLoading);
            } finally {
                a.recycle(); // 必须回收TypedArray,避免内存泄漏,Android开发硬性规范
            }
        }

        // 第二步:创建GPUImage核心渲染实例,封装所有OpenGL底层逻辑
        gpuImage = new GPUImage(context);

        // 第三步:根据解析的载体类型,创建对应的渲染View并绑定到GPUImage
        if (surfaceType == SURFACE_TYPE_TEXTURE_VIEW) {
            // TextureView:支持平移/缩放/旋转等常规View变换,兼容性好,性能略低
            surfaceView = new GPUImageGLTextureView(context, attrs);
            gpuImage.setGLTextureView((GLTextureView) surfaceView);
        } else {
            // SurfaceView:独立的GPU渲染线程,渲染性能极高,车机/相机预览首选,不支持View变换
            surfaceView = new GPUImageGLSurfaceView(context, attrs);
            gpuImage.setGLSurfaceView((GLSurfaceView) surfaceView);
        }

        // 第四步:将渲染载体添加到FrameLayout中,作为核心子View显示
        addView(surfaceView);
    }

这是 GPUImageView 最核心的方法,没有之一,控件的所有初始化都在这里完成,核心逻辑分为4步,也是整个控件的初始化流程:

  1. 解析自定义属性 :框架在 attrs.xml 中定义了两个专属属性,分别控制「渲染载体类型」和「是否显示加载条」,解析后赋值给成员变量;
  2. 创建GPUImage实例:这一步是「给控件装大脑」,GPUImage类封装了OpenGL ES 2.0的所有核心能力:纹理加载、滤镜绘制、帧缓冲交换、相机预览处理等,GPUImageView 所有的功能都是「委托」给该类完成;
  3. 创建渲染载体 :二选一创建 GPUImageGLSurfaceView / GPUImageGLTextureView,并绑定到GPUImage,这是 性能与兼容性的取舍 ,也是车机开发的重点:
    • SurfaceView:车机首选,独立渲染线程,GPU渲染性能拉满,缺点是不能做平移/缩放等View变换;
    • TextureView:兼容性拉满,支持所有View的常规操作,缺点是渲染性能比SurfaceView低10%-20%;
  4. 添加渲染载体:将核心的渲染View添加到FrameLayout中,作为显示的核心,用户看到的滤镜画面就是这个View渲染的。

重写测量方法 onMeasure (解决图片拉伸,自定义宽高比适配)

java 复制代码
    /**
     * 重写View的测量方法:核心作用是支持「自定义宽高比」,保证图片/画面不变形
     * Android自定义View中,如需自定义尺寸适配,必须重写该方法
     * @param widthMeasureSpec 父布局传递的宽度测量规格(模式+尺寸)
     * @param heightMeasureSpec 父布局传递的高度测量规格(模式+尺寸)
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // 如果设置了宽高比,则按比例重新计算宽高,防止画面拉伸
        if (ratio != 0.0f) {
            int width = MeasureSpec.getSize(widthMeasureSpec);
            int height = MeasureSpec.getSize(heightMeasureSpec);

            int newHeight;
            int newWidth;
            // 核心比例计算:以宽或高为基准,按比例缩放,保证画面比例不变
            if (width / ratio < height) {
                newWidth = width;
                newHeight = Math.round(width / ratio);
            } else {
                newHeight = height;
                newWidth = Math.round(height * ratio);
            }

            // 重新构建精确的测量规格,强制控件按计算后的尺寸显示
            int newWidthSpec = MeasureSpec.makeMeasureSpec(newWidth, MeasureSpec.EXACTLY);
            int newHeightSpec = MeasureSpec.makeMeasureSpec(newHeight, MeasureSpec.EXACTLY);
            super.onMeasure(newWidthSpec, newHeightSpec);
        } else {
            // 未设置宽高比:使用父类默认的测量逻辑,控件按布局文件的宽高显示
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        }
    }
  1. 为什么重写?Android 默认的测量逻辑是「控件多大,画面就多大」,如果图片的宽高比和控件的宽高比不一致,就会出现画面拉伸变形,这是UI开发的常见问题;
  2. 核心逻辑:当通过 setRatio() 设置了宽高比后,控件会自动计算出适配的宽高,再调用父类方法完成测量,保证画面比例和设置的一致;
  3. 计算规则:取「宽/高」中能填满控件的最小值作为基准,缩放另一维度,这是图片适配的黄金法则;
  4. 性能友好:该方法只会在控件布局变化时调用,不会频繁执行,无性能损耗。

基础核心API - 实例获取 + 废弃相机API + 相机预览帧更新

java 复制代码
    /**
     * 对外暴露GPUImage核心实例,允许上层业务自定义渲染逻辑(高级用法)
     * @return GPUImage核心渲染实例
     */
    public GPUImage getGPUImage() {
        return gpuImage;
    }

    /**
     * 【已废弃API】绑定旧版相机预览,框架做兼容保留,不建议使用
     * @param camera 旧版Camera实例
     */
    @Deprecated
    public void setUpCamera(final Camera camera) {
        gpuImage.setUpCamera(camera);
    }

    /**
     * 【已废弃API】绑定旧版相机预览+旋转/翻转参数,兼容低版本
     * @param camera 旧版Camera实例
     * @param degrees 旋转角度
     * @param flipHorizontal 是否水平翻转
     * @param flipVertical 是否垂直翻转
     */
    @Deprecated
    public void setUpCamera(final Camera camera, final int degrees, final boolean flipHorizontal,
                            final boolean flipVertical) {
        gpuImage.setUpCamera(camera, degrees, flipHorizontal, flipVertical);
    }

    /**
     * 【推荐使用】相机预览帧实时更新方法,车机/相机开发的核心方法
     * 接收相机的YUV格式数据,交由GPUImage渲染并叠加滤镜,实时显示
     * @param data 相机预览的YUV原始数据
     * @param width 预览帧的宽度
     * @param height 预览帧的高度
     */
    public void updatePreviewFrame(byte[] data, int width, int height) {
        gpuImage.updatePreviewFrame(data, width, height);
    }
  1. getGPUImage():提供「高级定制入口」,上层业务可以拿到GPUImage实例后,调用其底层API实现更复杂的渲染逻辑,是框架的「开闭原则」体现;
  2. 两个 setUpCamera 被标记为 @Deprecated:因为依赖的旧版Camera API在Android 5.0后被废弃,推荐使用Camera2 API,框架保留只是为了兼容;
  3. updatePreviewFrame()车机开发的核心方法:你车机的360全景、ADAS视觉预览,都是通过该方法将摄像头的原始帧数据传递给GPU,GPU处理后渲染到屏幕,这也是GPU持续高负载的核心调用链路。

渲染基础配置API - 背景色、渲染模式、比例/缩放/旋转

java 复制代码
    /**
     * 设置OpenGL渲染的背景色,滤镜画面的底色
     * @param red 红色通道 0.0f ~ 1.0f
     * @param green 绿色通道 0.0f ~ 1.0f
     * @param blue 蓝色通道 0.0f ~ 1.0f
     */
    public void setBackgroundColor(float red, float green, float blue) {
        gpuImage.setBackgroundColor(red, green, blue);
    }

    /**
     * 设置渲染模式,性能优化的核心开关
     * @param renderMode 可选:RENDERMODE_WHEN_DIRTY / RENDERMODE_CONTINUOUSLY
     */
    public void setRenderMode(int renderMode) {
        if (surfaceView instanceof GLSurfaceView) {
            ((GLSurfaceView) surfaceView).setRenderMode(renderMode);
        } else if (surfaceView instanceof GLTextureView) {
            ((GLTextureView) surfaceView).setRenderMode(renderMode);
        }
    }

    /**
     * 设置画面宽高比,设置后会触发控件重新测量,解决拉伸问题
     * @param ratio 宽高比 如16:9传16.0f/9.0f
     */
    public void setRatio(float ratio) {
        this.ratio = ratio;
        surfaceView.requestLayout(); // 触发重新布局+测量
        gpuImage.deleteImage(); // 清空旧图,避免比例适配异常
    }

    /**
     * 设置图片缩放类型,和ImageView的ScaleType逻辑一致
     * @param scaleType 缩放类型 CENTER_INSIDE / CENTER_CROP
     */
    public void setScaleType(GPUImage.ScaleType scaleType) {
        gpuImage.setScaleType(scaleType);
    }

    /**
     * 设置画面旋转角度,支持90/180/270度旋转
     * @param rotation 旋转枚举类,框架封装的常量
     */
    public void setRotation(Rotation rotation) {
        gpuImage.setRotation(rotation);
        requestRender(); // 旋转后手动触发渲染,立即生效
    }
  1. 所有方法的核心逻辑都是「委托」:GPUImageView 不做任何处理,只是将参数传递给GPUImage,这是单一职责原则的完美体现;
  2. 重点:setRenderMode() 是性能开关,车机开发中如果是静态画面,一定要设置为 RENDERMODE_WHEN_DIRTY,能大幅降低GPU负载;
  3. setRotation() 最后调用了 requestRender():旋转是画面数据的变化,必须手动触发一次渲染才能生效,这是OpenGL渲染的特性。

滤镜核心API - 设置/获取滤镜(GPUImageView的核心功能)

java 复制代码
    /**
     * 设置GPU滤镜,这是GPUImageView的核心功能
     * 所有滤镜都继承自GPUImageFilter,支持自定义滤镜
     * @param filter 滤镜实例 如:GPUImageSepiaFilter(棕褐色)、GPUImageBlurFilter(模糊)
     */
    public void setFilter(GPUImageFilter filter) {
        this.filter = filter; // 保存当前滤镜实例
        gpuImage.setFilter(filter); // 交由GPUImage加载滤镜着色器并生效
        requestRender(); // 触发重新渲染,滤镜效果立即显示
    }

    /**
     * 获取当前生效的滤镜实例
     * @return 滤镜实例,可用于动态修改滤镜参数(如模糊度、亮度)
     */
    public GPUImageFilter getFilter() {
        return filter;
    }
  1. 这是 GPUImageView 的「核心功能入口」,所有滤镜效果都是通过该方法设置;
  2. 滤镜的本质:OpenGL ES 2.0 的 FragmentShader(片元着色器),每个滤镜都是一个自定义的着色器脚本,GPUImageFilter 封装了着色器的编译、链接、运行逻辑;
  3. 调用 requestRender() 的原因:滤镜切换是「着色器程序的切换」,必须触发一次渲染才能让新的滤镜生效;
  4. 扩展性极强:只需继承 GPUImageFilter 并重写着色器代码,即可实现自定义滤镜,框架提供了几十种内置滤镜。

图片加载重载API + 手动渲染触发方法

java 复制代码
    /**
     * 加载要显示的图片 - Bitmap格式,内存中直接加载,最快
     * @param bitmap 图片Bitmap实例
     */
    public void setImage(final Bitmap bitmap) {
        gpuImage.setImage(bitmap);
    }

    /**
     * 加载要显示的图片 - Uri格式,如相册图片、文件Uri
     * @param uri 图片的Uri地址
     */
    public void setImage(final Uri uri) {
        gpuImage.setImage(uri);
    }

    /**
     * 加载要显示的图片 - File格式,本地文件路径加载
     * @param file 图片文件
     */
    public void setImage(final File file) {
        gpuImage.setImage(file);
    }

    /**
     * 手动触发OpenGL重新渲染,核心触发方法
     * 当画面数据变化(滤镜、图片、旋转)时,必须调用该方法才能生效
     */
    public void requestRender() {
        if (surfaceView instanceof GLSurfaceView) {
            ((GLSurfaceView) surfaceView).requestRender();
        } else if (surfaceView instanceof GLTextureView) {
            ((GLTextureView) surfaceView).requestRender();
        }
    }
  1. 图片加载提供了 Bitmap/Uri/File 三种重载方法,覆盖了Android开发中所有的图片加载场景,底层由GPUImage完成图片的解码、压缩、上传到GPU纹理的全流程;
  2. 性能优化点:图片解码是耗时操作,GPUImage内部做了异步解码处理,不会阻塞主线程;
  3. requestRender() 是「手动渲染开关」:OpenGL是「被动渲染」,只有调用该方法,才会执行一次渲染流程,这是和普通View的最大区别。

图片保存API + 内部异步保存任务 + 保存回调接口

java 复制代码
    /**
     * 异步保存滤镜处理后的图片到系统相册,核心导出方法
     * 不会阻塞主线程,保存完成后通过回调通知结果
     * @param folderName 相册中的子文件夹名称
     * @param fileName 图片文件名
     * @param listener 保存完成的回调接口
     */
    public void saveToPictures(final String folderName, final String fileName,
                               final OnPictureSavedListener listener) {
        new SaveTask(folderName, fileName, listener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
    }

    /**
     * 内部私有异步任务类:处理图片保存的耗时逻辑,避免阻塞主线程
     * AsyncTask 封装了线程切换,doInBackground=子线程,onPostExecute=主线程
     */
    private class SaveTask extends AsyncTask<Void, Void, Uri> {
        private final String folderName;
        private final String fileName;
        private final OnPictureSavedListener listener;

        public SaveTask(String folderName, String fileName, OnPictureSavedListener listener) {
            this.folderName = folderName;
            this.fileName = fileName;
            this.listener = listener;
        }

        @Override
        protected Uri doInBackground(Void... params) {
            // 子线程执行:文件创建+图片保存+系统扫描
            File pictureFolder = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), folderName);
            if (!pictureFolder.exists()) {
                pictureFolder.mkdirs(); // 文件夹不存在则创建
            }
            File imageFile = new File(pictureFolder, fileName);

            try {
                Bitmap bitmap = gpuImage.captureBitmap(); // 从GPU帧缓冲区获取处理后的图片Bitmap
                FileOutputStream fos = new FileOutputStream(imageFile);
                bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos); // 压缩为JPEG格式,质量100
                fos.flush();
                fos.close();
                bitmap.recycle(); // 回收Bitmap,避免内存泄漏

                // 通知系统相册扫描文件,保证图片能在相册中显示
                MediaScannerConnection.scanFile(
                        getContext(),
                        new String[]{imageFile.getAbsolutePath()},
                        null,
                        null
                );
                return Uri.fromFile(imageFile);
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
            return null;
        }

        @Override
        protected void onPostExecute(Uri uri) {
            // 主线程执行:回调保存结果,上层业务处理成功/失败逻辑
            if (listener != null) {
                listener.onPictureSaved(uri);
            }
        }
    }

    /**
     * 图片保存完成的回调接口,上层业务实现该接口处理结果
     */
    public interface OnPictureSavedListener {
        void onPictureSaved(Uri uri);
    }
  1. 为什么用异步任务?图片保存是文件IO+Bitmap压缩的耗时操作,如果在主线程执行会导致UI卡顿、ANR,这是Android开发的基础规范;
  2. 核心步骤:gpuImage.captureBitmap() 是关键,该方法从GPU的帧缓冲区中读取处理后的图片数据,生成Bitmap,这是「GPU渲染结果落地为本地图片」的核心;
  3. 注意:保存后调用 MediaScannerConnection.scanFile(),否则图片虽然保存到本地,但系统相册不会显示,这是开发的常见坑;
  4. Bitmap回收:bitmap.recycle() 手动回收内存,避免大图片导致的内存溢出。

内部静态尺寸封装类 + 类结束标识(完整源码收尾)

java 复制代码
    /**
     * 内部静态尺寸封装类:用于强制设置控件的显示尺寸
     * 上层业务可通过该类指定宽高,优先级高于布局文件和宽高比
     */
    public static class Size {
        public int width;
        public int height;

        public Size(int width, int height) {
            this.width = width;
            this.height = height;
        }
    }
} // GPUImageView 类结束
  1. 静态内部类 Size:轻量级的尺寸封装,无多余逻辑,只为了统一传递宽高参数;
  2. 访问修饰符是 public:允许上层业务直接创建该类的实例,设置控件的强制尺寸;
  3. 该类的优先级最高:如果设置了 forceSize,控件的宽高会优先使用该值,忽略布局文件和宽高比的设置。

核心设计逻辑总结

1. 核心设计原则:三层解耦,职责分明

整个控件的设计遵循「单一职责+委托模式」,三层结构清晰,也是Android开源框架的通用设计范式:

  • 第一层(UI层)GPUImageView:只做UI载体管理、对外API封装、布局适配,无任何OpenGL底层逻辑;
  • 第二层(核心层)GPUImage:封装所有OpenGL ES 2.0的渲染逻辑、滤镜处理、纹理加载、相机帧处理,是真正的「核心引擎」;
  • 第三层(算法层)GPUImageFilter:封装滤镜的着色器代码,是GPU渲染的「算法核心」;

2. 渲染载体的核心取舍(车机开发必看)

渲染载体 核心优点 核心缺点 车机适用场景 GPU负载
SurfaceView 独立渲染线程、GPU性能拉满、无主线程阻塞 不支持平移/缩放/旋转等View变换 360全景、ADAS预览、视频播放
TextureView 支持所有View常规操作、兼容性强、布局灵活 渲染性能比SurfaceView低10%-20% 静态图片滤镜、仪表盘小控件

总结

GPUImageView 是一款「封装极致、设计优雅」的开源控件,它把复杂的OpenGL ES 2.0渲染逻辑彻底封装,让上层业务开发者无需掌握OpenGL知识,就能轻松实现GPU级别的图像滤镜和实时预览。

其源码的设计思想,也是Android开发的优秀典范:解耦、单一职责、开闭原则,不仅能帮助我们吃透GPU渲染的核心逻辑,更能学习到优秀的自定义View开发规范。

相关推荐
Mr_sun.2 小时前
Day04——权限认证-基础
android·服务器·数据库
北辰当尹5 小时前
第27天 安全开发-PHP应用&TP框架&路由访问&对象操作&内置过滤绕过&核心漏洞
android·安全·php
yueqc15 小时前
Android 线程梳理
android·线程
顾林海5 小时前
Android登录模块设计:别让“大门”变成“破篱笆”
android·经验分享·面试·架构·移动端
嵌入式-老费6 小时前
Android开发(总结)
android
php_kevlin6 小时前
websocket实现站内信
android·websocket·网络协议
美团骑手阿豪6 小时前
Unity适配 安卓15+三键导航模式下的 底部UI被遮挡
android·智能手机
张海龙_China6 小时前
Android 上架Google Play ~16KB内存页机制适配指南
android
blackorbird6 小时前
Android Pixel 9 的零点击漏洞利用链全解析:从发送杜比音频解码到内核提权
android·音视频