Android图形显示系统4 图像生产者(下)

2023-11-17

一 概述

在上一篇文章 Android图形显示系统2 图像消费者 中,我们详细地讲解了图像消费者,我们已经了解了 Android 中的图像元数据是如何被 SurfaceFlinger,HWComposer 或者 OpenGL ES 消费的,那么,图像元数据又是怎么生成的呢?这一篇文章就来详细介绍 Android 中的图像生产者—— SKIA,OPenGL ES,Vulkan,他们是 Android 中最重要的三支画笔。

二 Skia

Skia 是谷歌开源的一款跨平台的 2D 图形引擎,目前谷歌的 Chrome 浏览器、Android、Flutter、以及火狐浏览器、火狐操作系统和其它许多产品都使用它作为图形引擎,它作为 Android 系统第三方软件,放在 external/skia/ 目录下。虽然 Android 从 4.0 开始默认开启了硬件加速,但不代表 Skia 的作用就不大了,其实 Skia 在 Android 中的地位是越来越重要了,从 Android 8 开始,我们可以选择使用 Skia 进行硬件加速,Android 9 开始就默认使用 Skia 来进行硬件加速。Skia 的硬件加速主要是通过 copybit 模块调用 OpenGL 或者 SKia 来实现。

在这里插入图片描述
由于 Skia 的硬件加速也是通过 Copybit 模块调用的 OpenGL 或者 Vulkan 接口,所以我们这儿只说说 Skia 通过 cpu 绘制的,也就是软绘的方式。还是老规则,先看看 Skia 要如何使用

2.1 如何使用Skia

OpenGL ES 的使用要配合 EGL,需要初始化 Display,surface,context 等,用法还是比较繁琐的,Skia 在使用上就方便很多了。掌握 Skia 绘制三要素:画板 SkCanvas、画纸 SkBitmap、画笔 SkPaint,我们就能很轻松的用 Skia 来绘制图形。

下面详细的解释 Skia 的绘图三要素

1.SkBitmap 用来存储图形数据,它封装了与位图相关的一系列操作

SkBitmap bitmap = new SkBitmap();
//设置位图格式及宽高
bitmap->setConfig(SkBitmap::kRGB_565_Config,800,480);
//分配位图所占空间
bitmap->allocPixels();

2.SkCanvas 封装了所有画图操作的函数,通过调用这些函数,我们就能实现绘制操作。

//使用前传入bitmap
SkCanvas canvas(bitmap);
//移位,缩放,旋转,变形操作
translate(SkiaScalar dx, SkiaScalar dy);
scale(SkScalar sx, SkScalar sy);
rotate(SkScalar degrees);
skew(SkScalar sx, SkScalar sy);
//绘制操作
drawARGB(u8 a, u8 r, u8 g, u8 b....)  //给定透明度以及红,绿,兰3色,填充整个可绘制区域。
drawColor(SkColor color...) //给定颜色color, 填充整个绘制区域。
drawPaint(SkPaint& paint) //用指定的画笔填充整个区域。
drawPoint(...)//根据各种不同参数绘制不同的点。
drawLine(x0, y0, x1, y1, paint) //画线,起点(x0, y0), 终点(x1, y1), 使用paint作为画笔。
drawRect(rect, paint) //画矩形,矩形大小由rect指定,画笔由paint指定。
drawRectCoords(left, top, right, bottom, paint),//给定4个边界画矩阵。
drawOval(SkRect& oval, SkPaint& paint) //画椭圆,椭圆大小由oval矩形指定。
//……其他操作

3.SkPaint 用来设置绘制内容的风格,样式,颜色等信息

setAntiAlias: 设置画笔的锯齿效果。 
setColor: 设置画笔颜色 
setARGB:  设置画笔的a,r,p,g值。 
setAlpha:  设置Alpha值 
setTextSize: 设置字体尺寸。 
setStyle:  设置画笔风格,空心或者实心。 
setStrokeWidth: 设置空心的边框宽度。 
getColor:  得到画笔的颜色 
getAlpha:  得到画笔的Alpha值。 

我们看一个完整的使用 Demo

void draw() {
    SkBitmap bitmap = new SkBitmap();
    //设置位图格式及宽高
    bitmap->setConfig(SkBitmap::kRGB_565_Config,800,480);
    //分配位图所占空间
    bitmap->allocPixels();
    //使用前传入bitmap
    SkCanvas canvas(bitmap);
    //定义画笔
    SkPaint paint1, paint2, paint3;

    paint1.setAntiAlias(true);
    paint1.setColor(SkColorSetRGB(255, 0, 0));
    paint1.setStyle(SkPaint::kFill_Style);

    paint2.setAntiAlias(true);
    paint2.setColor(SkColorSetRGB(0, 136, 0));
    paint2.setStyle(SkPaint::kStroke_Style);
    paint2.setStrokeWidth(SkIntToScalar(3));

    paint3.setAntiAlias(true);
    paint3.setColor(SkColorSetRGB(136, 136, 136));

    sk_sp<SkTextBlob> blob1 =
            SkTextBlob::MakeFromString("Skia!", SkFont(nullptr, 64.0f, 1.0f, 0.0f));
    sk_sp<SkTextBlob> blob2 =
            SkTextBlob::MakeFromString("Skia!", SkFont(nullptr, 64.0f, 1.5f, 0.0f));

    canvas->clear(SK_ColorWHITE);
    canvas->drawTextBlob(blob1.get(), 20.0f, 64.0f, paint1);
    canvas->drawTextBlob(blob1.get(), 20.0f, 144.0f, paint2);
    canvas->drawTextBlob(blob2.get(), 20.0f, 224.0f, paint3);
}

这个 Demo 的效果如下:
在这里插入图片描述
了解了 Skia 如何使用,我们接着看两个场景:Skia 进行软件绘制,Flutter 界面绘制

2.2 Skia进行软件绘制

在上一篇文章中我们讲了通过使用 OpenGL 渲染的硬件绘制方式,这里会接着讲使用 Skia 渲染的软件绘制方式,虽然 Android 默认开启了硬件加速,但是由于硬件加速会有耗电和内存的问题,一些系统应用和常驻应用依然是使用的软件绘制的方式,软绘入口还是在 draw 方法中。

//文件-->/frameworks/base/core/java/android/view/ViewRootImpl.java
private void performDraw() {
    ......
    draw(fullRedrawNeeded);
    ......
}

private void draw(boolean fullRedrawNeeded) {
    Surface surface = mSurface;
    if (!surface.isValid()) {
        return;
    }
    ......
    if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
        if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
            if (mAttachInfo.mThreadedRenderer != null &&
            mAttachInfo.mThreadedRenderer.isEnabled()) {                
                ......
                //硬件渲染
           mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);

            } else {               
                ......
                //软件渲染
                if (!drawSoftware(surface, mAttachInfo, xOffset,
                    yOffset, scalingRequired, dirty)) {
                    return;
                }
            }
        }
        ......
    }
    ......
}

我们来看看 drawSoftware 函数的实现

private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
        boolean scalingRequired, Rect dirty) {

    // Draw with software renderer.
    final Canvas canvas;
    ......
    canvas = mSurface.lockCanvas(dirty);    
    ......     
    mView.draw(canvas);    
    ......    
    surface.unlockCanvasAndPost(canvas);    
    ......    
    return true;
}

