关于flutter:Flutter-Android-端-FlutterView-相关流程源码分析

75次阅读

共计 16225 个字符,预计需要花费 41 分钟才能阅读完成。

Flutter 系列文章连载~

  • 《Flutter Android 工程构造及应用层编译源码深入分析》
  • 《Flutter 命令实质之 Flutter tools 机制源码深入分析》
  • 《Flutter 的 runApp 与三棵树诞生流程源码剖析》
  • 《Flutter Android 端 Activity/Fragment 流程源码剖析》
  • 《Flutter Android 端 FlutterInjector 及依赖流程源码剖析》
  • 《Flutter Android 端 FlutterEngine Java 相干流程源码剖析》
  • 《Flutter Android 端 FlutterView 相干流程源码剖析》
  • 《Flutter 绘制动机 VSYNC 流程源码全方位剖析》
  • 《Flutter 安卓 Platform 与 Dart 端音讯通信形式 Channel 源码解析》

背景

后面系列文章咱们剖析了 FlutterActivity 等相干流程,晓得一个 Flutter Android App 的实质是通过 FlutterView 进行渲染。过后因为篇幅限度,咱们没有进入详细分析,这里作为一个专题进行简略剖析。

SDK 中同属于 FlutterView 体系的控件大抵有如图这些:

下文次要围绕上图进行剖析。

FlutterSplashView 相干剖析

FlutterSplashView 的次要作用是在 FlutterView render 渲染进去之前显示一个 SplashScreen(实质 Drawable)过渡图(能够了解成相似开屏图片)。这个控件的调用在后面《Flutter Android 端 Activity/Fragment 流程源码剖析》文章中剖析 FlutterActivityAndFragmentDelegate 时有看到过,在其 onCreateView 办法中先实例化了 FlutterSplashView,接着调用flutterSplashView.displayFlutterViewWithSplash(flutterView, host.provideSplashScreen()),而后把这个 FlutterSplashView 控件返回给 FlutterActivity 通过 setContentView 进行设置。上面是其相干流程次要源码:

final class FlutterSplashView extends FrameLayout {
  //......
  // 步骤 1、把给定的 splashScreen 显示在 flutterView 之上,直到 flutterView 的首帧渲染进去才过渡隐没。public void displayFlutterViewWithSplash(@NonNull FlutterView flutterView, @Nullable SplashScreen splashScreen) {
    // 步骤 2、一堆反复调用的复位操作。if (this.flutterView != null) {this.flutterView.removeOnFirstFrameRenderedListener(flutterUiDisplayListener);
      removeView(this.flutterView);
    }
    if (splashScreenView != null) {removeView(splashScreenView);
    }

    // 步骤 3、把 flutterView 增加给以后 FlutterSplashView,实质是一个 FrameLayout。this.flutterView = flutterView;
    addView(flutterView);

    this.splashScreen = splashScreen;

    // 步骤 4、显示一个 splash screen 开屏图。if (splashScreen != null) {
      // 步骤 5、如果 flutterView 未渲染进去则条件成立。if (isSplashScreenNeededNow()) {Log.v(TAG, "Showing splash screen UI.");
        // 步骤 6、splashScreen 是 FlutterActivity 中实现的 DrawableSplashScreen。//DrawableSplashScreen 中的 Drawable 实质来自清单文件 meta-data 中 io.flutter.embedding.android.SplashScreenDrawable 配置。//DrawableSplashScreen implements SplashScreen,所以就是 DrawableSplashScreen 的 createSplashView 办法。// 因而 splashScreenView 是 DrawableSplashScreenView,继承自 ImageView,设置的图为 Drawable。splashScreenView = splashScreen.createSplashView(getContext(), splashScreenState);
        // 步骤 7、把 ImageView 增加到 FlutterSplashView 中。// 因为 FlutterSplashView 是 FrameLayout,所以 ImageView 盖在步骤 3 的 flutterView 之上。addView(this.splashScreenView);
        // 步骤 8、给 flutterView 增加监听回调,等第一帧绘制时触发。// 回调外面做的事实质就是从开屏过渡隐没到 flutterView 显示进去。flutterView.addOnFirstFrameRenderedListener(flutterUiDisplayListener);
      } else if (isSplashScreenTransitionNeededNow()) {Log.v(TAG, "Showing an immediate splash transition to Flutter due to previously interrupted transition.");
        // 步骤 9、同步骤 6、7 做的事件。splashScreenView = splashScreen.createSplashView(getContext(), splashScreenState);
        addView(splashScreenView);
        // 步骤 10、因为是中间状态,所以不必监听,间接增加后就从开屏过渡隐没到 flutterView 显示进去。transitionToFlutter();} else if (!flutterView.isAttachedToFlutterEngine()) {Log.v(TAG, "FlutterView is not yet attached to a FlutterEngine. Showing nothing until a FlutterEngine is attached.");
        // 步骤 11、如果这时候 flutter 引擎还没 attach 上。// 那就监听 attach,等 attach 上就开始追加开屏并显示,等到渲染第一帧开始就完结。flutterView.addFlutterEngineAttachmentListener(flutterEngineAttachmentListener);
      }
    }
  }

  // 单纯的判断 flutterView 是否渲染进去,没进去就阐明须要过渡界面。private boolean isSplashScreenNeededNow() {
    return flutterView != null
        && flutterView.isAttachedToFlutterEngine()
        && !flutterView.hasRenderedFirstFrame()
        && !hasSplashCompleted();}

  // 判断是否上一个过渡动画开屏正在进行中。private boolean isSplashScreenTransitionNeededNow() {
    return flutterView != null
        && flutterView.isAttachedToFlutterEngine()
        && splashScreen != null
        && splashScreen.doesSplashViewRememberItsTransition()
        && wasPreviousSplashTransitionInterrupted();}
  //......
  // 开屏过渡到 flutterview 显示
  private void transitionToFlutter() {
    //......
    // 步骤 12、splashScreen 就是 DrawableSplashScreen。// 实质就是 DrawableSplashScreenView(即 ImageView)做一个默认 500ms 的 alpha 突变通明动画。// 动画结束回调 onTransitionComplete 接口实现,从以后 FrameLayout 中删除开屏追加的 ImageView,child 只剩下 FlutterView。splashScreen.transitionToFlutter(onTransitionComplete);
  }
  //......
  // 等 attach 上后走进步骤 1 流程,不解释。@NonNull
  private final FlutterView.FlutterEngineAttachmentListener flutterEngineAttachmentListener =
      new FlutterView.FlutterEngineAttachmentListener() {
        @Override
        public void onFlutterEngineAttachedToFlutterView(@NonNull FlutterEngine engine) {flutterView.removeFlutterEngineAttachmentListener(this);
          displayFlutterViewWithSplash(flutterView, splashScreen);
        }
        //......
      };

  //flutterView 的第一帧绘制时触发,实质就是从开屏过渡隐没到 flutterView 显示进去。@NonNull
  private final FlutterUiDisplayListener flutterUiDisplayListener =
      new FlutterUiDisplayListener() {
        @Override
        public void onFlutterUiDisplayed() {if (splashScreen != null) {transitionToFlutter();
          }
        }
        //......
      };
  // 动画做完就移除开屏 view 控件。@NonNull
  private final Runnable onTransitionComplete =
      new Runnable() {
        @Override
        public void run() {removeView(splashScreenView);
          //......
        }
      };
  //......
}

看完下面代码你也就明确为什么咱们在 Android Studio 中查看 FlutterActivity 的安卓层级树时,只看到 Activity content 的 child 是 FlutterSplashView,FlutterSplashView 的 child 是 FlutterView,而 FlutterSplashView 的另一个 child DrawableSplashScreenView 不见的起因就是 500ms 动画之后被 remove 了。如下图:

FlutterTextureView 相干剖析

在后面系列文章中剖析 FlutterActivity 时咱们晓得,FlutterView 创立时依赖一个 FlutterTextureView 或者 FlutterSurfaceView,其判断条件的实质就是看 FlutterActivity 的 window 窗体背景是否通明(FlutterFragment 时通过 Arguments 的 flutterview_render_mode 参数来决定),不通明就是 surface,通明就是 texture。因而,咱们这里就是针对其 window 通明场景来剖析的。

