Android图形架构总览

图形渲染流程

​ 在Android中,所有的渲染都是将内容绘制到对应的Surface上,Surface作为BufferQueue的生产方,每当发生入队操作时,都会通知BufferQueue创建者进行消费,也就是SurfaceFlinger,由SurfaceFlinger进行合并,然后发送到显示器进行显示。

下面这张图描述了关键组件的工作流程:

工作流程

  1. 当前一个可见的界面被创建时,会通过WindowManager去创建一个Window对象,而每一个Window对象中都会包含一个Surface对象,该对象由WindowManager向SurfaceFlinger请求创建。Surface中包含一个BufferQueue,它由SurfaceFlinger向HAL层调用Gralloc对其进行内存分配。

OpenGL-ES入门之介绍篇

名词解释

OpenGL

通俗来讲,OpenGL是一个图形库,提供了一系列操作图形图像的API。因为OpenGL只是一个规范,具体的实现一般是由显卡提供。类似的图形库还有Windows平台的DirectX。

OpenGL-ES

OpenGL-ES是专为嵌入式设备准备的一个OpenGL的裁剪版(去除了一些复杂的图元)。OpenGL-ES支持Android、iOS等平台,并且它也是WebGL的基础。

EGL

EGL是OpenGL-ES与本地窗口系统之间的一个中间层API,一般由系统实现。EGL作为OpenGL-ES与设备之间的桥梁,将OpenGL-ES绘制在设备上。

WebGL

WebGL是OpenGL-ES在浏览器上的实现,WebGL1.0基于OpenGL-ES2.0实现,WebGL2.0基于OpenGl-ES3.0实现。

图形渲染管线

图形渲染管线也叫做渲染流水线,指的是将输入的原始图形数据经过渲染管线处理,输出一帧想要的图像的过程。

在OpenGL-ES中,任何事物都在3D空间中,而屏幕和窗口却都是2D的。所以图形渲染管线主要是在将输入的3D坐标转换成2D坐标,再讲2D坐标转换成实际有颜色的像素。(将输入的3D坐标画在3D坐标系中,然后根据视锥范围截取一个2D平面,将截取的平面转换为平面坐标显示)

2D坐标精确的表示一个点在2D空间的位置;而像素是这个点的近似值,像素的显示收到屏幕分辨率的限制

与CPU不同,GPU适合处理的问题都有这样的特征,一个问题可以分解为多个相同的小问题,每个问题之间相互独立不影响,所以GPU拥有大量的处理单元,处理单元数量越多,性能也就越好。而每个单元都可以并行处理,所以GPU的并行能力非常强。

可编程的渲染管线

自OpenGL-ES 2.0开始,采用的都是可编程的渲染管线,也就是开发者可以自定义部分流水线中的着色器,使得可以更细致的控制图形渲染管线。

着色器是运行在GPU上的小程序,这些小程序在图形渲染管线的某个特定部分运行,着色器是一个非常独立的程序,是一种把输入转化为输出的程序,着色器与着色器之间无法通信。

开发者可编程的阶段有两个,一个是顶点着色器,一个是片段着色器。

OpenGL-ES渲染管线

了解cmake

什么是cmake

cmake是一个跨平台的构建工具,主要是用来解决不同平台上Makefile不同的问题,通过编写CMakeLists.txt文件,然后通过cmake命令自动生成对应平台的Makefile。

编写CMakeLists

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 指定运行此配置文件所需的 CMake 的最低版本
cmake_minimum_required(VERSION 3.4.1)

// 该指令的主要作用就是将指定的源文件生成链接文件,然后添加到工程中去。
// native-lib表示生成的链接文件的名字。
// SHARED表示生成的文件类型:STATIC类型在链接其它目标的时候使用。SHARED库会被动态链接,在运行时会被加载。MODULE库是一种不会被链接到其它目标中的插件,但是可能会在运行时使用dlopen-系列的函数。
// native-lib.cpp 表示源文件,可以有多个。
add_library(native-lib SHARED native-lib.cpp)