drawSoftware 函数的流程主要为三步:

  • 通过 mSurface.lockCanvas 获取 Canvas
  • 通过 draw 方法,将根 View 及其子 View 遍历绘制到 Canvas 上
  • 通过 surface.unlockCanvasAndPost 将绘制内容提交给 surfaceFlinger 进行合成

2.2.1 Lock Surface

我们先来看第一步,这个 Canvas 对应着 Native 层的 SkCanvas。

//文件-->/frameworks/base/core/java/android/view/Surface.java
public Canvas lockCanvas(Rect inOutDirty)
    throws Surface.OutOfResourcesException, IllegalArgumentException {
    synchronized (mLock) {
        checkNotReleasedLocked();
        if (mLockedObject != 0) {           
            throw new IllegalArgumentException("Surface was already locked");
        }
        mLockedObject = nativeLockCanvas(mNativeObject, mCanvas, inOutDirty);
        return mCanvas;
    }
}

lockCanvas 函数中通过 JNI 函数 nativeLockCanvas,创建 Nativce 层的 Canvas,nativeLockCanvas 的入参 mNativeObject 对应着 Native 层的 Surface,关于 Surface 和 Buffer 的知识,在下一篇图形缓冲区中会详细简介,这里不做太多介绍。我们直接看 nativeLockCanvas 的实现。

static jlong nativeLockCanvas(JNIEnv* env, jclass clazz,
        jlong nativeObject, jobject canvasObj, jobject dirtyRectObj) {
    sp<Surface> surface(reinterpret_cast<Surface *>(nativeObject));

    if (!isSurfaceValid(surface)) {
        doThrowIAE(env);
        return 0;
    }

    Rect dirtyRect(Rect::EMPTY_RECT);
    Rect* dirtyRectPtr = NULL;

    if (dirtyRectObj) {
        dirtyRect.left   = env->GetIntField(dirtyRectObj, gRectClassInfo.left);
        dirtyRect.top    = env->GetIntField(dirtyRectObj, gRectClassInfo.top);
        dirtyRect.right  = env->GetIntField(dirtyRectObj, gRectClassInfo.right);
        dirtyRect.bottom = env->GetIntField(dirtyRectObj, gRectClassInfo.bottom);
        dirtyRectPtr = &dirtyRect;
    }

    ANativeWindow_Buffer outBuffer;
    //关键点1,获取用来存储图形绘制的buffer
    status_t err = surface->lock(&outBuffer, dirtyRectPtr);
    if (err < 0) {
        const char* const exception = (err == NO_MEMORY) ?
                OutOfResourcesException :
                "java/lang/IllegalArgumentException";
        jniThrowException(env, exception, NULL);
        return 0;
    }

    SkImageInfo info = 
    SkImageInfo::Make(outBuffer.width, outBuffer.height,
          convertPixelFormat(outBuffer.format),
          outBuffer.format == PIXEL_FORMAT_RGBX_8888
          ? kOpaque_SkAlphaType : kPremul_SkAlphaType,
          GraphicsJNI::defaultColorSpace());

    SkBitmap bitmap;
    ssize_t bpr = outBuffer.stride * bytesPerPixel(outBuffer.format);
    bitmap.setInfo(info, bpr);
   
    if (outBuffer.width > 0 && outBuffer.height > 0) {
        //将上一个buffer里的图形数据复制到当前bitmap中
        bitmap.setPixels(outBuffer.bits);
    } else {
        // be safe with an empty bitmap.
        bitmap.setPixels(NULL);
    }

    //关键点2,创建一个SKCanvas
    Canvas* nativeCanvas = GraphicsJNI::getNativeCanvas(env, canvasObj);
    //关键点3,给SKCanvas设置Bitmap
    nativeCanvas->setBitmap(bitmap);
    //如果指定了脏区,则设定脏区的区域
    if (dirtyRectPtr) {
        nativeCanvas->clipRect(dirtyRect.left, dirtyRect.top,
                dirtyRect.right, dirtyRect.bottom, SkClipOp::kIntersect);
    }

    if (dirtyRectObj) {
        env->SetIntField(dirtyRectObj, gRectClassInfo.left,   dirtyRect.left);
        env->SetIntField(dirtyRectObj, gRectClassInfo.top,    dirtyRect.top);
        env->SetIntField(dirtyRectObj, gRectClassInfo.right,  dirtyRect.right);
        env->SetIntField(dirtyRectObj, gRectClassInfo.bottom, dirtyRect.bottom);
    }

    sp<Surface> lockedSurface(surface);
    lockedSurface->incStrong(&sRefBaseOwner);
    return (jlong) lockedSurface.get();
}

nativeLockCanvas 主要做了这几件事情:

  • 通过 surface->lock 函数获取绘制用的 Buffer
  • 根据 Buffer 信息创建 SkBitmap
  • 根据 SkBitmap,创建并初始化 SkCanvas

通过 nativeLockCanvas,我们就创建好 SkCanvas 了,并且设置了可以绘制图形的 SkBitmap,此时我们就可以通过 SkCanvas 往 bitmap 里面绘制图形,mView.draw() 函数,就做了这件事情。

2.2.2 绘制

我们接着看看 View 中的 draw() 函数