// 步骤 13、在一个 SurfaceTexture 上绘制 Flutter UI,就是单纯的渲染,不解决点击等各种事件。// 想要开始渲染,FlutterTextureView 的持有者须要先调用 attachToRenderer(FlutterRenderer)。// 同理,想要终止渲染,FlutterTextureView 的持有者须要先调用 detachFromRenderer()。public class FlutterTextureView extends TextureView implements RenderSurface {
  //......
  // 步骤 14、次要是基于规范监听的 connectSurfaceToRenderer 和 disconnectSurfaceFromRenderer 操作。private final SurfaceTextureListener surfaceTextureListener =
      new SurfaceTextureListener() {
        @Override
        public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) {
          //......
          if (isAttachedToFlutterRenderer) {connectSurfaceToRenderer();
          }
        }
        //......
        @Override
        public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surface) {
          //......
          if (isAttachedToFlutterRenderer) {disconnectSurfaceFromRenderer();
          }
          return true;
        }
      };

  //......

  // 步骤 15、在 FlutterView 的 attachToFlutterEngine 办法中被调用。// 参数来自 FlutterEngine 的 getRenderer(),类型是 FlutterRenderer,外面实质是 SurfaceTexture。public void attachToRenderer(@NonNull FlutterRenderer flutterRenderer) {
    //......
    connectSurfaceToRenderer();
    //......
  }

  // 步骤 16、在 FlutterView 的 detachFromFlutterEngine 办法中被调用。// 与步骤 15 办法成对始终。public void detachFromRenderer() {
    //......
    disconnectSurfaceFromRenderer();
    //......
  }

  private void connectSurfaceToRenderer() {
    //......
    renderSurface = new Surface(getSurfaceTexture());
    flutterRenderer.startRenderingToSurface(renderSurface);
  }

  private void disconnectSurfaceFromRenderer() {
    //......
    flutterRenderer.stopRenderingToSurface();
    if (renderSurface != null) {renderSurface.release();
      renderSurface = null;
    }
  }
  //......
}

下面能够看到,FlutterTextureView 的实质就是一个规范的 TextureView,用法也齐全一样,只是渲染数据是通过 FlutterJNI 进行 engine 与 Android Java 层传递而已。

FlutterSurfaceView 相干剖析

与下面 FlutterTextureView 剖析同理,FlutterSurfaceView 天然就是针对其 window 不通明场景来剖析的。上面是相似下面概览源码:

// 步骤 17、在一个 Surface 上绘制 Flutter UI,就是单纯的渲染,不解决点击等各种事件。// 想要开始渲染,FlutterSurfaceView 的持有者须要先调用 attachToRenderer(FlutterRenderer)。// 同理,想要终止渲染,FlutterSurfaceView 的持有者须要先调用 detachFromRenderer()。public class FlutterSurfaceView extends SurfaceView implements RenderSurface {
  //......
  private final SurfaceHolder.Callback surfaceCallback =
      new SurfaceHolder.Callback() {
        @Override
        public void surfaceCreated(@NonNull SurfaceHolder holder) {
          //......
          connectSurfaceToRenderer();}
        //......
        @Override
        public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
          //......
          disconnectSurfaceFromRenderer();}
      };

  public void attachToRenderer(@NonNull FlutterRenderer flutterRenderer) {
    //......
    connectSurfaceToRenderer();}

  public void detachFromRenderer() {
    //......
    disconnectSurfaceFromRenderer();}

  private void connectSurfaceToRenderer() {
    //......
    flutterRenderer.startRenderingToSurface(getHolder().getSurface());
  }

  private void disconnectSurfaceFromRenderer() {
    //......
    flutterRenderer.stopRenderingToSurface();}
  //......
}

能够看到,不多解释,和 FlutterSurfaceView 根本一模一样。

FlutterRenderer 相干剖析

FlutterRenderer 的主要职责是通过 FlutterEngine 进行渲染关联解决,与原生平台提供的 FlutterSurfaceView、FlutterTextureView 进行纯 UI 渲染,将 Flutter 像素绘制到 Android 视图层次结构。

public class FlutterRenderer implements TextureRegistry {
  //......
  @NonNull private final FlutterJNI flutterJNI;
  @Nullable private Surface surface;
  //......
}

通过下面源码的两个属性成员就能看进去他的职责。联合下面大节能够失去一个如下职责形象架构图:

FlutterView 相干剖析