// 查找一个库,log库已经包含在NDK中。
// log-lib用来存储查找结果
// log 表示查找库的名字
find_library(log-lib log)

// 为了确保CMake可以在编译时定位您的标头文件,使用该命令来指定包含标头文件的路径
include_directories(${openCVPath}/native/jni/include)

// set用来设置一些变量
// CMAKE_CURRENT_SOURCE_DIR 表示当前CMakeLists所在的目录
set(openCVPath ${CMAKE_SOURCE_DIR}/../../../../opencv-sdk )

// 使用static来添加依赖库,IMPORTED表示您只希望将库导入到项目中,
// 然后需要通过set_target_properties来指定库的路径
add_library(lib_opencv STATIC IMPORTED)

// lib_opencv就是上面导入的库的变量
// PROPERTIES 表示设置属性
// IMPORTED_LOCATION 设置导入库的位置
set_target_properties(lib_opencv PROPERTIES IMPORTED_LOCATION
${openCVPath}/native/libs/${ANDROID_ABI}/libopencv_java4.so)

// 将预构建库关联到您自己的原生库
// native-lib库就是原生库,将上面查找的系统库和添加的依赖库都链接到目标库上。
target_link_libraries(native-lib ${log-lib} lib_opencv)

运行cmake

运行cmake只需要执行cmake命令,后面跟上CMakeLists所在的目录位置,运行后在当前目录中生成Makefile。

1
cmake .

然后再在当前目录执行make命令,就可以生成对应的项目编译文件。

Flutter深入之flutter初始化

开篇

前面两篇我们讲到了Flutter应用如何编译,并将编译结果如何放入APK中。那么当应用启动时,又是如何执行到main.dart中的main方法呢?Flutter的绘制内容又是如何被显示的呢?

flutter-启动流程

FlutterApplication

在应用启动的Application中会执行一些初始化操作,比如初始化一些路径文件夹等配置,搬移flutter资源,最为重要的是安装flutter.so。在前面的编译讲解中,我们知道该so就是flutter-engine。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# FlutterMain.java
public static void startInitialization(Context applicationContext, Settings settings) {
...
sSettings = settings;
long initStartTimestampMillis = SystemClock.uptimeMillis();
// 初始化路径等配置
initConfig(applicationContext);
// 检测aot
initAot(applicationContext);
// 搬移flutter资源
initResources(applicationContext);
// 加载so
System.loadLibrary("flutter");

// initTimeMicros是java层初始化loadLibrary所花费的时间。
// 因为so库中需要记录flutter开始的时间,但是在加载so库之前,
// 是无法调用so中的api,所以将加载so库所花费的时间传下去,然后so库中使用当前时间回退该花费时间,就是应用启动的真正时间。
long initTimeMillis = SystemClock.uptimeMillis() - initStartTimestampMillis;
nativeRecordStartTimestamp(initTimeMillis);
}
1
2
3
4
5
6
7
8
9
# flutter_main.cc
static void RecordStartTimestamp(JNIEnv* env,
jclass jcaller,
jlong initTimeMillis) {
int64_t initTimeMicros =
static_cast<int64_t>(initTimeMillis) * static_cast<int64_t>(1000);
// initTimeMicros是java层初始化所花费的时间,使用当前时间回退该花费时间,就是应用启动的真正时间。
blink::engine_main_enter_ts = Dart_TimelineGetMicros() - initTimeMicros;
}

在加载so库完成后,会触发JNI回调JNI_OnLoad,开始动态注册JNI方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# library_loader.cc
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
// 将javaVM保存成全局变量
fml::jni::InitJavaVM(vm);
// 获取JNIEnv,通过JNIEnv.RegisterNatives来动态注册
JNIEnv* env = fml::jni::AttachCurrentThread();
bool result = false;