//文件-->/frameworks/base/core/java/android/view/View.java
public void draw(Canvas canvas) {
    final int privateFlags = mPrivateFlags;
    final boolean dirtyOpaque = 
    (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
            (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
    mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;

    int saveCount;
    //1,绘制背景
    if (!dirtyOpaque) {
        drawBackground(canvas);
    }

    final int viewFlags = mViewFlags;
    boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
    boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
    if (!verticalEdges && !horizontalEdges) {
        // 2,绘制当前view的图形
        if (!dirtyOpaque) onDraw(canvas);

        // 3,绘制子view的图形
        dispatchDraw(canvas);

        drawAutofilledHighlight(canvas);

        // Overlay is part of the content and draws beneath Foreground
        if (mOverlay != null && !mOverlay.isEmpty()) {
            mOverlay.getOverlayView().dispatchDraw(canvas);
        }

        //4,绘制decorations,如滚动条,
        // 前景等 Step 6, draw decorations (foreground, scrollbars)
        onDrawForeground(canvas);

        // 5,绘制焦点的高亮
        drawDefaultFocusHighlight(canvas);

        if (debugDraw()) {
            debugDrawFocus(canvas);
        }

        // we're done...
        return;
    }
    ......
}

draw 函数中做了这几件事情:

  • 绘制背景
  • 绘制当前 view
  • 遍历绘制子 view
  • 绘制前景

我们可以看看 Canvas 里的绘制方法,这些绘制方法都是 JNI 方法,并且一一对应着 SkCanvas 中的绘制方法

//文件-->/frameworks/base/graphics/java/android/graphics/Canvas.java
......
private static native void nDrawBitmap(long nativeCanvas, ......);

private static native void nDrawColor(long nativeCanvas, ......);

private static native void nDrawPaint(long nativeCanvas, ......);

private static native void nDrawPoint(long canvasHandle, ......);

private static native void nDrawPoints(long canvasHandle, ......);

private static native void nDrawLine(long nativeCanvas, ......);

private static native void nDrawLines(long canvasHandle, ......);

private static native void nDrawRect(long nativeCanvas, ......);

private static native void nDrawOval(long nativeCanvas, ......);

private static native void nDrawCircle(long nativeCanvas, ......);

private static native void nDrawArc(long nativeCanvas, ......);

private static native void nDrawRoundRect(long nativeCanvas, ......);
......

2.2.3 Post Surface

软件绘制的最后一步,通过 surface.unlockCanvasAndPost 将绘制内容提交给 surfaceFlinger 合成,将绘制出来的图形提交给 SurfaceFlinger,然后 SurfaceFlinger 作为消费者处理图形后,我们的界面就显示出来了。

public void unlockCanvasAndPost(Canvas canvas) {
    synchronized (mLock) {
        checkNotReleasedLocked();

        if (mHwuiContext != null) {
            mHwuiContext.unlockAndPost(canvas);
        } else {
            unlockSwCanvasAndPost(canvas);
        }
    }
}

private void unlockSwCanvasAndPost(Canvas canvas) {
    if (canvas != mCanvas) {
        throw new IllegalArgumentException(
        "canvas object must be the same instance that "
         + "was previously returned by lockCanvas");
    }
    if (mNativeObject != mLockedObject) {
        Log.w(TAG, "WARNING: Surface's mNativeObject (0x" +
              Long.toHexString(mNativeObject) + ") != mLockedObject (0x" +
              Long.toHexString(mLockedObject) +")");
    }
    if (mLockedObject == 0) {
        throw new IllegalStateException("Surface was not locked");
    }
    try {
        nativeUnlockCanvasAndPost(mLockedObject, canvas);
    } finally {
        nativeRelease(mLockedObject);
        mLockedObject = 0;
    }
}

这里调用了 Native 函数 nativeUnlockCanvasAndPost,我们接着往下看。

static void nativeUnlockCanvasAndPost(JNIEnv* env, jclass clazz,
        jlong nativeObject, jobject canvasObj) {
    sp<Surface> surface(reinterpret_cast<Surface *>(nativeObject));
    if (!isSurfaceValid(surface)) {
        return;
    }

    // detach the canvas from the surface
    Canvas* nativeCanvas = GraphicsJNI::getNativeCanvas(env, canvasObj);
    nativeCanvas->setBitmap(SkBitmap());

    // unlock surface
    status_t err = surface->unlockAndPost();
    if (err < 0) {
        doThrowIAE(env);
    }
}

在这里,surface->unlockAndPost() 函数就会将 Skia 绘制出来的图像传递给 SurfaceFlinger 进行合成。通过 skia 进行软件绘制的流程已经讲完了,至于如何通过 Surface 获取缓冲区,在缓冲区绘制完数据后,surface->unlockAndPost() 又如何通知 SurfaceFlinger,这一点在下一篇文章的图形缓冲区中会详细的讲解。

可以看到,Skia 软件绘制的流程比硬件绘制要简单很多,我们接着看看 Skia 进行 Flutter 绘制。

2.3 Skia进行Flutter的界面绘制

在讲解 Flutter 如何通过 Skia 生产图像之前,先简单介绍一下 Flutter,Flutter 的架构分为 Framework层,Engine 层和 Embedder 三层。

  • Framework 层使用 dart 语言实现,包括 UI,文本,图片,按钮等 Widgets,渲染,动画,手势等
  • Engine 使用 C++ 实现,主要包括渲染引擎 Skia,Dart 虚拟机和文字排版 Text 等模块
  • Embedder 是一个嵌入层,通过该层把 Flutter 嵌入到各个平台上去,Embedder 的主要工作包括渲染 Surface 设置, 线程设置,以及插件等

在这里插入图片描述
了解了 Flutter 的架构,我们接着了解 Flutter 显示一个界面的流程。我们知道在 Android 中,显示一个界面需要将 XML 界面布局解析成 ViewGroup,然后再经过测量 Measure,布局 Layout 和绘制 Draw 的流程。Flutter 和 Android 的显示不太一样,它会将通过 Dart 语言编写的 Widget 界面布局转换成 ElementTree 和 Render ObjectTree。ElementTree 相当于是 ViewGroup,Render ObjectTree 相当于是经过 Measure 和 Layout 流程之后的 ViewGroup。这种模式在很多场景上都有使用,比如 Webview,在渲染界面时,也会创建一颗 Dom 树,render 树和 RenderObject,这样的好处是可以通过 Diff 比较改变过的组件,然后渲染时,只对改变过的组件做渲染,同时对跨平台友好,可以通过这种树的形式来抽象出不同平台的公共部分。

在这里插入图片描述
讲完了上面两个背景,我们直接来看 Flutter 是如何使用 Skia 来绘制界面的。

下面是一个 Flutter 页面的 Demo

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

这个页面是一个 WidgetTree,相当于我们 Activity 的 xml,widget 树会转换成 ElementTree 和 RenderObjectTree,我们看看入口函数 runApp 时如何进行树的转换的。

//文件-->/packages/flutter/lib/src/widgets
void runApp(Widget app) {
  WidgetsFlutterBinding.ensureInitialized()
    ..scheduleAttachRootWidget(app)
    ..scheduleWarmUpFrame();
}

void scheduleAttachRootWidget(Widget rootWidget) {
    Timer.run(() {
        attachRootWidget(rootWidget);
    });
}

void attachRootWidget(Widget rootWidget) {
    _readyToProduceFrames = true;
    _renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(
        container: renderView,
        debugShortDescription: '[root]',
        child: rootWidget,
    ).attachToRenderTree(buildOwner, 
    renderViewElement as RenderObjectToWidgetElement<RenderBox>);
}

接着看 attachToRenderTree 函数

RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, 
[RenderObjectToWidgetElement<T> element]) {
    if (element == null) {
      owner.lockState(() {
        element = createElement();  //创建rootElement
        element.assignOwner(owner); //绑定BuildOwner
      });
      owner.buildScope(element, () { //子widget的初始化从这里开始
        element.mount(null, null);  // 初始化子Widget前,先执行rootElement的mount方法
      });
    } else {
      ...
    }
    return element;
  }

void mount(Element parent, dynamic newSlot) {
  super.mount(parent, newSlot);
  _renderObject = widget.createRenderObject(this);
  attachRenderObject(newSlot);
  _dirty = false;
}

从代码中可以看到,Widget 都被转换成了 Element,Element 接着调用了 mount 方法,在 mount 方法中,可以看到 Widget 又被转换成了 RenderObject,此时 Widget Tree 的 ElementTree 和 RenderObject 便都生成完了。

前面提到了 RenderObject 类似于经过了 Measure 和 Layout 流程的 ViewGroup,RenderObject 的 Measure 和 Layout 就不在这儿说了,那么还剩一个流程 Draw 流程,同样是在 RenderObject 中进行的,它的入口在 RenderObject 的 paint 函数中。

// 绘制入口,从 view 根节点开始,逐个绘制所有子节点
@override
  void paint(PaintingContext context, Offset offset) {
    if (child != null)
      context.paintChild(child, offset);
  }

可以看到,RenderObject 通过 PaintingContext 来进行了图形的绘制,我们接着来了解一下PaintingContext 是什么。

//文件-->/packages/flutter/lib/src/rendering/object.dart

import 'dart:ui' as ui show PictureRecorder;

class PaintingContext extends ClipContext {
  @protected
  PaintingContext(this._containerLayer, this.estimatedBounds)
 
  final ContainerLayer _containerLayer;
  final Rect estimatedBounds;
  
  PictureLayer _currentLayer;
  ui.PictureRecorder _recorder;
  Canvas _canvas;
 
  @override
  Canvas get canvas {
    if (_canvas == null)
      _startRecording();
    return _canvas;
  }
 
  void _startRecording() {
    _currentLayer = PictureLayer(estimatedBounds);
    _recorder = ui.PictureRecorder();
    _canvas = Canvas(_recorder);
    _containerLayer.append(_currentLayer);
  }
  
   void stopRecordingIfNeeded() {
    if (!_isRecording)
      return;
    _currentLayer.picture = _recorder.endRecording();
    _currentLayer = null;
    _recorder = null;
    _canvas = null;
}

可以看到,PaintingContext 是绘制的上下文,前面讲 OpenGL 进行硬件加速时提到的 CanvasContext,它也是绘制的上下文,里面封装了 Skia,Opengl 或者 Vulkan 的渲染管线。这里的 PaintingContext 则封装了 Skia。

我们可以通过 CanvasContext 的 get canvas 函数获取 Canvas,它调用了 _startRecording 函数,函数中创建了 PictureRecorder 和 Canvas,这两个类都是位于 dart:ui 库中,dart:ui 位于 engine 层,在前面架构中提到,Flutter 分为 Framewrok,Engine 和 embened 三层,Engine 中包含了 Skia,dart 虚拟机和 Text。dart:ui 就是位于 Engine 层的。

我们接着去 Engine 层的代码看看 Canvas 的实现。

//文件-->engine-master\lib\ui\canvas.dart  
Canvas(PictureRecorder recorder, [ Rect? cullRect ]) : 
assert(recorder != null) { // ignore: unnecessary_null_comparison
    if (recorder.isRecording)
      throw ArgumentError('"recorder" must not already be '+
      'associated with another Canvas.');
    _recorder = recorder;
    _recorder!._canvas = this;
    cullRect ??= Rect.largest;
    _constructor(recorder, cullRect.left, cullRect.top,
    cullRect.right, cullRect.bottom);
  }
void _constructor(PictureRecorder recorder,
                  double left,
                  double top,
                  double right,
                  double bottom) native 'Canvas_constructor';

这里 Canvas 调用了 Canvas_constructor 这一个 native 方法,我们接着看这个 native 方法的实现。

//文件-->engine-master\lib\ui\painting\engine.cc
static void Canvas_constructor(Dart_NativeArguments args) {
  UIDartState::ThrowIfUIOperationsProhibited();
  DartCallConstructor(&Canvas::Create, args);
}
fml::RefPtr<Canvas> Canvas::Create(PictureRecorder* recorder,
                                   double left,
                                   double top,
                                   double right,
                                   double bottom) {
  if (!recorder) {
    Dart_ThrowException(
        ToDart("Canvas constructor called with non-genuine PictureRecorder."));
    return nullptr;
  }
  fml::RefPtr<Canvas> canvas = fml::MakeRefCounted<Canvas>(
      recorder->BeginRecording(SkRect::MakeLTRB(left, top, right, bottom)));
  recorder->set_canvas(canvas);
  return canvas;
}

Canvas::Canvas(SkCanvas* canvas) : canvas_(canvas) {}

可以看到,这里通过 PictureRecorder->BeginRecording 创建了 SkCanvas,这其实是 SkCanvas 的另外一种使用方式,这里我简单的介绍一个 demo。

Picture createSolidRectanglePicture(
  Color color, double width, double height)
{guanjiandguanjiand

  PictureRecorder recorder = PictureRecorder();
  Canvas canvas = Canvas(recorder);

  Paint paint = Paint();
  paint.color = color;

  canvas.drawRect(Rect.fromLTWH(0, 0, width, height), paint);
  return recorder.endRecording();
}

这个 demo 的效果如下图,它创建 Skia 的方式就和 Flutter 创建 Skia 的方式是一样的。
在这里插入图片描述
此时,我们的 SkCanvas 创建好了,并且直接通过 PaintingContext 的 get canvas 函数就能获取到,那么获取到 SkCanvas 后直接调用 Canvas 的绘制 api,就可以将图像绘制出来了。

Flutter 界面显示的全流程是比较复杂的,Flutter 完全是自建的一套图像显示流程,无法通过 Android 的 SurfaceFlinger 进行图像合成,也无法使用 Android 的 Gralloc 模块分配图像缓冲区,所以它需要有自己的图像生产者,有自己的图形消费者,也有自己的图形缓冲区,这里面就有非常多的流程,比如如何接收 VSync,如何处理及合成 Layer,如何创建图像缓冲区,这里只是对 Flutter 的图像生产者的部分做了一个初步的介绍,关于 Flutter 更深入一步的细节,就不在这里继续讲解了。后面我会专门写一系列文章来详细讲解 Flutter。

三 Vulkan

与 OpenGL 相比,Vulkan 可以更详细的向显卡描述你的应用程序打算做什么,从而可以获得更好的性能和更小的驱动开销,作为 OpenGL 的替代者,它设计之初就是为了跨平台实现的,可以同时在 Windows、Linux 和 Android 开发。甚至在 Mac OS 系统上运行。Android 在 7.0 开始,便增加了对 Vulkan 的支持,Vulkan 一定是未来的趋势,因为它比 OpenGL 的性能更好更强大。下面我们就了解一下,如何使用 Vulkan 来生产图像。

3.1 如何使用Vulkan

Vulkan 的使用和 OpenGL 类似,同样是三步:初始化,绘制,提交 buffer。下面来看一下具体的流程:

1.初始化 Vulkan 实例,物理设备和任务队列以及 Surface

  • 创建 Instances 实例
VkInstanceCreateInfo instance_create_info = { 
  VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, 
  nullptr, 
  0, 
  &application_info, 
  0, 
  nullptr, 
  static_cast<uint32_t>(desired_extensions.size()), 
  desired_extensions.size() > 0 ? &desired_extensions[0] : nullptr 
};

VkInstance inst;
VkResult result = vkCreateInstance( &instance_create_info, nullptr, &inst ); 
  • 初始化物理设备,也就是我们的显卡设备,Vulkna 的设计是支持多 GPU 的,这里选择第一个设备就行了
uint32_t extensions_count = 0; 
VkResult result = VK_SUCCESS; 

//获取所有可用物理设备,并选择第一个
result = vkEnumerateDeviceExtensionProperties( physical_device, nullptr,
    &extensions_count, &available_extensions[0]); 
if( (result != VK_SUCCESS) || 
    (extensions_count == 0) ) { 
  std::cout << "Could not get the number of device extensions." << std::endl; 
  return false; 
}
  • 获取 queue,Vulkan 的所有操作,从绘图到上传纹理,都需要将命令提交到队列中
uint32_t queue_families_count = 0; 

//获取队列簇,并选择第一个
queue_families.resize( queue_families_count ); 
vkGetPhysicalDeviceQueueFamilyProperties( physical_device,
    &queue_families_count, &queue_families[0] ); 
if( queue_families_count == 0 ) { 
  std::cout << "Could not acquire properties of queue families." << std::endl; 
  return false; 
} 
  • 初始化逻辑设备,在选择要使用的物理设备之后,我们需要设置一个逻辑设备用于交互
VkResult result = vkCreateDevice( physical_device, &device_create_info,
    nullptr, &logical_device ); 
if( (result != VK_SUCCESS) || 
    (logical_device == VK_NULL_HANDLE) ) { 
  std::cout << "Could not create logical device." << std::endl; 
  return false; 
} 

return true;
  • 上述初始完毕后,接着初始化 Surface,然后我们就可以使用 Vulkan 进行绘制了
#ifdef VK_USE_PLATFORM_WIN32_KHR 

//创建WIN32的surface,如果是Android,需要使用VkAndroidSurfaceCreateInfoKHR
VkWin32SurfaceCreateInfoKHR surface_create_info = { 
  VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR, 
  nullptr, 
  0, 
  window_parameters.HInstance, 
  window_parameters.HWnd 
}; 

VkResult result = vkCreateWin32SurfaceKHR(
    instance, &surface_create_info, nullptr, &presentation_surface );

2.通过 vkCmdDraw 函数进行图像绘制

void vkCmdDraw(
    //在Vulkan中,像绘画命令、内存转换等操作并不是直接通过方法调用去完成的
    // 而是需要把所有的操作放在Command Buffer里
    VkCommandBuffer commandBuffer,
    uint32_t vertexCount, //顶点数量
    uint32_t instanceCount, // 要画的instance数量,没有:置1
    uint32_t firstVertex,// vertex buffer中第一个位置 和 vertex Shader 里gl_vertexIndex 相关。
    uint32_t firstInstance);// 同firstVertex 类似。

3.提交 buffer

if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE) != VK_SUCCESS) {
    throw std::runtime_error("failed to submit draw command buffer!");
}

