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也是类似的原理。