Android drawFunctor 原理及应用详情

目录
  • 一. 背景
  • 二. drawFunctor 原理介绍
  • 三. 利用 drawFunctor 注入 GL 渲染
    • Android Functor 定义
    • Functor 设计
    • 在 View.onDraw () 中调度 functor
  • 四. 实践中遇到的问题
    • GL 状态保存&恢复
    • View变换处理
    • ContextLost
  • 五. 效果

一. 背景

蚂蚁 NativeCanvas 项目 Android 平台中使用了基于 TextureView 环境实现 GL 渲染的技术方案,而 TextureView 需使用与 Activity Window 独立的 GraphicBuffer,RenderThread 在上屏 TextureView 内容时需要将 GraphicBuffer 封装为 EGLImage 上传为纹理再渲染,内存占用较高。为降低内存占用,经仔细调研 Android 源码,发现其中存在一种称为 drawFunctor 的技术,用来将 WebView 合成后的内容同步到 Activity Window 内上屏。经过一番探索成功实现了基于 drawFunctor 实现 GL 注入 RenderThread 的功能,本文将介绍这是如何实现的。

二. drawFunctor 原理介绍

drawFunctor 是 Android 提供的一种在 RenderThread 渲染流程中插入执行代码机制,Android 框架是通过以下三步来实现这个机制的:

  • 在 UI 线程 View 绘制流程 onDraw 方法中,通过 RecordingCanvas.invoke 接口,将 functor 插入 DisplayList 中
  • 在 RenderThread 渲染 frame 时执行 DisplayList,判断如果是 functor 类型的 op,则保存当前部分 gl 状态
  • 在 RenderThread 中真正执行 functor 逻辑,执行完成后恢复 gl 状态并继续

目前只能通过 View.OnDraw 来注入 functor,因此对于非 attached 的 view 是无法实现注入的。Functor 对具体要执行的代码并未限制,理论上可以插入任何代码的,比如插入一些统计、性能检测之类代码。系统为了 functor 不影响当前 gl context,执行 functor 前后进行了基本的状态保存和恢复工作。

另外,如果 View 设置了使用 HardwareLayer, 则 RenderThread 会单独渲染此 View,具体做法是为 Layer 生成一块 FBO,View 的内容渲染到此 FBO 上,然后再将 FBO 以 View 在 hierachy 上的变换绘制 Activity Window Buffer 上。 对 drawFunctor 影响的是, 会切换到 View 对应的 FBO 下执行 functor, 即 functor 执行的结果是写入到 FBO 而不是 Window Buffer。

三. 利用 drawFunctor 注入 GL 渲染

根据上文介绍,通过 drawFunctor 可以在 RenderThread 中注入任何代码,那么也一定可以注入 OpenGL API 来进行渲染。我们知道 OpenGL API 需要执行 EGL Context 上,所以就有两种策略:一种是利用 RenderThread 默认的 EGL Context 环境,一种是创建与 RenderThread EGL Context share 的 EGL Context。本文重点介绍第一种,第二种方法大同小异。

Android Functor 定义

首先找到 Android 源码中 Functor 的头文件定义并引入项目:

namespace android {
    class Functor {
    public:
    Functor() {}
    virtual ~Functor() {}
    virtual int operator()(int /*what*/, void * /*data*/) { return 0; }
    };
}

RenderThread 执行 Functor 时将调用 operator()方法,what 表示 functor 的操作类型,常见的有同步和绘制, 而 data 是 RenderThread 执行 functor 时传入的参数,根据源码发现是 data 是 android::uirenderer::DrawGlInfo 类型指针,包含当前裁剪区域、变换矩阵、dirty 区域等等。

DrawGlInfo 头文件定义如下:

namespace android {
namespace uirenderer {
/**
* Structure used by OpenGLRenderer::callDrawGLFunction() to pass and
* receive data from OpenGL functors.
*/
struct DrawGlInfo {
// Input: current clip rect
int clipLeft;
int clipTop;
int clipRight;
int clipBottom;
// Input: current width/height of destination surface
int width;
int height;
// Input: is the render target an FBO
bool isLayer;
// Input: current transform matrix, in OpenGL format
float transform[16];
// Input: Color space.
// const SkColorSpace* color_space_ptr;
const void* color_space_ptr;
// Output: dirty region to redraw
float dirtyLeft;
float dirtyTop;
float dirtyRight;
float dirtyBottom;
/**
* Values used as the "what" parameter of the functor.
*/
enum Mode {
// Indicates that the functor is called to perform a draw
kModeDraw,
// Indicates the the functor is called only to perform
// processing and that no draw should be attempted
kModeProcess,
// Same as kModeProcess, however there is no GL context because it was
// lost or destroyed
kModeProcessNoContext,
// Invoked every time the UI thread pushes over a frame to the render thread
// *and the owning view has a dirty display list*. This is a signal to sync
// any data that needs to be shared between the UI thread and the render thread.
// During this time the UI thread is blocked.
kModeSync
};
/**
* Values used by OpenGL functors to tell the framework
* what to do next.
*/
enum Status {
// The functor is done
kStatusDone = 0x0,
// DisplayList actually issued GL drawing commands.
// This is used to signal the HardwareRenderer that the
// buffers should be flipped - otherwise, there were no
// changes to the buffer, so no need to flip. Some hardware
// has issues with stale buffer contents when no GL
// commands are issued.
kStatusDrew = 0x4
};
}; // struct DrawGlInfo
} // namespace uirenderer
} // namespace android

Functor 设计

operator()调用时传入的 what 参数为 Mode 枚举, 对于注入 GL 的场景只需处理 kModeDraw 即可,c++ 侧类设计如下:

// MyFunctor定义
namespace android {
class MyFunctor : Functor {
public:
MyFunctor();
virtual ~MyFunctor() {}
virtual void onExec(int what,
android::uirenderer::DrawGlInfo* info);
virtual std::string getFunctorName() = 0;
int operator()(int /*what*/, void * /*data*/) override;
private:

};

}
// MyFunctor实现
int MyFunctor::operator() (int what, void *data) {
if (what == android::uirenderer::DrawGlInfo::Mode::kModeDraw) {
auto info = (android::uirenderer::DrawGlInfo*)data;
onExec(what, info);
}
return android::uirenderer::DrawGlInfo::Status::kStatusDone;

}
void MyFunctor::onExec(int what, android::uirenderer::DrawGlInfo* info) {
// 渲染实现

}

因为 functor 是 Java 层调度的,而真正实现是在 c++ 的,因此需要设计 java 侧类并做 JNI 桥接:

// java MyFunctor定义
class MyFunctor {
private long nativeHandle;
public MyFunctor() {
nativeHandle = createNativeHandle();

}
public long getNativeHandle() {
return nativeHanlde;

}
private native long createNativeHandle();

}
// jni 方法:
extern "C" JNIEXPORT jlong JNICALL
Java_com_test_MyFunctor_createNativeHandle(JNIEnv *env, jobject thiz) {
auto p = new MyFunctor();
return (jlong)p;
}

在 View.onDraw () 中调度 functor

框架在 java Canvas 类上提供了 API,可以在 onDraw () 时将 functor 记录到 Canvas 的 DisplayList 中。不过由于版本迭代的原因 API 在各版本上稍有不同,经总结可采用如下代码调用,兼容各版本区别:

public class FunctorView extends View {
...
private static Method sDrawGLFunction;
private MyFunctor myFunctor = new MyFunctor();
@Override
public void onDraw(Canvas cvs) {
super.onDraw(cvs);
getDrawFunctorMethodIfNot();
invokeFunctor(cvs, myFunctor);
}
private void invokeFunctor(Canvas canvas, MyFunctor functor) {
if (functor.getNativeHandle() != 0 && sDrawGLFunction != null) {
try {
sDrawGLFunction.invoke(canvas, functor.getNativeHandle());
} catch (Throwable t) {
// log
}
}
}
public synchronized static Method getDrawFunctorMethodIfNot() {

if (sDrawGLFunction != null) {
return sDrawGLFunction;

}
hasReflect = true;
String className;
String methodName;
Class<?> paramClass = long.class;
try {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {
className = "android.graphics.RecordingCanvas";
methodName = "callDrawGLFunction2";
} else if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP_MR1) {
className = "android.view.DisplayListCanvas";
methodName = "callDrawGLFunction2";
} else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.LOLLIPOP) {
className = "android.view.HardwareCanvas";
methodName = "callDrawGLFunction";
} else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.LOLLIPOP_MR1) {
className = "android.view.HardwareCanvas";
methodName = "callDrawGLFunction2";
} else {
className = "android.view.HardwareCanvas";
methodName = "callDrawGLFunction";
paramClass = int.class;
}
Class<?> canvasClazz = Class.forName(className);
sDrawGLFunction = SystemApiReflector.getInstance().
getDeclaredMethod(SystemApiReflector.KEY_GL_FUNCTOR, canvasClazz,
methodName, paramClass);
} catch (Throwable t) {
// 异常
}
if (sDrawGLFunction != null) {
sDrawGLFunction.setAccessible(true);
} else {
// (异常)
}
return sDrawGLFunction;
}
}

注意上述代码反射系统内部 API,Android 10 之后做了 Hidden API 保护,直接反射会失败,此部分可网上搜索解决方案,此处不展开。

四. 实践中遇到的问题

GL 状态保存&恢复

Android RenderThread 在执行 drawFunctor 前会保存部分 GL 状态,如下源码:

// Android 9.0 code
// 保存状态
void RenderState::interruptForFunctorInvoke() {
mCaches->setProgram(nullptr);
mCaches->textureState().resetActiveTexture();
meshState().unbindMeshBuffer();

meshState().unbindIndicesBuffer();
meshState().resetVertexPointers();
meshState().disableTexCoordsVertexArray();
debugOverdraw(false, false);
// TODO: We need a way to know whether the functor is sRGB aware (b/32072673)
if (mCaches->extensions().hasLinearBlending() &&
mCaches->extensions().hasSRGBWriteControl()) {
glDisable(GL_FRAMEBUFFER_SRGB_EXT);
}
}
// 恢复状态
void RenderState::resumeFromFunctorInvoke() {
if (mCaches->extensions().hasLinearBlending() &&
mCaches->extensions().hasSRGBWriteControl()) {
glEnable(GL_FRAMEBUFFER_SRGB_EXT);

}
glViewport(0, 0, mViewportWidth, mViewportHeight);
glBindFramebuffer(GL_FRAMEBUFFER, mFramebuffer);
debugOverdraw(false, false);
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
scissor().invalidate();
blend().invalidate();
mCaches->textureState().activateTexture(0);
mCaches->textureState().resetBoundTextures();
}

可以看出并没有保存所有 GL 状态,可以增加保存和恢复所有其他 GL 状态的逻辑,也可以针对实际 functor 中改变的状态进行保存和恢复;特别注意 functor 执行时的 GL 状态是非初始状态,例如 stencil、blend 等都可能被系统 RenderThread 修改,因此很多状态需要重置到默认。

View变换处理

当承载 functor 的 View 外部套 ScrollView、ViewPager,或者 View 执行动画时,渲染结果异常或者不正确。例如水平滚动条中 View 使用 functor 渲染,内容不会随着滚动条移动调整位置。进一步研究源码 Android 发现,此类问题原因都是 Android 在渲染 View 时加入了变换,变换采用标准 4x4 变换列矩阵描述,其值可以从 DrawGlInfo::transform 字段中获取, 因此渲染时需要处理 transform,例如将 transform 作为模型变换矩阵传入 shader。

ContextLost

Android framework 在 trimMemory 时在 RenderThread 中会销毁当前 GL Context 并创建一个新 Context, 这样会导致 functor 的 program、shader、纹理等 GL 资源都不可用,再去渲染的话可能会导致闪退、渲染异常等问题,因此这种情况必须处理。

首先,需要响应 lowMemory 事件,可以通过监听 Application 的 trimMemory 回调实现:

activity.getApplicationContext().registerComponentCallbacks(
new ComponentCallbacks2() {
@Override
public void onTrimMemory(int level) {
if (level == 15) {
// 触发functor重建
}
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
}
@Override
public void onLowMemory() {
}
});