我在这里比较浅显的介绍了 Vulkan 的用法,但上面介绍的只是 Vulkan 的一点皮毛,Vulkan 的使用比 OpenGL 要复杂的很多,机制也复杂很多,如果想进一步了解 Vulkan 还是得专门去深入研究。虽然只介绍了一点皮毛,但已经可以让我们去了解 Vulkan 这一图像生产者,是如何在 Android 系统中生产图像的,下面就来看看吧。

3.2 Vulkan进行硬件加速

在前面讲 OpenGL 进行硬件加速时,提到了 CanvasContext,它会根据渲染的类型选择不同的渲染管线,Android 是通过 Vulkan 或者还是通过 OpenGL 渲染,主要是 CanvasContext 里选择的渲染管线的不同。

CanvasContext* CanvasContext::create(RenderThread& thread,
        bool translucent, RenderNode* rootRenderNode, IContextFactory* contextFactory) {

    auto renderType = Properties::getRenderPipelineType();

    switch (renderType) {
        case RenderPipelineType::OpenGL:
            return new CanvasContext(thread, translucent, rootRenderNode, contextFactory,
                    std::make_unique<OpenGLPipeline>(thread));
        case RenderPipelineType::SkiaGL:
            return new CanvasContext(thread, translucent, rootRenderNode, contextFactory,
                    std::make_unique<skiapipeline::SkiaOpenGLPipeline>(thread));
        case RenderPipelineType::SkiaVulkan:
            return new CanvasContext(thread, translucent, rootRenderNode, contextFactory,
                                std::make_unique<skiapipeline::SkiaVulkanPipeline>(thread));
        default:
            LOG_ALWAYS_FATAL("canvas context type %d not supported", (int32_t) renderType);
            break;
    }
    return nullptr;
}

