亲爱的,漫步在这片童话之地
听那清澈流水的声音,在耳畔回荡
阳光和煦,如同你温暖的微笑
这片宁谧的天堂注定了我们的相遇
在这里,我渐渐地明白
你便是我的心灵伴侣
每一次你的微笑,都让我陶醉
每一次你的拥抱,都让我如沐春风
而今,我手捧一束鲜花
内心充满着悸动与兴奋
我要向你诉说我的心思
请你成为我共同经历幸福与挑战的人
我爱你,深深地爱你
你是我命中注定的挚爱
愿我们相互扶持,在阳光下翩翩起舞
共同谱写出一曲属于我们的梦幻乐章
亲爱的,漫步在这片童话之地
听那清澈流水的声音,在耳畔回荡
阳光和煦,如同你温暖的微笑
这片宁谧的天堂注定了我们的相遇
在这里,我渐渐地明白
你便是我的心灵伴侣
每一次你的微笑,都让我陶醉
每一次你的拥抱,都让我如沐春风
而今,我手捧一束鲜花
内心充满着悸动与兴奋
我要向你诉说我的心思
请你成为我共同经历幸福与挑战的人
我爱你,深深地爱你
你是我命中注定的挚爱
愿我们相互扶持,在阳光下翩翩起舞
共同谱写出一曲属于我们的梦幻乐章
更详细的介绍可以看看这篇文章:OpenGL纹理详解
纹理(Texture)可以理解为GPU内代表图像数据的一个对象。
主要是以Android为例简要说明,参考来源:谈一谈Android上的SurfaceTexture
SurfaceTexture 是Android上做渲染的核心组件,它是 Surface 和 OpenGL ES纹理的组合,用于提供输出到 GLES 纹理的 Surface。从安卓渲染系统上来说,SurfaceTexture 是一个BufferQueue的消费者,当生产方将新的缓冲区排入队列时,onFrameAvailable() 回调会通知应用。然后,应用调用 updateTexImage(),这会释放先前占有的缓冲区,从队列中获取新缓冲区并执行 EGL 调用,从而使 GLES 可将此缓冲区作为外部纹理使用。

基本流程如下:
1.通过Camera、Decoder、OpenGL生成图像流。
2.图像流通过Surface入队到BufferQueue,并通知到GLConsumer。
3.GLConsumer从BufferQueue获取图像流GraphicBuffer,并转换为EXTERNAL OES纹理。
4.得到OES纹理后,用户方就可以将其转换成普通的纹理,然后应用特效或者上屏。
假设我们有两个OpenGL的环境,一个是自身的环境称为A,另一个是第三方环境称为B,B想渲染A的纹理,有以下这两种共享形式:
ShareContext(iOS平台的EAGL叫ShareGroup):A共享自己EGLContext给B,但是本身EGLContext是用来管理GL环境的,例如环境下的资源的隔离,共享了EGLContext相当于A把自己的GL环境和资源都共享给B,B当然可以直接访问A的纹理资源了。但是如此对B的侵入较多,破坏了B的环境隔离,很容易对B造成影响,所以存在一定风险,基本所有的GL环境都不会开发接口给外部操作自己的Context,这个方法适用于A与B有大量交互的场景,方便互相使用。
共享内存:一般来说第三方OpenGL环境都会开放SurfaceTexture的接口,通过这个接口我们可以将A的纹理数据共享到B的OpenGL环境中,B可以将其转换成普通的纹理,然后应用特效或者上屏。这个共享在安卓端是通过EGLImageKHR来实现的,当然还有其他方式如HardwareBuffer,而IOS平台使用的是CVPixelBuffer实现。
EGLImageKHR其设计目的就是为了共享2D纹理数据的,因此驱动程序在底层实现的时候,往往实现了CPU与GPU对同一资源的访问,这样就可以做到无需拷贝的数据共享,降低功耗与提高性能。
在Android平台上,了解这点是非常重要的。而iOS平台由于使用了EAGL而不是EGL,因此并不会使用EGLImage,但也有自己的数据映射的方式。
CVPixelBuffer可以用来保存多种格式的图像数据,包括YUV/RGBA等。在CVPixelBuffer的内存管理中,包括了何时释放内存,数据的生命周期等
下图为Flutter的一个简单的渲染框架