FlutterView 的作用是在 Android 设施上显示一个 Flutter UI,绘制内容来自于 FlutterEngine 提供。FlutterView 有两种不同的渲染模式(io.flutter.embedding.android.RenderMode#surfaceio.flutter.embedding.android.RenderMode#texture),其中 surface 模式的性能比拟高,然而在 z-index 上无奈与其余 Android View 进行布局,没法进行 animated、transformed 变换;而 texture 模式尽管性能没有 surface 高,然而没有 surface 的那些毛病限度。个别尽可能抉择 surface 模式,FlutterView 的默认结构器就是 surface 模式,FlutterActivity 的 window 不通明时默认也是 surface 模式,FlutterFragment 的默认无参数批改状况下也是 surface 模式,不信能够翻看本系列的后面相干文章。

上面咱们先看下 FlutterView 的成员和结构初始化相干流程,如下代码片段:

public class FlutterView extends FrameLayout implements MouseCursorPlugin.MouseCursorViewDelegate {
  // 用来真正渲染绘制视图的。@Nullable private FlutterSurfaceView flutterSurfaceView;
  @Nullable private FlutterTextureView flutterTextureView;
  @Nullable private FlutterImageView flutterImageView;
  @Nullable private RenderSurface renderSurface;
  @Nullable private RenderSurface previousRenderSurface;
  //......
  // 用来解决 Android View 的 input and events。@Nullable private MouseCursorPlugin mouseCursorPlugin;
  @Nullable private TextInputPlugin textInputPlugin;
  @Nullable private LocalizationPlugin localizationPlugin;
  @Nullable private AndroidKeyProcessor androidKeyProcessor;
  @Nullable private AndroidTouchProcessor androidTouchProcessor;
  @Nullable private AccessibilityBridge accessibilityBridge;
  
  // 缺省构造函数,默认模式为 surface,即 FlutterSurfaceView 渲染。public FlutterView(@NonNull Context context) {this(context, null, new FlutterSurfaceView(context));
  }
  // 省略一堆各种参数的构造函数
  //......
  // 实质就是指定一个 RenderSurface,即如下三者之一。private void init() {if (flutterSurfaceView != null) {addView(flutterSurfaceView);
    } else if (flutterTextureView != null) {addView(flutterTextureView);
    } else {addView(flutterImageView);
    }

    //FlutterView 本人须要能接管事件。setFocusable(true);
    setFocusableInTouchMode(true);
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {setImportantForAutofill(View.IMPORTANT_FOR_AUTOFILL_YES_EXCLUDE_DESCENDANTS);
    }
  }
  //......
}

通过下面代码咱们能够晓得,FlutterView 其实就是一个一般的 Android FrameLayout,其外部根据条件被 addView 了一个 View,这个 View 都实现自 RenderSurface 接口,也就是 FlutterSurfaceView、FlutterTextureView、FlutterImageView 之一,默认为 FlutterSurfaceView 而已。所以说真正绘制渲染 FlutterEngine 数据的不是 FlutterView,而是实现 RenderSurface 接口的控件,譬如 FlutterSurfaceView。整体 View 层级关系如下图:

结构完 FlutterView 实例后,咱们通过后面的系列文章能够晓得,在 FlutterActivityAndFragmentDelegate 的 onCreateView 办法返回给 FlutterActivity 一个 contentView 前 FlutterView 有通过本人的 attachToFlutterEngine 办法与 FlutterEngine 关联,所以咱们看下这个关联办法(对应还有一个 detachFromFlutterEngine 办法进行勾销关联):