我们这里直接看 SkiaVulkanPipeline。

//文件->/frameworks/base/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp
SkiaVulkanPipeline::SkiaVulkanPipeline(renderthread::RenderThread& thread)
        : SkiaPipeline(thread), mVkManager(thread.vulkanManager()) {}

SkiaVulkanPipeline 的构造函数中初始化了 VulkanManager,VulkanManager 是对 Vulkan 使用的封装,和前面讲到的 OpenGLPipeline 中的 EglManager 类似。我们看一下 VulkanManager 的初始化函数。

//文件-->/frameworks/base/libs/hwui/renderthread/VulkanManager.cpp
void VulkanManager::initialize() {
    if (hasVkContext()) {
        return;
    }

    auto canPresent = [](VkInstance, VkPhysicalDevice, uint32_t) { return true; };
    mBackendContext.reset(GrVkBackendContext::Create(vkGetInstanceProcAddr, 
    vkGetDeviceProcAddr, &mPresentQueueIndex, canPresent));
    //……
}

初始化函数中我们主要关注 GrVkBackendContext::Create 方法。

// Create the base Vulkan objects needed by the GrVkGpu object
const GrVkBackendContext* GrVkBackendContext::Create(uint32_t* presentQueueIndexPtr,
                                                     CanPresentFn canPresent,
                                                     GrVkInterface::GetProc getProc) {
    //……

    const VkInstanceCreateInfo instance_create = {
        VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,    // sType
        nullptr,                                   // pNext
        0,                                         // flags
        &app_info,                                 // pApplicationInfo
        (uint32_t) instanceLayerNames.count(),     // enabledLayerNameCount
        instanceLayerNames.begin(),                // ppEnabledLayerNames
        (uint32_t) instanceExtensionNames.count(), // enabledExtensionNameCount
        instanceExtensionNames.begin(),            // ppEnabledExtensionNames
    };

    ACQUIRE_VK_PROC(CreateInstance, VK_NULL_HANDLE, VK_NULL_HANDLE);
    //1,创建Vulkan实例
    err = grVkCreateInstance(&instance_create, nullptr, &inst);
    if (err < 0) {
        SkDebugf("vkCreateInstance failed: %d\n", err);
        return nullptr;
    }

    uint32_t gpuCount;
    //2,查询可用物理设备
    err = grVkEnumeratePhysicalDevices(inst, &gpuCount, nullptr);
    if (err) {
        //……
    }
    //……
    gpuCount = 1;
    //3,选择物理设备
    
    err = grVkEnumeratePhysicalDevices(inst, &gpuCount, &physDev);
    if (err) {
        //……
    }

    //4,查询队列簇
    uint32_t queueCount;
    grVkGetPhysicalDeviceQueueFamilyProperties(physDev, &queueCount, nullptr);
    if (!queueCount) {
        //……
        return nullptr;
    }

    SkAutoMalloc queuePropsAlloc(queueCount * sizeof(VkQueueFamilyProperties));
    // now get the actual queue props
    VkQueueFamilyProperties* queueProps = (VkQueueFamilyProperties*)queuePropsAlloc.get();
    //5,选择队列簇
    grVkGetPhysicalDeviceQueueFamilyProperties(physDev, &queueCount, queueProps);
    
    //……

    // iterate to find the graphics queue
    const VkDeviceCreateInfo deviceInfo = {
        VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,    // sType
        nullptr,                                 // pNext
        0,                                       // VkDeviceCreateFlags
        queueInfoCount,                          // queueCreateInfoCount
        queueInfo,                               // pQueueCreateInfos
        (uint32_t) deviceLayerNames.count(),     // layerCount
        deviceLayerNames.begin(),                // ppEnabledLayerNames
        (uint32_t) deviceExtensionNames.count(), // extensionCount
        deviceExtensionNames.begin(),            // ppEnabledExtensionNames
        &deviceFeatures                          // ppEnabledFeatures
    };
    //6,创建逻辑设备
    err = grVkCreateDevice(physDev, &deviceInfo, nullptr, &device);
    if (err) {
        SkDebugf("CreateDevice failed: %d\n", err);
        grVkDestroyInstance(inst, nullptr);
        return nullptr;
    }

    auto interface =
        sk_make_sp<GrVkInterface>(getProc, inst, device, extensionFlags);
    if (!interface->validate(extensionFlags)) {
        SkDebugf("Vulkan interface validation failed\n");
        grVkDeviceWaitIdle(device);
        grVkDestroyDevice(device, nullptr);
        grVkDestroyInstance(inst, nullptr);
        return nullptr;
    }

    VkQueue queue;
    grVkGetDeviceQueue(device, graphicsQueueIndex, 0, &queue);

    GrVkBackendContext* ctx = new GrVkBackendContext();
    ctx->fInstance = inst;
    ctx->fPhysicalDevice = physDev;
    ctx->fDevice = device;
    ctx->fQueue = queue;
    ctx->fGraphicsQueueIndex = graphicsQueueIndex;
    ctx->fMinAPIVersion = kGrVkMinimumVersion;
    ctx->fExtensions = extensionFlags;
    ctx->fFeatures = featureFlags;
    ctx->fInterface.reset(interface.release());
    ctx->fOwnsInstanceAndDevice = true;

    return ctx;
}

