Dart 读取文件过程分析
Dart读取文件时,先在Dart代码创建File引用,通过与IOService
跨Isolate
通信(先通过IO Service而发送请求到native端,等到native执行完操作之后再回调结果)从而实现对文件的读写。
实现一个简单的读取文件的代码如下:
1 | import 'dart:io'; |
整个过程如下:
过程分析
Dart端发起文件读写请求
其中file.readAsBytes()
是具体执行读取文件的地方,他的定义如下:
1 | // -> sdk\lib\io\file_impl.dart |
在我们创建File
时,实际上创建的是_File
(class _File extends FileSystemEntity implements File
)对象:
1 | // -> sdk\lib\io\file_impl.dart |
_File
是File
的实现类,所以file.readAsBytes()
实际调用的是_File
实现的方法:
1 | // -> sdk\lib\io\file_impl.dart |
可以看到,无论是普通的文件格式,还是character device,最后都是调用了_RandomAccessFile
的open()
和read(int bytes)
方法异步读取文件。
设备文件分为Block Device Driver和Character Device Drive两类。
Character Device Driver又被称为字符设备或裸设备raw devices; Block Device Driver通常成为块设备。
而Block Device Driver是以固定大小长度来传送转移资料 ; Character Device Driver是以不定长度的字元传送资料。 https://www.cnblogs.com/qlee/archive/2011/07/27/2118406.html#:~:text=Character
1 | // -> flutter\bin\cache\pkg\sky_engine\lib\io\file_impl.dart |
在_RandomAccessFile
中,除了同步读写文件是对返回的文件引用直接操作外,很多操作都能看到通过_dispatch()
方法与IO Service
通信,让我们看一下这个方法的实现:
1 | // -> sdk\lib\io\file_impl.dart |
查阅_IOService的源码后发现这是个external
方法.
1 | external static Future _dispatch(int request, List data); |
An
external
function is connected to its body by an implementation-specific mechanism. Attempting to invoke an external function that has not been connected to its body will throw a NoSuchMethodError or some subclass thereof.
****https://github.com/dart-lang/sdk/issues/4300
根据external
的定义,_dispatch
方法在不同的机器上面实现不同。我们只看和app相关的实现(在sdk\lib\_internal\vm
目录下,vm同级目录还有js等实现),具体的实现如下:
1 | // -> sdk\lib\_internal\vm\bin\io_service_patch.dart |
可以看到,最后是通过RawReceivePort
/SendPort
进行跨Isolate通信。
_IOService
使用_servicePorts
对native层发送消息触发IO操作,然后使用_receivePort
监听,当IO操作完成时会通过_replyToPort
回调结果,会在 _receivePort!.handler
方法中根据当时请求的id
找到Completer
将结果传递回去。
这样当时我们在 file.readAsBytes()
时获取到的Future
便会收到回调,从而完成文件操作的流程。
1 | file.readAsBytes().then((value) { |
下面是到目前为止涉及到的类关系示意图:
IO Service中转
那么,这个IO Service是做什么的,他又是如何实现与dart中的调用方双向通信,以及执行调用方需要的功能呢?
位于sdk\lib\_internal\vm\bin\io_service_patch.dart
的_IOService是一个中转站,向上承接来自Dart代码的IO请求指令(先行返回Future),向下将这些指令转发至Native层的IO Service,并监听回调,当native层处理完这些IO指令之后,将结果通过Future返回给Dart调用方。
让我们再看一下他的具体实现:
1 | // -> sdk\lib\_internal\vm\bin\io_service_patch.dart |
可以看到:
_IOService
持有_IOServicePorts _servicePorts
以便获取SendPort servicePort
和native层通信,- 在之前的代码分析中,我们已经知道
_IOService
还在_ensureInitialize()
中监听着RawReceivePort? _receivePort
的回调, - 这样当
_IOService
在_dispatch()
方法中将_replyToPort
(_receivePort
的SendPort)传递给servicePort
后,一旦native通过_replyToPort
发送处理结果,_IOService
立马可以收到并通过Completer.complete
返回给Dart中的调用方。
上述这些步骤能够实施的关键,在于Dart层的_IOService
如何与native层的_IOService
关联起来呢?
让我们来分析一下SendPort servicePort
的获取过程:
1 | // -> sdk\lib\_internal\vm\bin\io_service_patch.dart |
可以看到这里最后的关键方法是SendPort _newServicePort()
,这是一个external
方法,在native实现。
Native处理Dart的指令
IOService_NewServicePort
SendPort
是由_newServicePort()
方法创建的,这是一个external
方法,他的native层实现名称是IOService_NewServicePort
:
1 | // -> runtime\bin\io_service.cc |
注意,在Dart层的_IOService
的SendPort _newServicePort()
方法最后再这里调用了IOService_NewServicePort
。
这里主要有3个步骤:
- 使用
Dart_NewNativePort("IOService", IOServiceCallback, true);
创建Dart_Port
- 使用
Dart_NewSendPort
将Dart_Port
转化为Dart_Handle
(也就是Dart中的SendPort
) - 返回上面创建好的
Dart_Handle
,Dart代码拿到返回的Dart_Handle也就是SendPort servicePort
之后,就可以和native层的IO Service同通信。
接下来我们看一下前2步分别是怎么实现的:
Dart_NewNativePort
再看一下Dart_NewNativePort
的调用参数:
1 | Dart_NewNativePort("IOService", IOServiceCallback, true); |
IOServiceCallback
Dart_NewNativePort
总共有3个参数,Dart_NativeMessageHandler handler
是当这个Dart_Port
收到消息的时候,会被回调的方法,也就是我们通过Dart端的_IOService.dispatch
方法的**servicePort**.send(<dynamic>[id, **_replyToPort**, request, data]);
语句执行向native发送IO指令时,在native这里真正负责执行的方法:
1 | // -> runtime\bin\io_service.cc |
IOService具体的执行是在IO_SERVICE_REQUEST_LIST
根据解析到的参数执行对应的方法:
1 | // -> runtime\bin\io_service.h |
通过上述代码,可以得知,IOService主要处理的方法有四类:
File
Directory
Socket
SSLFilter
在IOServiceCallback
方法中,我们注意到,程序最后执行的结果是通过Dart_PostCObject
返回的,来看一下他是怎么实现的:
1 | // -> runtime\vm\native_api_impl.cc |
上述代码最后将结果包装成了Message打包进MessageHandler
的消息队列中,这样便可以在Dart端通过消息分发接收到结果。
Dart_NewNativePort
再来看一下Dart_NewNativePort
的实现如下:
1 | // -> runtime\vm\native_api_impl.cc |
主要的流程有:
- 切换退出当前isolate
- 创建
NativeMessageHandler nmh
包裹要处理的回调 - 根据上面创建的
nmh
创建Dart_Port port_id
- 执行**
nmh->Run()
**方法将nmh
放到线程池中运行 - 当
nmh
执行完毕回调后,关闭Dart_Port port_id
也就是说,在Dart中向Native发送指令时,通过Dart的_IOService._dispatch()
方法中执行_servicePorts._getPort(id);
向Native层的IOService获取用于通信的SendPort servicePort
时,会先通过Dart_NewNativePort创建一个NativeMessageHandler(会压入消息栈中),然后创建一个对应的Dart_Port port_id
并返回给Dart用来触发消息。
让我们挨个分析一下:
1.退出当前isolate
2.创建NativeMessageHandler nmh
包裹要处理的回调
3.根据上面创建的nmh
创建Dart_Port port_id
看一下PortMap::CreatePort
的实现:
1 | // -> runtime\vm\port.cc |
4.执行**nmh->Run()
**方法将nmh
放到线程池中运行
1 |
|
在创建了新的系统线程后,会执行下面的方法:
1 | // -> runtime\vm\thread_pool.cc |
Dart_NewSendPort
看一下Dart_NewSendPort
如何将创建好的Dart_Port service_port
转变为Dart的SendPort
的:
1 | // -> runtime\vm\dart_api_impl.cc |
到这里我们发现,Dart_NewNativePort
将要处理的事件handler
封装起来,最后在非当前isolate的线程中执行。
结论
从上面的分析中,我们可以知道,在Dart中通过File进行文件操作,其实是通过Dart中的_IOService进行消息中转,将用户的IO指令发送到Native层的IOService中;
IOService通过一些列操作,得到一个SendPort servicePort
,与此同时对应的IO操作已经压入消息栈中等待触发在单独的线程中执行;
之后在_IOService中servicePort
将用户需要的IO操作和与自己通信的_replyToPort = _receivePort!.sendPort;
通过send
方法触发IOServiceCallback
执行对应的IO操作,并且在最后调用Dart_PostCObject
方法将结果压入消息栈中,这会触发Dart层_IOService的_receivePort!.handler回调事件,然后根据事件失败或者成功,使用Completer通过Event loop一步步将事件上报,最终回调用户需要的命令。