// 注册FlutterMain.java中的native方法
result = shell::FlutterMain::Register(env);
FML_CHECK(result);

// 注册FlutterCallbackInformation.java、FlutterView.java、FlutterNativeView.java中的native方法
result = shell::PlatformViewAndroid::Register(env);
FML_CHECK(result);

// 注册VsyncWaiter.java中的native方法
result = shell::VsyncWaiterAndroid::Register(env);
FML_CHECK(result);

return JNI_VERSION_1_4;
}

Flutter深入之flutter run命令究竟做了什么?

开篇

当我们创建一个Flutter App项目后,在当前项目路径下运行命令flutter run,就可以编译生成一个APK,并且将APK安装到模拟器中并启动。那么Flutter究竟是如何编译Dart资源的?又是如何将Dart资源放入到APK中?

接下来让我们慢慢跟着代码来分析flutter run 命令的执行过程。

flutter命令

在Mac上,flutter命令是指 ${flutterSdk}/bin/flutter 这个shell程序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
... 
update逻辑
...
PROG_NAME="$(path_uri "$(follow_links "$BASH_SOURCE")")"
BIN_DIR="$(cd "${PROG_NAME%/*}" ; pwd -P)"
export FLUTTER_ROOT="$(cd "${BIN_DIR}/.." ; pwd -P)"

FLUTTER_TOOLS_DIR="$FLUTTER_ROOT/packages/flutter_tools"
SNAPSHOT_PATH="$FLUTTER_ROOT/bin/cache/flutter_tools.snapshot"
STAMP_PATH="$FLUTTER_ROOT/bin/cache/flutter_tools.stamp"
SCRIPT_PATH="$FLUTTER_TOOLS_DIR/bin/flutter_tools.dart"
DART_SDK_PATH="$FLUTTER_ROOT/bin/cache/dart-sdk"
DART="$DART_SDK_PATH/bin/dart"
PUB="$DART_SDK_PATH/bin/pub"

// 实际上抛除更新逻辑,flutter的这个shell就是直接执行了dart命令
// $FLUTTER_TOOL_ARGS 这个参数可以无视
// $SNAPSHOT_PATH 这个指的就是$FLUTTER_ROOT/bin/cache/flutter_tools.snapshot这个snapshot
// $@ 这个就是flutter 后面跟的参数
"$DART" $FLUTTER_TOOL_ARGS "$SNAPSHOT_PATH" "$@"

我们可以看到,实际上flutter run 命令,执行的就是 dart flutter_tools.snapshot run

Flutter深入之flutter-build-bundle命令如何编译Dart?

开篇

上一篇我们讲到,在flutterTask中会调用flutter build bundle命令来编译dart代码,生成dart资源。那么build bundle命令是如何编译dart代码的?编译后生成了哪些资源?这些资源都是些什么?

flutter-build-bundle-产物

  • app.dill : 这就是dart代码编译后的二级制文件

  • Frontend_server.d : 这里面放的是frontend_server.dart.snapshot的绝对路径,使用该snapshot来编译dart代码生成上面的app.dill

    ​ flutter/bin/cache/artifacts/engine/darwin-x64/frontend_server.dart.snapshot

  • snapshot_blob.bin.d : 这里面放的是所有参与编译的dart文件的绝对路径的集合,包括项目的代码和flutterSdk的代码以及pub库中的三方代码。

  • snapshot_blob.bin.d.fingerprint : 这里面放的是snapshot_blob.bin.d中的所有文件的绝对路径以及每个文件所对应的md5值。使用这个md5来判断该文件是否有修改。在每次编译的时候会判断,如果没有文件修改,则直接跳过编译。

LeetCode-206反转链表

反转一个单链表。

示例:

1
2
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL

进阶:
你可以迭代或递归地反转链表。你能否用两种方法解决这道题?

迭代解法:
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×