public class FlutterView extends FrameLayout implements MouseCursorPlugin.MouseCursorViewDelegate {
  //......
  public void attachToFlutterEngine(@NonNull FlutterEngine flutterEngine) {
    //......
    // 赋值 flutterEngine。this.flutterEngine = flutterEngine;

    // 从 flutterEngine 引擎获取 flutterRenderer 实例。FlutterRenderer flutterRenderer = this.flutterEngine.getRenderer();
    //renderSurface 进行 attachToRenderer,实质譬如就是 FlutterSurfaceView 的 attachToRenderer 办法。renderSurface.attachToRenderer(flutterRenderer);
    // 初始化各种 plugin。if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
      // 鼠标相干插件。mouseCursorPlugin = new MouseCursorPlugin(this, this.flutterEngine.getMouseCursorChannel());
    }
    // 输出相干插件。textInputPlugin =
        new TextInputPlugin(this, this.flutterEngine.getTextInputChannel(), this.flutterEngine.getPlatformViewsController());
    //config 本地变更等插件。localizationPlugin = this.flutterEngine.getLocalizationPlugin();
    //key 及 touch 事件、accessibility 辅助模式相干 channel 通道解决。androidKeyProcessor =
        new AndroidKeyProcessor(this, this.flutterEngine.getKeyEventChannel(), textInputPlugin);
    androidTouchProcessor =
        new AndroidTouchProcessor(this.flutterEngine.getRenderer(), /*trackMotionEvents=*/ false);
    accessibilityBridge =
        new AccessibilityBridge(
            this,
            flutterEngine.getAccessibilityChannel(),
            (AccessibilityManager) getContext().getSystemService(Context.ACCESSIBILITY_SERVICE),
            getContext().getContentResolver(),
            this.flutterEngine.getPlatformViewsController());
    accessibilityBridge.setOnAccessibilityChangeListener(onAccessibilityChangeListener);
    // 各种平台相干事件初始调度。this.flutterEngine.getPlatformViewsController().attachAccessibilityBridge(accessibilityBridge);
    this.flutterEngine
        .getPlatformViewsController()
        .attachToFlutterRenderer(this.flutterEngine.getRenderer());

    textInputPlugin.getInputMethodManager().restartInput(this);

    sendUserSettingsToFlutter();
    localizationPlugin.sendLocalesToFlutter(getResources().getConfiguration());
    sendViewportMetricsToFlutter();

    flutterEngine.getPlatformViewsController().attachToView(this);
    //......
  }
}

能够看到,FlutterView 与 FlutterEngine 进行 attach 时次要做的事件就是回调设置、渲染关联、零碎平台 plugin 初始化关联等。下面的各种 plugin 咱们能够先不必关怀细节,晓得 attachToFlutterEngine 次要做这些事件即可,前面会专门剖析。

接着咱们依照规范 Android 平台的 View 次要办法进行分类剖析,先看看 FlutterView 的 onConfigurationChanged 办法,如下:

public class FlutterView extends FrameLayout implements MouseCursorPlugin.MouseCursorViewDelegate {
  //......
  @Nullable private LocalizationPlugin localizationPlugin;

  @Override
  protected void onConfigurationChanged(@NonNull Configuration newConfig) {super.onConfigurationChanged(newConfig);
    // 响应零碎屏幕渲染或者配置发生变化,譬如分屏、暗黑、多语言啥的。if (flutterEngine != null) {Log.v(TAG, "Configuration changed. Sending locales and user settings to Flutter.");
      // 调用 LocalizationPlugin 插件设置变更后新的 Configuration。localizationPlugin.sendLocalesToFlutter(newConfig);
      // 把变更发送到 FlutterEngine 去,告诉引擎。sendUserSettingsToFlutter();}
  }

  void sendUserSettingsToFlutter() {
    // 以后是不是暗黑模式。boolean isNightModeOn = (getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES;
    SettingsChannel.PlatformBrightness brightness = isNightModeOn
            ? SettingsChannel.PlatformBrightness.dark : SettingsChannel.PlatformBrightness.light;
    // 通过 flutterEngine 的 SettingsChannel 发送变更音讯。flutterEngine
        .getSettingsChannel()
        .startMessage()
        .setTextScaleFactor(getResources().getConfiguration().fontScale)
        .setUse24HourFormat(DateFormat.is24HourFormat(getContext()))
        .setPlatformBrightness(brightness)
        .send();}
  //......
}

能够看到,当系统配置产生变更时 FlutterView 本人在安卓端其实不做什么事的,次要就是负责把事件告诉到 flutterEngine 端去,而后 flutterEngine 再传递到 dart 响应,从而触发新的绘制刷新成果。

因为整体都是这个模式,所以 FlutterView 中的非典型办法咱们不再剖析,类比即可。上面咱们看下事件是怎么派发的,如下:

@Override
public boolean dispatchKeyEvent(KeyEvent event) {
  //......
  return (isAttachedToFlutterEngine() && androidKeyProcessor.onKeyEvent(event))
      || super.dispatchKeyEvent(event);
}

@Override
public boolean onTouchEvent(@NonNull MotionEvent event) {
  //......
  return androidTouchProcessor.onTouchEvent(event);
}

啥感觉?androidTouchProcessor 实例就是后面剖析的 FlutterView 中 attachToFlutterEngine 办法里实例化的,实质就是通过 flutterEngine 的 KeyEventChannel 进行事件散发。到此也就应证了咱们后面说的,FlutterView 只是一个在安卓端治理的 View,外部的渲染有专门的 View 负责,外部的事件全副通过原生散发到 flutterEngine 进行 dart 代码的触发解决,而后交回原生平台渲染。以 FlutterSurfaceView 为例整体交互流程图很像上面这样:

通过如上超级形象图其实咱们就大略明确了 Flutter 框架的精华(当然,细节还是很简单的),也印证了一个纯 Flutter Android App 在原生平台侧的层级构造是上面这样:

FlutterImageView 相干剖析

剖析完 FlutterRenderer、FlutterSurfaceView、FlutterTextureView 及 FlutterView 之后咱们再来看看 FlutterImageView,其实他和下面的 FlutterSurfaceView 等工作流程很像,也是 FlutterView 外部的一种绘制成载体,只是有一些本人的独有特点。FlutterImageView 的次要作用是通过 android.media.ImageReader 把 Flutter UI 绘制到 android.graphics.Canvas 上。FlutterView 中 addView 为 FlutterImageView 的形式其实有两种,一种是后面介绍过的,通过 FlutterView 结构函数参数为 FlutterImageView 的办法实现,另一种是通过调用 FlutterView 中的 convertToImageView 办法实现。上面是 FlutterImageView 源码中的外围片段:

@TargetApi(19)
public class FlutterImageView extends View implements RenderSurface {
  //......
  // 原生控件的绘制操作
  @Override
  protected void onDraw(Canvas canvas) {super.onDraw(canvas);
    // 绘制前先更新 bitmap 数据源
    if (currentImage != null) {updateCurrentBitmap();
    }
    // 把 bitmap 画到 canvas 下面
    if (currentBitmap != null) {canvas.drawBitmap(currentBitmap, 0, 0, null);
    }
  }

  @TargetApi(29)
  private void updateCurrentBitmap() {if (android.os.Build.VERSION.SDK_INT >= 29) {final HardwareBuffer buffer = currentImage.getHardwareBuffer();
      currentBitmap = Bitmap.wrapHardwareBuffer(buffer, ColorSpace.get(ColorSpace.Named.SRGB));
      buffer.close();} else {final Plane[] imagePlanes = currentImage.getPlanes();
      if (imagePlanes.length != 1) {return;}

      final Plane imagePlane = imagePlanes[0];
      final int desiredWidth = imagePlane.getRowStride() / imagePlane.getPixelStride();
      final int desiredHeight = currentImage.getHeight();

      if (currentBitmap == null
          || currentBitmap.getWidth() != desiredWidth
          || currentBitmap.getHeight() != desiredHeight) {
        currentBitmap =
            Bitmap.createBitmap(desiredWidth, desiredHeight, android.graphics.Bitmap.Config.ARGB_8888);
      }
      ByteBuffer buffer = imagePlane.getBuffer();
      buffer.rewind();
      currentBitmap.copyPixelsFromBuffer(buffer);
    }
  }
  //......
}

能够看到,FlutterImageView 是一个一般原生 View,也实现了 RenderSurface 接口从而实现相似 FlutterSurfaceView 的个性。它的存在次要是解决咱们既须要渲染一个 Flutter UI 又想同时渲染一个 PlatformView(对于 PlatformView 咱们前面会有专题文章)的场景,因为 PlatformView 默认实现是在原生 FlutterView 上进行 addView 操作,当咱们想在 PlatformView 上持续盖一个 Flutter 本人渲染的控件就须要应用 FlutterImageView,通过 FlutterImageView 实现了 Surface(ImageReader)和 Surface 的重叠。

总结

通过这么一个篇幅的剖析,咱们能够简略粗犷的总结为下图模式:

这下你懂了吗?

正文完
 0