可以看到,GrVkBackendContext::Create 中所作的事情就是初始化 Vulkan,初始化的流程和前面介绍如何使用 Vulkan 中初始化流程都是一样的,这些都是通用的流程。

初始化完成,我们接着看看 Vulkan 如何绑定 Surface,只有绑定了 Surface,我们才能使用 Vulkan 进行图像绘制。

//文件-->/frameworks/base/libs/hwui/renderthread/VulkanManager.cpp
VulkanSurface* VulkanManager::createSurface(ANativeWindow* window) {
    initialize();

    if (!window) {
        return nullptr;
    }

    VulkanSurface* surface = new VulkanSurface();

    VkAndroidSurfaceCreateInfoKHR surfaceCreateInfo;
    memset(&surfaceCreateInfo, 0, sizeof(VkAndroidSurfaceCreateInfoKHR));
    surfaceCreateInfo.sType = VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR;
    surfaceCreateInfo.pNext = nullptr;
    surfaceCreateInfo.flags = 0;
    surfaceCreateInfo.window = window;

    VkResult res = mCreateAndroidSurfaceKHR(mBackendContext->fInstance, &surfaceCreateInfo, nullptr,
                                            &surface->mVkSurface);
    if (VK_SUCCESS != res) {
        delete surface;
        return nullptr;
    }

    SkDEBUGCODE(VkBool32 supported; res = mGetPhysicalDeviceSurfaceSupportKHR(
                                            mBackendContext->fPhysicalDevice, mPresentQueueIndex,
                                            surface->mVkSurface, &supported);
                // All physical devices and queue families on Android must be capable of
                // presentation with any
                // native window.
                SkASSERT(VK_SUCCESS == res && supported););

    if (!createSwapchain(surface)) {
        destroySurface(surface);
        return nullptr;
    }

    return surface;
}

可以看到,这个创建了 VulkanSurface,并绑定了 ANativeWindow,ANativeWindow 是 Android 的原生窗口,在前面介绍 OpenGL 进行硬件渲染时,也提到过 createSurface 这个函数,它是在 performDraw 被执行的,在这里就不重复说了。

接下来就是调用 Vulkan 的 api 进行绘制的图像的流程

bool SkiaVulkanPipeline::draw(const Frame& frame, const SkRect& screenDirty,
   const SkRect& dirty, const FrameBuilder::LightGeometry& lightGeometry,
   LayerUpdateQueue* layerUpdateQueue, const Rect& contentDrawBounds,
   bool opaque, bool wideColorGamut, const BakedOpRenderer::LightInfo& lightInfo,
   const std::vector<sp<RenderNode>>& renderNodes, FrameInfoVisualizer* profiler) {
   
    sk_sp<SkSurface> backBuffer = mVkSurface->getBackBufferSurface();
    if (backBuffer.get() == nullptr) {
        return false;
    }
    SkiaPipeline::updateLighting(lightGeometry, lightInfo);
    renderFrame(*layerUpdateQueue, dirty, renderNodes, opaque, 
    wideColorGamut, contentDrawBounds, backBuffer);
    layerUpdateQueue->clear();

    // Draw visual debugging features
    if (CC_UNLIKELY(Properties::showDirtyRegions ||
                    ProfileType::None != Properties::getProfileType())) {
        SkCanvas* profileCanvas = backBuffer->getCanvas();
        SkiaProfileRenderer profileRenderer(profileCanvas);
        profiler->draw(profileRenderer);
        profileCanvas->flush();
    }

    // Log memory statistics
    if (CC_UNLIKELY(Properties::debugLevel != kDebugDisabled)) {
        dumpResourceCacheUsage();
    }

    return true;
}

最后通过 swapBuffers 提交绘制内容

