Android?drawFunctor?原理及應(yīng)用詳情
一. 背景
螞蟻 NativeCanvas 項(xiàng)目 Android 平臺(tái)中使用了基于 TextureView 環(huán)境實(shí)現(xiàn) GL 渲染的技術(shù)方案,而 TextureView 需使用與 Activity Window 獨(dú)立的 GraphicBuffer,RenderThread 在上屏 TextureView 內(nèi)容時(shí)需要將 GraphicBuffer 封裝為 EGLImage 上傳為紋理再渲染,內(nèi)存占用較高。為降低內(nèi)存占用,經(jīng)仔細(xì)調(diào)研 Android 源碼,發(fā)現(xiàn)其中存在一種稱為 drawFunctor 的技術(shù),用來(lái)將 WebView 合成后的內(nèi)容同步到 Activity Window 內(nèi)上屏。經(jīng)過(guò)一番探索成功實(shí)現(xiàn)了基于 drawFunctor 實(shí)現(xiàn) GL 注入 RenderThread 的功能,本文將介紹這是如何實(shí)現(xiàn)的。
二. drawFunctor 原理介紹
drawFunctor 是 Android 提供的一種在 RenderThread 渲染流程中插入執(zhí)行代碼機(jī)制,Android 框架是通過(guò)以下三步來(lái)實(shí)現(xiàn)這個(gè)機(jī)制的:
- 在 UI 線程 View 繪制流程 onDraw 方法中,通過(guò) RecordingCanvas.invoke 接口,將 functor 插入 DisplayList 中
- 在 RenderThread 渲染 frame 時(shí)執(zhí)行 DisplayList,判斷如果是 functor 類型的 op,則保存當(dāng)前部分 gl 狀態(tài)
- 在 RenderThread 中真正執(zhí)行 functor 邏輯,執(zhí)行完成后恢復(fù) gl 狀態(tài)并繼續(xù)
目前只能通過(guò) View.OnDraw 來(lái)注入 functor,因此對(duì)于非 attached 的 view 是無(wú)法實(shí)現(xiàn)注入的。Functor 對(duì)具體要執(zhí)行的代碼并未限制,理論上可以插入任何代碼的,比如插入一些統(tǒng)計(jì)、性能檢測(cè)之類代碼。系統(tǒng)為了 functor 不影響當(dāng)前 gl context,執(zhí)行 functor 前后進(jìn)行了基本的狀態(tài)保存和恢復(fù)工作。
另外,如果 View 設(shè)置了使用 HardwareLayer, 則 RenderThread 會(huì)單獨(dú)渲染此 View,具體做法是為 Layer 生成一塊 FBO,View 的內(nèi)容渲染到此 FBO 上,然后再將 FBO 以 View 在 hierachy 上的變換繪制 Activity Window Buffer 上。 對(duì) drawFunctor 影響的是, 會(huì)切換到 View 對(duì)應(yīng)的 FBO 下執(zhí)行 functor, 即 functor 執(zhí)行的結(jié)果是寫入到 FBO 而不是 Window Buffer。
三. 利用 drawFunctor 注入 GL 渲染
根據(jù)上文介紹,通過(guò) drawFunctor 可以在 RenderThread 中注入任何代碼,那么也一定可以注入 OpenGL API 來(lái)進(jìn)行渲染。我們知道 OpenGL API 需要執(zhí)行 EGL Context 上,所以就有兩種策略:一種是利用 RenderThread 默認(rèn)的 EGL Context 環(huán)境,一種是創(chuàng)建與 RenderThread EGL Context share 的 EGL Context。本文重點(diǎn)介紹第一種,第二種方法大同小異。
Android Functor 定義
首先找到 Android 源碼中 Functor 的頭文件定義并引入項(xiàng)目:
namespace android {
class Functor {
public:
Functor() {}
virtual ~Functor() {}
virtual int operator()(int /*what*/, void * /*data*/) { return 0; }
};
}RenderThread 執(zhí)行 Functor 時(shí)將調(diào)用 operator()方法,what 表示 functor 的操作類型,常見的有同步和繪制, 而 data 是 RenderThread 執(zhí)行 functor 時(shí)傳入的參數(shù),根據(jù)源碼發(fā)現(xiàn)是 data 是 android::uirenderer::DrawGlInfo 類型指針,包含當(dāng)前裁剪區(qū)域、變換矩陣、dirty 區(qū)域等等。
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 androidFunctor 設(shè)計(jì)
operator()調(diào)用時(shí)傳入的 what 參數(shù)為 Mode 枚舉, 對(duì)于注入 GL 的場(chǎng)景只需處理 kModeDraw 即可,c++ 側(cè)類設(shè)計(jì)如下:
// 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實(shí)現(xiàn)
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) {
// 渲染實(shí)現(xiàn)
}因?yàn)?functor 是 Java 層調(diào)度的,而真正實(shí)現(xiàn)是在 c++ 的,因此需要設(shè)計(jì) java 側(cè)類并做 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 () 中調(diào)度 functor
框架在 java Canvas 類上提供了 API,可以在 onDraw () 時(shí)將 functor 記錄到 Canvas 的 DisplayList 中。不過(guò)由于版本迭代的原因 API 在各版本上稍有不同,經(jīng)總結(jié)可采用如下代碼調(diào)用,兼容各版本區(qū)別:
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;
}
}注意上述代碼反射系統(tǒng)內(nèi)部 API,Android 10 之后做了 Hidden API 保護(hù),直接反射會(huì)失敗,此部分可網(wǎng)上搜索解決方案,此處不展開。
四. 實(shí)踐中遇到的問(wèn)題
GL 狀態(tài)保存&恢復(fù)
Android RenderThread 在執(zhí)行 drawFunctor 前會(huì)保存部分 GL 狀態(tài),如下源碼:
// Android 9.0 code
// 保存狀態(tài)
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);
}
}
// 恢復(fù)狀態(tài)
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();
}可以看出并沒(méi)有保存所有 GL 狀態(tài),可以增加保存和恢復(fù)所有其他 GL 狀態(tài)的邏輯,也可以針對(duì)實(shí)際 functor 中改變的狀態(tài)進(jìn)行保存和恢復(fù);特別注意 functor 執(zhí)行時(shí)的 GL 狀態(tài)是非初始狀態(tài),例如 stencil、blend 等都可能被系統(tǒng) RenderThread 修改,因此很多狀態(tài)需要重置到默認(rèn)。
View變換處理
當(dāng)承載 functor 的 View 外部套 ScrollView、ViewPager,或者 View 執(zhí)行動(dòng)畫時(shí),渲染結(jié)果異?;蛘卟徽_。例如水平滾動(dòng)條中 View 使用 functor 渲染,內(nèi)容不會(huì)隨著滾動(dòng)條移動(dòng)調(diào)整位置。進(jìn)一步研究源碼 Android 發(fā)現(xiàn),此類問(wèn)題原因都是 Android 在渲染 View 時(shí)加入了變換,變換采用標(biāo)準(zhǔn) 4x4 變換列矩陣描述,其值可以從 DrawGlInfo::transform 字段中獲取, 因此渲染時(shí)需要處理 transform,例如將 transform 作為模型變換矩陣傳入 shader。
ContextLost
Android framework 在 trimMemory 時(shí)在 RenderThread 中會(huì)銷毀當(dāng)前 GL Context 并創(chuàng)建一個(gè)新 Context, 這樣會(huì)導(dǎo)致 functor 的 program、shader、紋理等 GL 資源都不可用,再去渲染的話可能會(huì)導(dǎo)致閃退、渲染異常等問(wèn)題,因此這種情況必須處理。
首先,需要響應(yīng) lowMemory 事件,可以通過(guò)監(jiān)聽 Application 的 trimMemory 回調(diào)實(shí)現(xiàn):
activity.getApplicationContext().registerComponentCallbacks(
new ComponentCallbacks2() {
@Override
public void onTrimMemory(int level) {
if (level == 15) {
// 觸發(fā)functor重建
}
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
}
@Override
public void onLowMemory() {
}
});然后,保存 & 恢復(fù) functor 的 GL 資源和執(zhí)行狀態(tài),例如 shader、program、fbo 等需要重新初始化,紋理、buffer、uniform 數(shù)據(jù)需要重新上傳。注意由于無(wú)法事前知道 onTrimMemory 發(fā)生,上一幀內(nèi)容是無(wú)法恢復(fù)的,當(dāng)然知道完整的狀態(tài)是可以重新渲染出來(lái)的。
鑒于存在無(wú)法提前感知的 ContextLost 情況,建議采用基于 commandbuffer 的模式來(lái)實(shí)現(xiàn) functor 渲染邏輯。
五. 效果
我們用一個(gè) OpenGL 渲染的簡(jiǎn)單 case (分辨率1080x1920),對(duì)使用 TextureView 渲染和使用 drawFunctor 渲染的方式進(jìn)行了比較,
結(jié)果如下:
| Simple Case | 內(nèi)存 | CPU 占用 |
|---|---|---|
| 基于 TextureView | 100 M ( Graphics 38 M ) | 6% |
| 基于 GLFunctor | 84 M ( Graphics 26 M ) | 4% |
從上述結(jié)果可得出結(jié)論,使用 drawFunctor 方式在內(nèi)存、CPU 占用上具有優(yōu)勢(shì), 可應(yīng)用于局部頁(yè)面的互動(dòng)渲染、視頻渲染等場(chǎng)景。
到此這篇關(guān)于Android drawFunctor 原理及應(yīng)用詳情的文章就介紹到這了,更多相關(guān)Android drawFunctor 內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android使用BottomTabBar實(shí)現(xiàn)底部導(dǎo)航頁(yè)效果
這篇文章主要介紹了Android使用BottomTabBar實(shí)現(xiàn)底部導(dǎo)航頁(yè)效果,本文通過(guò)實(shí)例代碼結(jié)合文字說(shuō)明的形式給大家介紹的非常詳細(xì),需要的朋友參考下吧2018-03-03
Android開發(fā)之Sqliteopenhelper用法實(shí)例分析
這篇文章主要介紹了Android開發(fā)之Sqliteopenhelper用法,實(shí)例分析了SQLiteOpenHelper類操作數(shù)據(jù)庫(kù)的相關(guān)技巧,需要的朋友可以參考下2015-05-05
Android開發(fā)之PopupWindow創(chuàng)建彈窗、對(duì)話框的方法詳解
這篇文章主要介紹了Android開發(fā)之PopupWindow創(chuàng)建彈窗、對(duì)話框的方法,結(jié)合實(shí)例形式詳細(xì)分析了Android使用PopupWindow創(chuàng)建對(duì)話框相關(guān)操作技巧,需要的朋友可以參考下2019-03-03
Android使用WindowManager制作一個(gè)可拖動(dòng)的控件
這篇文章主要為大家詳細(xì)介紹了Android使用WindowManager制作一個(gè)可拖動(dòng)的控件的相關(guān)資料,感興趣的小伙伴們可以參考一下2016-08-08
Android studio 3.0 查看手機(jī)文件系統(tǒng)的方法(超簡(jiǎn)單)
本文給大家分享Android studio更新到3.0版本之后,查看手機(jī)文件系統(tǒng)的方法,需要的朋友參考下吧2017-11-11
Android studio實(shí)現(xiàn)左右滑動(dòng)切換圖片
這篇文章主要為大家詳細(xì)介紹了Android studio實(shí)現(xiàn)左右滑動(dòng)切換圖片,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-05-05
Android底部導(dǎo)航欄的三種風(fēng)格實(shí)現(xiàn)
這篇文章主要介紹了Android底部導(dǎo)航欄的三種風(fēng)格實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-06-06
Android 屬性動(dòng)畫ValueAnimator與插值器詳解
這篇文章主要介紹了Android 屬性動(dòng)畫ValueAnimator與插值器詳解的相關(guān)資料,需要的朋友可以參考下2017-05-05

