external关键字

1,182 阅读3分钟

1.前言

external关键字平时很少用到,但是却经常能在底层源码中见到它,例如下面的代码

class Object {
   const Object();
   external bool operator ==(other);
   external int get hashCode;
   external String toString();
   @pragma("vm:entry-point")
   external dynamic noSuchMethod(Invocation invocation);
   ......
}

这段代码是我们平时新建一个空列表的方法,可以看到它只有声明,没有实现,可以看到Object 并非抽象类,却可以只添加方法声明,不添加方法实现,为什么可以这样呢?这就是external的作用了

2.external是什么

external是flutter提供的一个关键字,可以在非抽象类中实现类似抽象方法的方法,将方法的声明和实现相分离

3.作用

  1. 可以在非抽象类中实现类似抽象方法的方法,将方法的声明和实现相分离,这样可以实现在不同的平台,不管是dart for vm, 还是dart for web, 上层应用都可以只使用一套api,然后由不同的平台各种对external 修饰的方法添加实现,有助于提升扩展性,减低应用实现跨平台的难度

  2. external 声明的方法由底层sdk根据不同的平台(vm或者web)添加实现,方法所在类不用声明为抽象类,所以可以直接实例化

4.external方法实现

需要使用@patch修饰类和对应的方法,代码如下:

@patch
class 类名 {
  ...
  @patch 
  external声明的方法名
  ...
}

5. Object 的实现

Object 的实现在flutter3.3.2\flutter\bin\cache\dart-sdk\lib_internal\vm\lib中,如下截图

可以看到这些实现了external方法的类文件都是patch结尾的,我们再看看里面的代码,如下

@pragma("vm:recognized", "asm-intrinsic")
@pragma("vm:exact-result-type", "dart:core#_Smi")
@pragma("vm:external-name", "Object_getHash")
external int _getHash(obj);

@patch
@pragma("vm:entry-point")
class Object {
  // The VM has its own implementation of equals.
  @patch
  @pragma("vm:recognized", "asm-intrinsic")
  @pragma("vm:exact-result-type", bool)
  @pragma("vm:prefer-inline")
  @pragma("vm:external-name", "Object_equals")
  external bool operator ==(Object other);

  @patch
  int get hashCode => _getHash(this);
  int get _identityHashCode => _getHash(this);

  @patch
  @pragma("vm:external-name", "Object_toString")
  external String toString();
  // A statically dispatched version of Object.toString.
  @pragma("vm:external-name", "Object_toString")
  external static String _toString(obj);

  @patch
  @pragma("vm:entry-point", "call")
  dynamic noSuchMethod(Invocation invocation) {
    // TODO(regis): Remove temp constructor identifier 'withInvocation'.
    throw new NoSuchMethodError.withInvocation(this, invocation);
  }

  
}

// Used by DartLibraryCalls::Equals.
@pragma("vm:entry-point", "call")
bool _objectEquals(Object? o1, Object? o2) => o1 == o2;

// Used by DartLibraryCalls::HashCode.
@pragma("vm:entry-point", "call")
int _objectHashCode(Object? obj) => obj.hashCode;

// Used by DartLibraryCalls::ToString.
@pragma("vm:entry-point", "call")
String _objectToString(Object? obj) => obj.toString();

// Used by DartEntry::InvokeNoSuchMethod.
@pragma("vm:entry-point", "call")
dynamic _objectNoSuchMethod(Object? obj, Invocation invocation) =>
    obj.noSuchMethod(invocation);
    
.....    

6.扩展思考

我们查看Dio源码,发现使用Dio做网络请求,其实网络请求会交给HttpClient,由HttpClient的openUrl方法开启网络请求,而openUrl其实会调用到一个external方法,代码如下

我们在flutter3.3.2\flutter\bin\cache\dart-sdk\lib_internal\vm\socket_patch.dart找到了对应的实现,代码如下

我们继续查看这个_startConnect方法的调用过程,发现最终会调用到另一个external方法,代码如下

nativeCreateUnixDomainConnect也是一个external方法,我们暂时没有找到它的实现,但是我们其实可以知道Unix domain socket是一种终端,可以使同一台操作系统上的两个或多个进程进行数据通信,然后我们再看看nativeCreateUnixDomainConnect方法所在类的一些注释,会发现一些关键信息,代码如下

画了红圈是关键的一句注释,这句注释的意思就是说_NativeSocket封装了一个操作系统的socket,os是操作系统的意思,也就是说调用socket.nativeCreateUnixDomainConnect方法的时候会到调用操作系统的socket,也就是说网络请求其实是操作系统完成的,这就是为什么flutter应用是单线程模型的应用,但是在默认的isolate做网络请求却不会卡 UI 的原因,因为网络请求就不是dart层完成的,是操作系统完成的