void VulkanManager::swapBuffers(VulkanSurface* surface) {
    if (CC_UNLIKELY(Properties::waitForGpuCompletion)) {
        ATRACE_NAME("Finishing GPU work");
        mDeviceWaitIdle(mBackendContext->fDevice);
    }

    SkASSERT(surface->mBackbuffers);
    VulkanSurface::BackbufferInfo* backbuffer =
            surface->mBackbuffers + surface->mCurrentBackbufferIndex;
    GrVkImageInfo* imageInfo;
    SkSurface* skSurface = surface->mImageInfos[backbuffer->mImageIndex].mSurface.get();
    skSurface->getRenderTargetHandle((GrBackendObject*)&imageInfo,
                                     SkSurface::kFlushRead_BackendHandleAccess);
    // Check to make sure we never change the actually wrapped image
    SkASSERT(imageInfo->fImage == surface->mImages[backbuffer->mImageIndex]);

    // We need to transition the image to VK_IMAGE_LAYOUT_PRESENT_SRC_KHR and make sure that all
    // previous work is complete for before presenting. So we first add the necessary barrier here.
    VkImageLayout layout = imageInfo->fImageLayout;
    VkPipelineStageFlags srcStageMask = layoutToPipelineStageFlags(layout);
    VkPipelineStageFlags dstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
    VkAccessFlags srcAccessMask = layoutToSrcAccessMask(layout);
    VkAccessFlags dstAccessMask = VK_ACCESS_MEMORY_READ_BIT;

    VkImageMemoryBarrier imageMemoryBarrier = {
            VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,     // sType
            NULL,                                       // pNext
            srcAccessMask,                              // outputMask
            dstAccessMask,                              // inputMask
            layout,                                     // oldLayout
            VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,            // newLayout
            mBackendContext->fGraphicsQueueIndex,       // srcQueueFamilyIndex
            mPresentQueueIndex,                         // dstQueueFamilyIndex
            surface->mImages[backbuffer->mImageIndex],  // image
            {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}     // subresourceRange
    };

    mResetCommandBuffer(backbuffer->mTransitionCmdBuffers[1], 0);
    VkCommandBufferBeginInfo info;
    memset(&info, 0, sizeof(VkCommandBufferBeginInfo));
    info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
    info.flags = 0;
    mBeginCommandBuffer(backbuffer->mTransitionCmdBuffers[1], &info);
    mCmdPipelineBarrier(backbuffer->mTransitionCmdBuffers[1], srcStageMask, dstStageMask, 0, 0,
                        nullptr, 0, nullptr, 1, &imageMemoryBarrier);
    mEndCommandBuffer(backbuffer->mTransitionCmdBuffers[1]);

    surface->mImageInfos[backbuffer->mImageIndex].mImageLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;

    // insert the layout transfer into the queue and wait on the acquire
    VkSubmitInfo submitInfo;
    memset(&submitInfo, 0, sizeof(VkSubmitInfo));
    submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
    submitInfo.waitSemaphoreCount = 0;
    submitInfo.pWaitDstStageMask = 0;
    submitInfo.commandBufferCount = 1;
    submitInfo.pCommandBuffers = &backbuffer->mTransitionCmdBuffers[1];
    submitInfo.signalSemaphoreCount = 1;
    // When this command buffer finishes we will signal this semaphore so that we know it is now
    // safe to present the image to the screen.
    submitInfo.pSignalSemaphores = &backbuffer->mRenderSemaphore;

    // Attach second fence to submission here so we can track when the command buffer finishes.
    mQueueSubmit(mBackendContext->fQueue, 1, &submitInfo, backbuffer->mUsageFences[1]);

    // Submit present operation to present queue. We use a semaphore here to make sure all rendering
    // to the image is complete and that the layout has been change to present on the graphics
    // queue.
    const VkPresentInfoKHR presentInfo = {
            VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,  // sType
            NULL,                                // pNext
            1,                                   // waitSemaphoreCount
            &backbuffer->mRenderSemaphore,       // pWaitSemaphores
            1,                                   // swapchainCount
            &surface->mSwapchain,                // pSwapchains
            &backbuffer->mImageIndex,            // pImageIndices
            NULL                                 // pResults
    };

    mQueuePresentKHR(mPresentQueue, &presentInfo);

    surface->mBackbuffer.reset();
    surface->mImageInfos[backbuffer->mImageIndex].mLastUsed = surface->mCurrentTime;
    surface->mImageInfos[backbuffer->mImageIndex].mInvalid = false;
    surface->mCurrentTime++;
}

这些流程都和 OpenGL 是一样的,初始化,绑定 Surface,绘制,提交,所以就不细说了,对 Vulkan 有兴趣的,可以深入的去研究。至此 Android 中的另一个图像生产者 Vulkan 生产图像的流程也讲完了。

四 总结

OpenGL,Skia,Vulkan 都是跨平台的图形生产者,我们不仅仅可以在 Android 设备上使用,我们也可以在 IOS 设备上使用,也可以在 Windows 设备上使用,使用的流程基本和上面一致,但是需要适配设备的原生窗口和缓冲,所以掌握了 Android 是如何绘制图像的,我们也具备了掌握其他任何设备上是如何绘制图像的能力。

在随后的两篇文章中,我们会介绍 Android 图像渲染原理的最后一部分:图像缓冲区。这三部分如果都能掌握,我们基本就掌握了 Android 中图像绘制原理了。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Android图形显示系统4 图像生产者(下) 的相关文章

  • rk3399 高可靠OTA升级

    https blog csdn net m0 37631324 article details 106254910
  • Android 12 源码下载、编译与烧录到Pixel 3a

    android 12 源码下载 编译与烧录到Pixel 3a 当前设备环境 源码下载 温馨跳转 个人总结源码下载 谷歌手机设备驱动的下载 编译 烧录 当前设备环境 操作系统 ubuntu 18 04 LTS 手机 谷歌手机Pixel 3a
  • 深入AMS源码(四)——ActivityManagerService的进程管理

    1 AMS中的进程管理 final ArrayList
  • Android混合开发全解析

    现在的app都开始流行混合开发了 这是一个app开发的新技术 作为android程序猿的我们也应该要了解并且掌握他 那么在使用之前 我们一定要搞清楚 我们的哪些场景使用混合开发好一些呢 这个问题一定要搞清楚 因为现在的混合开发还不成熟 We
  • android GMS认证之CTS 常用命令

    http blog csdn net hfreeman2008 article details 51084821 前面的一篇文章 lt
  • Android中添加CallStack打印

    Android 打印调用堆栈 内核 添加头文件 include
  • Android AMS 系列之 systemReady

    一 前言 Android 系统服务进程 system server 进入消息循环前的最后一步 就是调用 AMS 的 systemReady 方法 今天我们分析一下这里的流程 二 systemReady 第一部分 ActivityManage
  • Android开发之RecyclerView的使用全解

    转自 http blog csdn net dmk877 article details 50816933 自Android 5 0之后 谷歌公司推出了RecylerView控件 RecylerView 我想看到一个新名词后大部分人会首先发
  • Android数据的四种存储方式

    Android数据的四种存储方式 SharePreferences SQLite Contert Provider File 网络存储 作为一个完整的应用程序 数据存储的操作是必不可少的 Android系统提供了四种存储数据方式 分别为 S
  • android Intent常用标识

    Intent常用标识 FLAG ACTIVITY BROUGHT TO FRONT 这个标志一般不是由程序代码设置的 如在launchMode中设置singleTask模式时系统帮你设定 FLAG ACTIVITY CLEAR TOP 如果
  • android面试题-ActivityRecord TaskRecord和ProcessRecord之间的关系

    转自 http blog csdn net mwq384807683 article details 72529285 源码分析相关面试题 Volley源码分析 注解框架实现原理 okhttp3 0源码分析 onSaveInstanceSt
  • Android中的Loaders机制

    转自 http blog csdn net guoshaobei article details 17451647 Loaders机制在Android 3 0版本后引入 Loaders机制使一个Activity或者一个Fragment更加容
  • Android SurfaceFlinger3 申请Buffer

    本章节思维导图如上 主要讲述了 surafce 测试程序 demo 的第3步中的获取 Buffer 锁定 最关键 并写入 Buffer 的过程 一 概述 该部分代码是在上一章节中 Surface 测试程序源码的精简版 保存了最关键的流程 如
  • Android 特许权限白名单

    1 前言 在项目开发中 需求 app中有恢复出厂设置的功能 分解这个需求的时候 第一反应肯定不是第三方app 恢复出厂设置肯定需要有系统权限 属于系统级的app 然后在看手机系统中的功能 恢复出厂设置功能属于设置模块 找到源码阅读 当然是能
  • adb install安装流程

    把一个编译好的APK通过 include BUILD PREBUILT 预制到系统中 但是启动后一直crash log中显示 dlopen failed cannot locate symbol 02 25 16 18 20 143 126
  • Android servicemanager进程启动过程

    在分析ServiceManager实例化注册流程前 先放张ServiceManager在Binder体系中的UML图 一 ServiceManager启动流程 查看system core rootdir init rc脚本可知 init进程
  • Android 13 - binder阅读(6)- 使用ServiceManager获取服务

    最近事情好多 使用ServiceManager获取服务就暂时先不学习了 不过从之前的学习中我们也大致可以猜到使用ServiceManager获取服务的过程 根据服务名称获取到ServiceManager中服务代理对应的Handle 再到Bi
  • 使用 source insight 代码跳转时出现 symbol not found 问题

    P S 本篇博客是根据自己的经验来写的 如果大家有不同意见随时交流 1 使用 source insight 代码跳转功能时出现 symbol not found 问题一般是有三种可能 在你添加代码时没有选择 Add tree 选项 创建 p
  • 一些关于dagger2的理解(一)

    转自 http blog csdn net shareye1992 article details 51398554 首先 真实的原理我不准 但是我还是提供我的理解 阅读这篇文章希望读者能满足一个假设 不管懂不懂 看过一些其他关于dagge
  • Android 12读写存储卡权限申请