Layer Tree:这个是dart runtime输出的一个树状数据结构,树上的每一个叶子节点,代表了一个界面元素(Button,Image等等)。
Skia:这个是谷歌的一个跨平台渲染框架,从目前IOS和anrdroid来看,SKIA底层最终都是调用OpenGL绘制。Vulkan支持还不太好,Metal还不支持。
Shell:这里的Shell特指平台特性(Platform)的那一部分,包含IOS和Android平台相关的实现,包括EAGLContext管理、上屏的操作以及后面将会重点介绍的外接纹理实现等等。
从图中可以看出,当Runtime完成Layout输出一个Layertree以后,在管线中会遍历Layertree的每一个叶子节点,每一个叶子节点最终会调用Skia引擎完成界面元素的绘制,在遍历完成后,在调用glPresentRenderBuffer(IOS)或者glSwapBuffer(Android)按完成上屏操作。
基于这个基本原理,Flutter在Native和Flutter Engine上实现了UI的隔离,书写UI代码时不用再关心平台实现从而实现了跨平台。
Flutter在设计的时候考虑到了高性能的纹理渲染,所以设计了TextureLayer(上图橙色框),和其他的FlutterLayer不一样,这个是用来专门渲染外接纹理的,当在Flutter里创建出一个Texture控件时,代表的是在这个控件上显示的数据,需要由Native提供。
我们已经知道,可以通过TextureLayer来实现纹理共享,那么在实际开发中,该怎么实现呢?
共享纹理三步走:
原生端创建一个对象,实现FlutterTexture协议,该对象用来管理具体的纹理数据
原生端通过FlutterTextureRegistry来注册第一步的FlutterTexture对象,获取一个flutter纹理id
原生端将该id通过channel机制传递给dart侧,dart侧就能够通过Texture这个widget来使用纹理了
1 | //自己创建texture注册 val surfaceTexture = MySurfaceTexture val surfaceTextureEntry = binding.textureRegistry.registerSurfaceTexture() //使用flutter提供的方法快捷创建 val surfaceTextureEntry = binding.textureRegistry.createSurfaceTexture() //获取id返回dart端 val id = surfaceTextureEntry.id().toInt() |
原生注册纹理的起点:FlutterPluginBinding.textureRegistry.createSurfaceTexture()
1 | /** * Creates and returns a new {@link SurfaceTexture} managed by the Flutter engine that is also * made available to Flutter code. */ @Override public SurfaceTextureEntry createSurfaceTexture() { Log.v(TAG, "Creating a SurfaceTexture."); final SurfaceTexture surfaceTexture = new SurfaceTexture(0); return registerSurfaceTexture(surfaceTexture); } |
以下是JNI层的代码追踪
1 | static void RegisterTexture(JNIEnv* env, jobject jcaller, jlong shell\_holder, jlong texture\_id, jobject surface\_texture) { ANDROID\_SHELL\_HOLDER->GetPlatformView()->RegisterExternalTexture( static\_cast<int64\_t>(texture\_id), // fml::jni::ScopedJavaGlobalRef<jobject>(env, surface_texture) // ); } |
以上源码地址:platform_view_android_jni_impl.cc
1 | void PlatformViewAndroid::RegisterExternalTexture( int64\_t texture\_id, const fml::jni::ScopedJavaGlobalRef<jobject>& surface\_texture) { if (android\_context_->RenderingApi() == AndroidRenderingAPI::kOpenGLES) { RegisterTexture(std::make\_shared<AndroidExternalTextureGL>( texture\_id, surface\_texture, jni\_facade_)); } else { FML_LOG(INFO) << "Attempted to use a GL texture in a non GL context."; } } |
以上源码地址:platform_view_android.cc
1 | void TextureRegistry::RegisterTexture(const std::shared\_ptr<Texture>& texture) { if (!texture) { return; } mapping\_\[texture->Id()\] = texture; } |
以上源码地址:texture.cc
这里额外放出一个方法,是用于从mapping中获取已经注册的纹理。
1 | std::shared\_ptr<Texture> TextureRegistry::GetTexture(int64\_t id) { auto it = mapping_.find(id); return it != mapping_.end() ? it->second : nullptr; } |
mapping_end(): 返回的是 ‘end element’是跟在Map中最后一个元素之后的理论元素。
mapping_.erase() 擦除匹配 element,类似与其他语言的remove。
以上源码地址:texture.cc
1 | void TextureRegistry::UnregisterTexture(int64\_t id) { auto found = mapping\_.find(id); if (found == mapping_.end()) { return; } found->second->OnTextureUnregistered(); mapping_.erase(found); } |
以上源码地址:texture.cc
至此,我们在各原生端(Android)的注册已经完成,那我把场景切回flutter端,视线转移到textureLayer渲染中。
省略掉不重要一些过程,主要Flutter界面使用Texture Widget的过程,最终是通过TextureLayer来进行纹理渲染。
Flutter渲染机制:Flutter渲染机制—GPU线程
1 | @override void addToScene(ui.SceneBuilder builder) { builder.addTexture( textureId, offset: rect.topLeft, width: rect.width, height: rect.height, freeze: freeze, filterQuality: filterQuality, ); } |
这里又遇到JNI的方法,尝试继续往下追一下
1 | void SceneBuilder::addTexture(double dx, double dy, double width, double height, int64\_t textureId, bool freeze, int filterQualityIndex) { auto sampling = ImageFilter::SamplingFromIndex(filterQualityIndex); auto layer = std::make\_unique<flutter::TextureLayer>( SkPoint::Make(dx, dy), SkSize::Make(width, height), textureId, freeze, sampling); AddLayer(std::move(layer)); } |
以上源码地址:scene_builder.cc
到最后只知道Flutter把这个layer入栈了,目前不知道入栈最后具体作用,从部分源码注释,应该是这里存储着渲染层级。然后scene最终渲染,其实是要经过FlutterView.render放法继续往下走。本次分享共享纹理过程,Flutter的渲染过程暂时忽略。

但是从这里我们应该能推测,这里应该是和引擎建立纹理关联,渲染的时候会拿出对应的layer来执行对应的渲染方法
然后从前渲染流程图得知,最终应该会走到print方法,回顾下图:

所以我们直接查阅TextureLayer的print方法,看看是否能和之前的步奏关联起来
1 | void TextureLayer::Paint(PaintContext& context) const { FML\_DCHECK(needs\_painting(context)); std::shared\_ptr<Texture> texture = context.texture\_registry ? context.texture\_registry->GetTexture(texture\_id_) : nullptr; if (!texture) { TRACE\_EVENT\_INSTANT0("flutter", "null texture"); return; } SkPaint sk\_paint; DlPaint dl\_paint; Texture::PaintContext ctx{ .canvas = context.canvas, .builder = context.builder, .gr\_context = context.gr\_context, .aiks\_context = context.aiks\_context, .sk\_paint = context.state\_stack.fill(sk\_paint), .dl\_paint = context.state\_stack.fill(dl\_paint), }; texture->Paint(ctx, paint\_bounds(), freeze\_, ToSk(sampling_)); } |
以上源码地址:texture_layer.cc
在这里看到了最终纹理的来源就是我们在原生端注册的纹理,从mapping里面获取的,然后执行了texture->Paint方法最终渲染到界面上。
其实IOS的整体流程也差不多,可能会存在纹理操作的差异而已。因为本次是以Android端作为例子的,所以这里的纹理是来自于AndroidExternalTextureGL,最终渲染如下:
1 | void AndroidExternalTextureGL::Paint(PaintContext& context, const SkRect& bounds, bool freeze, const SkSamplingOptions& sampling) { if (state_ == AttachmentState::detached) { return; } if (state_ == AttachmentState::uninitialized) { glGenTextures(1, &texture\_name\_); Attach(static\_cast<jint>(texture\_name_)); state_ = AttachmentState::attached; } if (!freeze && new\_frame\_ready_) { Update(); new\_frame\_ready_ = false; } GrGLTextureInfo textureInfo = {GL\_TEXTURE\_EXTERNAL\_OES, texture\_name_, GL\_RGBA8\_OES}; GrBackendTexture backendTexture(1, 1, GrMipMapped::kNo, textureInfo); sk\_sp<SkImage> image = SkImage::MakeFromTexture( context.gr\_context, backendTexture, kTopLeft\_GrSurfaceOrigin, kRGBA\_8888\_SkColorType, kPremul\_SkAlphaType, nullptr); if (image) { SkAutoCanvasRestore autoRestore(context.canvas, true); // The incoming texture is vertically flipped, so we flip it // back. OpenGL's coordinate system has Positive Y equivalent to up, while // Skia's coordinate system has Negative Y equvalent to up. context.canvas->translate(bounds.x(), bounds.y() + bounds.height()); context.canvas->scale(bounds.width(), -bounds.height()); if (!transform.isIdentity()) { sk\_sp<SkShader> shader = image->makeShader( SkTileMode::kRepeat, SkTileMode::kRepeat, sampling, transform); SkPaint paintWithShader; if (context.sk\_paint) { paintWithShader = *context.sk\_paint; } paintWithShader.setShader(shader); context.canvas->drawRect(SkRect::MakeWH(1, 1), paintWithShader); } else { context.canvas->drawImage(image, 0, 0, sampling, context.sk\_paint); } } } |
以上源码地址:android_external_texture_gl.cc
为了方便对比差异,附上IOS最终渲染过程
1 | void IOSExternalTextureMetal::Paint(PaintContext& context, const SkRect& bounds, bool freeze, const SkSamplingOptions& sampling) { \[darwin\_external\_texture\_metal\_ paintContext:context bounds:bounds freeze:freeze sampling:sampling\]; } |
由于IOS嵌套调用较多,因此详细过程可直接查阅源码:
Paint源码:ios_external_texture_metal.mm
paintContext源码:FlutterDarwinExternalTextureMetal.mm
IOS之前版本的渲染:
1 | void IOSExternalTextureGL::Paint(SkCanvas& canvas, const SkRect& bounds) { if (!cache\_ref\_) { CVOpenGLESTextureCacheRef cache; CVReturn err = CVOpenGLESTextureCacheCreate(kCFAllocatorDefault, NULL, \[EAGLContext currentContext\], NULL, &cache); if (err == noErr) { cache\_ref\_.Reset(cache); } else { FXL\_LOG(WARNING) << "Failed to create GLES texture cache: " << err; return; } } fml::CFRef<CVPixelBufferRef> bufferRef; bufferRef.Reset(\[external\_texture_ copyPixelBuffer\]); if (bufferRef != nullptr) { CVOpenGLESTextureRef texture; CVReturn err = CVOpenGLESTextureCacheCreateTextureFromImage( kCFAllocatorDefault, cache\_ref\_, bufferRef, nullptr, GL\_TEXTURE\_2D, GL\_RGBA, static\_cast<int>(CVPixelBufferGetWidth(bufferRef)), static\_cast<int>(CVPixelBufferGetHeight(bufferRef)), GL\_BGRA, GL\_UNSIGNED\_BYTE, 0,&texture); texture\_ref\_.Reset(texture); if (err != noErr) { FXL\_LOG(WARNING) << "Could not create texture from pixel buffer: " << err; return; } } if (!texture\_ref_) { return; } GrGLTextureInfo textureInfo = {CVOpenGLESTextureGetTarget(texture\_ref\_),CVOpenGLESTextureGetName(texture\_ref\_), GL\_RGBA8\_OES}; GrBackendTexture backendTexture(bounds.width(), bounds.height(), GrMipMapped::kNo, textureInfo); sk\_sp<SkImage> image =SkImage::MakeFromTexture(canvas.getGrContext(), backendTexture, kTopLeft\_GrSurfaceOrigin,kRGBA\_8888\_SkColorType, kPremul_SkAlphaType, nullptr); if (image) { canvas.drawImage(image, bounds.x(), bounds.y()); } } |
可以得出大致的最终渲染过程:
Flutter gl创建Texture
Attach,绑定GLcontext
更新纹理使用,标识即将渲染下一帧
准备下一帧数据(从原生纹理复制过来),生成SkImage
将SkImage渲染到给定的canvas上(由Skia上屏)
那么总体的共享流程用图表示大概是这样的:

1.原生独有的渲染器渲染的纹理需要共享到dart –如PDF渲染
2.原生渲染性能远大于dart –如视频播放
3.硬件交互限制,必须由原生渲染 –录像
4.dart想使用某些三方渲染引擎处理纹理
—-稍作记录,避免下次忘了又得我去查官方文档—-
有时候,我们开发的app需要支持打开某些文件,如何才能使得打开文件时系统能把我们开发的app列出来呢?
例如,
1.我开发了一个浏览器,那用户点击链接选择浏览器打开时,怎么关联上我们app可供选择
2.有时候我们app支持分享图片,那用户在系统相册选择分享时,怎么关联上我们app可供选择
3.最常见的一种情况,就是我们有时候数据文件的格式是自定义的,其他app不支持解析,但是从文件管理打开,怎么关联上我们app可供选择打开呢?
4….
上代码,再解释。
1 | <activity |
需要注意的是
首先第一个必不可少的就是
1 | <action android:name="android.intent.action.VIEW" /> |
官方解释:
1 | ACTION_VIEW |
简而言之,可以根据你的数据类型调用到你这个Activity来处理显示。
然后,第二个也是必不可少的是
官方解释:
1 | CATEGORY_BROWSABLE |
翻译过来就是:可以从浏览器安全地调用的活动必须支持此类别。例如,如果用户正在查看网页或电子邮件并单击文本中的链接,则生成的Intent执行该链接将需要BROWSABLE类别,以便只有支持此类别的活动才会被视为可能的操作。通过支持此类别,您要承诺通过调用任何匹配的Intent来(进行操作)对用户来说无任何损害(无需用户干预)。
这个很重要的!假如你设置了action.VIEW,并且把加上了,但是你忽略了category.BROWSABLE,那么恭喜你,你将会收获以下错误:
1 | Activity supporting ACTION_VIEW is not set as BROWSABLE less... (Ctrl+F1) |
看起来,似乎这样差不多了,然后把也写对了,运行app,你会发现可选择列表中并没有你的app,为什么呢?
这还少了一个重要的东西,第三个必须要设置的东西
1 | <category android:name="android.intent.category.DEFAULT" /> |
官方解释:
1 | CATEGORY_DEFAULT |
设置Activity是否应该是作为一个段数据执行的选项的默认操作。设置此项后,将在用户对某些数据执行操作时,通过指定的intent过滤器匹配到数据,然后就可以启动你来这Activity来处理了。哦~原来这才是重中之重。
这里需要特别说明一下,当这个Activity设置了
1 |
最后一步,就是设置data了,指定你能支持的数据类型,没啥好说的,照写就好了
emm…还是讲一下吧,讲之前先了解一个东西:
uri 的组成如下:
scheme
例如http、content、file。
host
URI的主机名,比如www.jianshu.com。
port
端口号。
path/pathPattern/pathPrefix
path表示完整的路径。 pathPattern是判定完整路径是否匹配用的正则表达式。pathPrefix也是正则表达式,它匹配的是路径的前缀信息。
所以,data里面的scheme指的就是类似于协议的 东西,如果我们支持打开本地的文件,那就是file咯,别忘了android 7.0 以上以某种方式打开本地文件是content://xxx开头的,所以把content加上吧。
如果你需要支持网页打开,也不妨加上http和https。
奇怪?为什么我不设置host?不设置默认是“*”,也就是所有主机地址都支持,如果你需要特定过滤,你可以加上。
还有比较重要的一点是
1 | mimeType |
这个就是限定识别的文件类型,例如image/*,那么只有打开图片类型才能选择使用你这个app打开,如果你的app足够强大,支持所有格式,那么设置成 / ,wow~好牛逼,什么格式都能打开。
当然,我也说了,有一些是自定义格式的,mineType不收集,我被迫设置成了 / ,但是我又不想打开文件什么都出现我的app怎么办呢?看到uri构成的那个pathPattern没? 就是它!用它来过滤一下后缀,例如我的自定义后缀是 .abc和.def,那么我可以加上这两句就完美了
1 | <data android:pathPattern=".*\\\.abc"/> |
好了,写到这里,编译运行,你已经可以愉快使用你的app打开这个类型的文件,美滋滋,再见,拜拜
--------------------------------------------------------------------------------------------------------------------------------------------------
别告诉我,选择你的app打开之后你不知道怎么获取文件信息
1 | //在Activity的onCreate()或者onNewIntent()中Intent intent = getIntent();Uri uri = intent.getData();if (uri != null) { |
我去喝枸杞了,拜~
Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.
1 | $ hexo new "My New Post" |
More info: Writing
1 | $ hexo server |
More info: Server
1 | $ hexo generate |
More info: Generating
1 | $ hexo deploy |
More info: Deployment