0%

Flutter开源库源码解析(一)

package_info_plus源码分析

package_info_plus用于获取对应平台的包信息(注意只是获取自己的),目前最新版本是1.3.0。

使用时,调用fromPlatform方法,就可以拿到对应平台的自身包信息,fromPlatform方法源码如下:

static Future<PackageInfo> fromPlatform() async {
    if (_fromPlatform != null) {
      return _fromPlatform!;
    }

    final platformData = await _platform.getAll();
    _fromPlatform = PackageInfo(
      appName: platformData.appName,
      packageName: platformData.packageName,
      version: platformData.version,
      buildNumber: platformData.buildNumber,
      buildSignature: platformData.buildSignature,
    );
    return _fromPlatform!;
  }

_fromPlatform是缓存对象,而首次真正获取数据的,是调用_platform的getAll方法,_platform对象在不同平台上是不一样的,看下_platform的get实现:

static PackageInfoPlatform get _platform {
    if (__platform == null) {
      if (!_disablePlatformOverride && !kIsWeb) {
        if (Platform.isLinux) {
          __platform = PackageInfoLinux();
        } else if (Platform.isWindows) {
          __platform = PackageInfoWindows();
        }
      }
      __platform ??= PackageInfoPlatform.instance;
    }
    return __platform!;
  }

可以看到,不同平台,会对应不同的__platform对象,比如在windows上,对应的就是PackageInfoWindows对象,它会继承自PackageInfoPlatform抽象类,实现了getAll方法:

@override
Future<PackageInfoData> getAll() {
    final info = _FileVersionInfo(Platform.resolvedExecutable);
    final versions = info.productVersion!.split('+');
    final data = PackageInfoData(
      appName: info.productName ?? '',
      packageName: info.internalName ?? '',
      version: versions.getOrNull(0) ?? '',
      buildNumber: versions.getOrNull(1) ?? '',
      buildSignature: '',
    );
    info.dispose();
    return Future.value(data);
}

在windows上,最终是通过_FileVersionInfo去拿到信息的,这个是win32库的类,Platform.resolvedExecutable就是exe的文件路径,也就是说它只能获取到自己的,而不是获取windows上已安装的所有软件信息。而对于Android和ios,就是采用MethodChannel实现,最终会调用native api读取软件信息。

总结:package_info_plus新增了适配层,抽取出公共接口,对不同平台(使用Platform进行ifelse判断)赋予不同的__platform对象,然后各自实现了获取信息逻辑。

device_info_plus源码分析

package_info_plus用于获取对应平台的基础信息,目前最新版本是3.1.0。

它原理和package_info_plus类似,也是有一个_platform对象,然后不同平台对应不同的对象:

static DeviceInfoPlatform get _platform {
    if (__platform == null) {
      if (!_disablePlatformOverride && !kIsWeb) {
        if (Platform.isLinux) {
          __platform = DeviceInfoLinux();
        } else if (Platform.isWindows) {
          __platform = DeviceInfoWindows();
        }
      }
      __platform ??= DeviceInfoPlatform.instance;
    }

    return __platform!;
  }

对于windows平台,还是通过win32库实现信息获取,对于android和ios,采用MethodChannel获取。

win32源码分析

win32封装了windows相关api,从而实现在Flutter中直接调用对应的api,提高Flutter开发windows程序的效率。

该库的原理就是去加载常见的windows系统dll,然后找到常见的API函数,然后通过Dart封装起来,形成尽量简单的Dart接口,供第三方调用。以user32.dll为例,加载dll的代码如下:

final _user32 = DynamicLibrary.open('user32.dll');

然后它又封装了_CreateWindowEx和ShowWindow对应的Dart接口,因此在Dart中直接调用这两个接口,就可以弹出一个windows窗口:

// Create the window.
  final windowCaption = TEXT('Dart Native Win32 window');
  final hWnd = CreateWindowEx(
      0, // Optional window styles.
      className, // Window class
      windowCaption, // Window caption
      WS_OVERLAPPEDWINDOW, // Window style

      // Size and position
      CW_USEDEFAULT,
      CW_USEDEFAULT,
      CW_USEDEFAULT,
      CW_USEDEFAULT,
      NULL, // Parent window
      NULL, // Menu
      hInstance, // Instance handle
      nullptr // Additional application data
      );
  free(windowCaption);
  free(className);

  if (hWnd == 0) {
    final error = GetLastError();
    throw WindowsException(HRESULT_FROM_WIN32(error));
  }

  ShowWindow(hWnd, nShowCmd);

对于其它接口也是类似,这样就不需要自己去写C++然后生成dll,然后再给Dart调用了,当然如果没有对应的系统dll和API,那还是需要自己用C++实现的。

call源码分析

call用于在Flutter中加载dll或者so等,如果通过ffi的DynamicLibrary.open方法进行加载,那么需要传入要加载的文件的路径,这个在不同平台(Windows,Linux或者macOS)上是不一样的,因此call这个库就做了适配层,通过使用这个库,直接把文件放assets目录下,然后加载时传文件名就可以了,加载时用的是getDyLibModule方法,代码如下:

ffi.DynamicLibrary getDyLibModule(String module) {
  String path = '';
  if (Platform.isWindows) {
    path = Windows().getCurrentPath();
  } else if (Platform.isLinux) {
    path = Linux().getCurrentPath();
  } else if (Platform.isMacOS) {
    path = Macos().getCurrentPath();
  } else {
    throw 'The version lib doesn\'t support the platform';
  }

  return ffi.DynamicLibrary.open(join(path, module));
}

这里会对不同平台做适配,从而获取到对应的路径,以windows为例,对应Windows类:

class Windows extends TargetPath {
  @override
  String getDebugPath() => join(Directory.current.path,
      'build\\windows\\runner\\Debug', 'data', 'flutter_assets');
}

Linux和macOS也是类似的原理。