随机推荐

  • kubeadm方式部署k8s最新版本V1.26.2

    Kubernetes核心概念 Master主要负责资源调度 控制副本 和提供统一访问集群的入口 核心节点也是管理节点 Node是Kubernetes集群架构中运行Pod的服务节点 Node是Kubernetes集群操作的单元 用来承载被分配
  • C++将一组数随机分成几个小组

    在生活中难免会遇到需要对一群人进行拆分小组 对于大的课题组的研究生还会分组开组会汇报确定名单这个事情 针对这些问题写出了下面的代码 include
  • python学习笔记之多线程练习ThreadPoolExecutor,map,submit

    主要练习了ThreadPoolExecutor map和submit的区别 推荐使用submit更灵活 import os import random import threading import requests as rq impor
  • Elementui - 下拉框(el-dropdown-menu组件)的使用方法

    Elementui 下拉框 el dropdown menu组件 的使用方法 官方文档 简单样式 此为指令事件 具体效果请看文档
  • 2023浙江省赛“信息安全管理与评估“--Reverse部分全部解析(高职组)

    2022全国职业技能大赛 信息安全管理与评估 高职组 任务书 2022全国职业技能大赛 信息安全管理与评估 任务书 第一阶段竞赛项目试题 第二阶段竞赛项目试题 第三阶段竞赛项目试题 任务3 Reverse 80分 2022全国职业技能大赛
  • GLES3.0中文API-glGetProgramInterface

    名称 glGetProgramInterface 查询程序中接口的属性 C 规范 void glGetProgramInterfaceiv GLuint program GLenum programInterface GLenum pnam
  • Hibernate复合主键的注解

    最近做项目用到了Hibernate框架 采用了纯面向对象的思想 使用ORM映射实体 在开发中 实体中出现了复合主键 不再是单一的属性作主键 由于采用了注解的方式 就不再使用xml文件进行配置了 而是直接在实体中进行注释 Hibernate注
  • Unity动画知识之二:Animator动画状态机

    文 拉撒路 上次我们讲过 Unity游戏动画从入门到住院 今天我们来讲一下动画状态机 好了 现在我们已经成功的导入了动画 接下来要玩的东西就很装13啦 因为大部分动画师是用不到这家伙的 需要掌握这个技能的 至少也是动画组长级别了 嗯 一个组
  • 找不到工作?对不起,这份测试面试题来晚了!

    1 测试测试与 测试的区别 首先alpha测试和beta都属于验收测试 这两种测试都需要用户参加 且都不能由程序员和测试员执行 广义上来讲 测试是 内测 测试是 公测 alpha测试是用户在开发环境或者是公司内部模拟实际操作环境的测试 测试
  • C#学习笔记 线程操作

    完整代码在这里 https github com techstay csharp learning note 创建并使用线程 使用线程执行任务 要创建一个线程很简单 实例化一个System Threading Thread对象并向其构造函数
  • optimizeinplace

    上篇介绍了 X文件网格的渲染方法 如果需要创建自己的网格文件 并将它渲染出来 那么可以考虑创建一个空的网格 然后读取网格文件内容 将顶点 材质和纹理数据写入以上的网格相关缓冲区中 创建一个自定义顶点格式的空Mesh网格可由 D3DXCrea
  • docker创建linux镜像,docker创建centos镜像无法使用systemctl

    docker获取centos镜像 里面执行安装lnmp之后 发现使用systemctl无法使用 root 92926bd84d70 systemctl restart nginx Failed to get D Bus connection
  • MES管理系统如何实现数据采集和过程控制

    随着工业4 0的到来 MES管理系统解决方案已成为企业实现生产过程数字化和智能化的关键工具 MES生产管理系统不仅提供生产计划 调度 质量管理和设备维护等功能 还在数据采集和过程控制方面发挥着重要作用 本文将探讨MES生产管理系统如何实现数
  • VS下Qt的信号与槽实现

    实现主窗口中Add按钮的功能 这一部分要特别注意 除了实现功能代码外 还需自己手动添加一些其他的代码 Qt Creator可以自动添加 我们需要在2个地方添加代码 第1个是在addressbook h文件下添加一个槽函数声明 即属于priv
  • net::ERR_CONNECTION_REFUSED 解决大全

    sockjs node info报错 sockjs node作用 SockJS is a JavaScript library for browsers that provides a WebSocket like object SockJ
  • 项目构建之maven篇:4.坐标与依赖及spring依赖注入demo

    源代码下载 坐标
  • 数据结构-将升序数组转化为平衡二叉搜索树-java

    1 题目 给定一个升序排序的数组 将其转化为平衡二叉搜索树 BST 平衡二叉搜索树指树上每个节点 node 都满足左子树中所有节点的的值都小于 node 的值 右子树中所有节点的值都大于 node 的值 并且左右子树的节点数量之差不大于1
  • 笔试算法题

    转自 http www cnblogs com xwdreamer archive 2011 12 13 2296910 html 1 把二元查找树转变成排序的双向链表 题目 输入一棵二元查找树 将该二元查找树转换成一个排序的双向链表 要求
  • 【TVM系列二】TVM介绍

    文章同步更新在公众号 AIPlayer 欢迎扫码关注 共同进步 目录 一 TVM的工作流程 1 整体流程 2 关键数据结构 3 Transformations 4 搜索空间和基于机器学习的转换 5 目标代码转化 二 逻辑架构组件 三 运行T
  • Android图形显示系统4 图像生产者(下)

    一 概述 在上一篇文章 Android图形显示系统2 图像消费者 中 我们详细地讲解了图像消费者 我们已经了解了 Android 中的图像元数据是如何被 SurfaceFlinger HWComposer 或者 OpenGL ES 消费的