然后,保存 & 恢复 functor 的 GL 资源和执行状态,例如 shader、program、fbo 等需要重新初始化,纹理、buffer、uniform 数据需要重新上传。注意由于无法事前知道 onTrimMemory 发生,上一帧内容是无法恢复的,当然知道完整的状态是可以重新渲染出来的。

鉴于存在无法提前感知的 ContextLost 情况,建议采用基于 commandbuffer 的模式来实现 functor 渲染逻辑。

五. 效果

我们用一个 OpenGL 渲染的简单 case (分辨率1080x1920),对使用 TextureView 渲染和使用 drawFunctor 渲染的方式进行了比较,

结果如下:

Simple Case 内存 CPU 占用
基于 TextureView 100 M ( Graphics 38 M ) 6%
基于 GLFunctor 84 M ( Graphics 26 M ) 4%

从上述结果可得出结论,使用 drawFunctor 方式在内存、CPU 占用上具有优势, 可应用于局部页面的互动渲染、视频渲染等场景。

到此这篇关于Android drawFunctor 原理及应用详情的文章就介绍到这了,更多相关Android drawFunctor 内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Android抽屉布局DrawerLayout的简单使用

    本文实例为大家分享了Android抽屉布局DrawerLayout的基本使用,供大家参考,具体内容如下 本次Demo的目录结构如下(图中红框即为所用文件): 创建好一个普通的Android项目后,在activity_main.xml中放入如下代码: <?xml version="1.0" encoding="utf-8"?> <android.support.v4.widget.DrawerLayout xmlns:android="ht

  • Android Canvas之drawBitmap方法案例详解

    前面讲了paint,后面会花几篇主要讲讲canvas,并且由于最近项目比较紧,所以近期的文章都会"短小精悍": paint 作为画笔,里面有非常多而强大的设置方法,比如设置颜色过滤器,设置位图渲染.渐变,设置图像的混合模式等等,而canvas呢?里面提供了哪些利器可以为我们所用,一起来看看:           通过上图我们可以看到,canvas 里的方法基本可以分为这么几类: save.restore 等与层的保存和回滚相关的方法: scale.rotate.clipXXX 等对画布

  • Android自定义view之利用drawArc方法实现动态效果(思路详解)

    目录 前言 一.准备 1.测量 2.初始化画笔 3.自定义属性 二.关键方法介绍 drawArc 三.实现 1.思路 2.效果图 前言 前几天看了一位字节Android工程师的一篇博客,他实现的是歌词上下滚动的效果,实现的关键就是定义一个偏移量,然后根据情况去修改这个值,最后触发View的重绘来达到效果.于是今天根据这个思路来写一篇简单的文章.欢迎留言 一.准备 在这之前呢,还是得简单描述一下自定义view中的一些准备工作 1.测量 @Override protected void onSize

  • Android drawFunctor 原理及应用详情

    目录 一. 背景 二. drawFunctor 原理介绍 三. 利用 drawFunctor 注入 GL 渲染 Android Functor 定义 Functor 设计 在 View.onDraw () 中调度 functor 四. 实践中遇到的问题 GL 状态保存&恢复 View变换处理 ContextLost 五. 效果 一. 背景 蚂蚁 NativeCanvas 项目 Android 平台中使用了基于 TextureView 环境实现 GL 渲染的技术方案,而 TextureView 需

  • Android Handler 原理分析及实例代码

    Android Handler 原理分析 Handler一个让无数android开发者头疼的东西,希望我今天这边文章能为您彻底根治这个问题 今天就为大家详细剖析下Handler的原理 Handler使用的原因 1.多线程更新Ui会导致UI界面错乱 2.如果加锁会导致性能下降 3.只在主线程去更新UI,轮询处理 Handler使用简介 其实关键方法就2个一个sendMessage,用来接收消息 另一个是handleMessage,用来处理接收到的消息 下面是我参考疯狂android讲义,写的一个子

  • Android 断点续传原理以及实现

    Android 断点续传原理以及实现 0.  前言 在Android开发中,断点续传听起来挺容易,在下载一个文件时点击暂停任务暂停,点击开始会继续下载文件.但是真正实现起来知识点还是蛮多的,因此今天有时间实现了一下,并进行记录. 1.  断点续传原理 在本地下载过程中要使用数据库实时存储到底存储到文件的哪个位置了,这样点击开始继续传递时,才能通过HTTP的GET请求中的setRequestProperty()方法可以告诉服务器,数据从哪里开始,到哪里结束.同时在本地的文件写入时,RandomAc

  • Android仿淘宝商品详情页效果

    本文实例为大家分享了Android仿淘宝商品详情页的具体代码,供大家参考,具体内容如下 Demo地址:先上效果图 效果就是上面图片的效果 接下来看看如何实现 首先我们来看下布局文件 <LinearLayout android:id="@+id/header" android:layout_width="match_parent" android:layout_height="72dp" android:paddingTop="24

  • Android ANR原理分析

    目录 卡顿原理 卡顿监控 ANR原理 卡顿原理 主线程有耗时操作会导致卡顿,卡顿超过阀值,触发ANR. 应用进程启动时候,Zygote会反射调用ActivityThread的main方法,启动loop循环. ActivityThread(api29) public static void main(String[] args) { Looper.prepareMainLooper(); ... Looper.loop(); throw new RuntimeException("Main thr

  • Android Broadcast原理分析之registerReceiver详解

    目录 1. BroadcastReceiver概述 2. BroadcastReceiver分类 3. registerReceiver流程图 4. 源码解析 4.1 ContextImpl.registerReceiverInternal 4.2 LoadedApk.getReceiverDispatcher 4.3 ActivityManagerService.registerReceiver 5. 总结 1. BroadcastReceiver概述 广播作为四大组件之一,在平时开发过程中会

  • Android Handle原理(Looper,Handler和Message三者关系案例详解

    介绍 前面的内容对Handler做了介绍,也讲解了如何使用handler,但是我们并不知道他的实现原理.本文从源码的角度来分析如何实现的. 首先我们得知道Handler,Looper,Message Queue三者之间的关系 - Handler封装了消息的发送,也负责接收消.内部会跟Looper关联. - Looper 消息封装的载,内部包含了MessageQueue,负责从MessageQueue取出消息,然后交给Handler处理 - MessageQueue 就是一个消息队列,负责存储消息

  • Android Handle原理(Looper,Handler和Message)三者关系案例详解

    介绍 前面的内容对Handler做了介绍,也讲解了如何使用handler,但是我们并不知道他的实现原理.本文从源码的角度来分析如何实现的. 首先我们得知道Handler,Looper,Message Queue三者之间的关系 - Handler封装了消息的发送,也负责接收消.内部会跟Looper关联. - Looper 消息封装的载,内部包含了MessageQueue,负责从MessageQueue取出消息,然后交给Handler处理 - MessageQueue 就是一个消息队列,负责存储消息

  • Android中的Launch Mode详情

    目录 一. 多任务和Task.启动模式 二. 四种启动模式详解 1. Standard 2. SingleTask 3. SingleTop 4.SingleInstance 三. Task间堆叠与Task Reparenting 1. Task间堆叠 2. Task Reparenting/Task重定父级 一. 多任务和Task.启动模式 Android 手机在早期,下方通常会内置三个实体的触摸按键,分别是:桌面.菜单.返回.大概在Android 5.0 之后,Android开始流行系统内置

  • Android动态更换应用图标详情

    目录 一.背景 二.技术实现 一.背景 近日,微博官方发布了一项新功能,即可以在App设置中动态更换微博的显示图标样式.根据微博官方的说法,除了最原始的图标外,微博还推出了另外10种不同的样式,既有3D微博.炫彩微博等保留了眼睛造型的新样式,也有奶酪甜馨.巧克力等以食物命名的“新口味”,还有梦幻紫.幻想星空等抽象派新造型,给了微博用户多种选择的自由. 不过需要注意的是,这一功能并不是面对所有人开放的,只有微博年费会员才能享受.此外,iOS 10.3及以上和Android 10及以上系统版本支持该

随机推荐