Flutter Android 打包保姆式全流程 2023 版
大家好,我是 17。
为什么要写这篇文章呢?对于一没有 android 开发经验,从未有过打包经历的新人来说,要想成功打包,是很困难的。因为受到的阻碍太多,是完全陌生的领域,几乎是寸步难行。如果有老人带还行,如果没有,那就只能自己四处找人或搜索资料求指导了。可惜的是,网上的文章不是过时了,就是不全,或是讲的不够明白,要几经坎坷才能成功。但是只要成功打包一次,回头再看,其实也是满简单的嘛,所缺少的只是一份完整的资料而已。
为了能让首次打包 android 的小伙伴不必再经历四处碰壁的磨难,17 决定写这篇文章,从此 android 打包不求人。虽然已经很详细,但还是不可能面面俱到,保证能发包。有的地方只是点到为止。
操作系统 Mac OS
- Flutter 版本 3.7.5
- android SDK 33
在打包之前,首先检查 pubspec.yaml 中的依赖,删除没有用到的依赖,减少包的大小。如果需要网络权限,需要在 android/app/src/main/AndroidManifest.xml 中添加网络权限申请。
...<uses-permission android:name="android.permission.INTERNET" /><uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
</manifest>
权限 INTERNET 允许网络连接。
权限 ACCESS_NETWORK_STATE 允许程序获取网络信息状态。如果用不到,这条权限可以不申请。
如果要用 http ,但是需要在 AndroidManifest.xml 的 applictation 加如下配置 android:usesCleartextTraffic="true"
下面详细说下打包的主要过程。
设置应用 ID 和包名
在建项目的时候可以直接设置应用 ID 和包名。
–org 指定 applicationId 的前缀,默认为 "com.example”,我们指定为 com.iam17,项目名为 app17。
flutter create --org=com.iam17 app17
创建成功后,项目的应用 ID(applicationId) 是 com.iam17.app17。包名(package) 也是 com.iam17.app17
默认情况下,项目的软件包名称与应用 ID 是一样的。
如果开始无法确定 org,到发版的时候才确定怎么办。 对于新手来说,与其做修改,不如直接新建项目,把 lib 下面的文件 copy 过来,并对 android 下面的内容做相应修改,这样肯定效率更高,也不容易出错。
应用 ID 和包名的区别
你一定会很奇怪,既然他们两个一样,为什么不用一个?
他们两个一个主外,一个主内。
应用 ID 主外,是 android app 的身份证,全球唯一。一般是把域名倒过来,加上你的 app 的名字。
包名主内,包名是 app 代码需要的。
-
它将此名称用作应用生成的
R.java
类的命名空间。示例:对于上面创建的项目,
R
类将为com.iam17.app17.R
。 -
它会使用此名称解析清单文件中声明的任何相关类名。
一个应用使用一个包名,但可以使用不同的应用 ID 发布为多个不同的应用,在应用市场上可以共存,这就是为什么要把应用 ID 和 包名分开的原因。
应用 ID 的位置
应用 ID 在gradle在 android/app/build.gradle 的 defaultConfig 中设置,新版本的 flutter 在新建项目的时候已经自动设置好了。
android {defaultConfig {applicationId = "com.iam17.app17"...
如果没有设置 applicationId,为了与以前的 SDK 工具兼容,构建工具会将 AndroidManifest.xml
文件中的包名称用作应用 ID。在这种情况下,重构您的软件包名称也会更改您的应用 ID。
包名的位置
AndroidManifest.xml 文件中的 package 属性就是包名。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"package="com.iam17.app17"...>
package 轻易不要修改,修改起来比较麻烦。不是光修改这一个地方就完事了,还要调整目录结构,修改文件中的代码。
包名和应用 ID 的位置了解一下即可,一般不需要我们手动去修改。
修改版本号
打开 pubspec.yaml 找到 version: 1.0.0+1
。
可能有的小伙伴对版本号不知道为什么用 3 位,我来解释一下。
一般来讲大部分的软件版本号 分 3 段,比如 A.B.C
A 表示大版本号,一般当软件整体重写,或出现不向后兼容的改变时,增加A。
一般来说,第一版发布的时候是 1.0.0。
B 表示功能更新,出现新功能时增加B。
比如原来没有点赞功能,增加点赞功能的版本可以是 1.1.0。
有的时候,软件还在快速迭代中,很容易出现向前不能兼容的情况,这个时候,也可以直接用 B 来代替 A 的作用。比如发第一版的时候,用 0.1.0。等稳定后再发 1.0.0 版本。一般来说 A 是不应该经常变动的。
C 表示小修改,如修复bug,只要有修改就增加C
C 的更新可以不经测试,直接更新。如果功能要修改,也必须要兼容以前的版本,也就是说接口是不能修改的,只能修改逻辑实现。
还有后面的 +1 是怎么回事? +号可以看做是分隔符。1 是累计版本,从 1 开始,只能增加,不能减小,并且,每次发版必须增加。这个版本号是给内部用的。比如苹果商店,android 平台都用这个版本号来判断软件的新旧。3 位版本号是给用户看的。3 位版本号包含软件更新的信息,让人一看就知道是重大更新,还是功能增加,还是 bug 修复。
消除 build.gradle 中的错误提示
打开 android/app/build.gradle 后,你可能会发现有一个错误提示:unable to resolve class GradleException。
新建的项目可能也有这个问题,虽然不影响编译,但是有这个错误在总是心里不舒服。我们可以把 GradleException 换成 FileNotFoundException 错误提示就消失了。
设置 sdk 版本
在 android 下面有三个版本相关的配置 compileSdkVersion,minSdkVersion,targetSdkVersion,需要我们关注一下。
- compileSdkVersion 告诉 gradle 用哪个 Android SDK 版本编译你的应用。compileSdkVersion 决定了你可以用哪个版本的 api。比如你的 compileSdkVersion 是 31,如果你在代码中用了 33 的 api,编译会报错。一般我们都把 compileSdkVersion 设置为最高版本。
- minSdkVersion 是应用可以运行的最低版本要求。也就是你要向下兼容哪些版本。根据 app 的用户特点,尽量设得高一些,否则很多插件不能用。
- targetSdkVersion 告诉 android 系统,app 要在哪个版本运行。android 的 api 在不同版本之间,虽然接口没有变化,但内部的行为可能发生了变化。当你指定了 targetSdkVersion 后,一定要在这个版本做完整的测试。新开项目 targetSdkVersion 应该和 compileSdkVersion 保持一致。支持新近的 API 级别有助于让您的应用利用平台的最新功能,为用户提供愉悦的体验。
说了半天,你可能最想知道,到底写什么?既然是新手第一次发包,那项目应该是新的。推荐这样设置。
compileSdkVersion 33
minSdkVersion 23
targetSdkVersion 33
33 是当前 Android SDK 最新版本,如果你本机没有安装 SDK 33,需要先安装。
具体要如何修改呢?两个方案
- 修改 flutter安装目录/packages/flutter_tools/gradle/flutter.gradle
class FlutterExtension {static int compileSdkVersion = 33static int minSdkVersion = 16static int targetSdkVersion = 33...
默认的 minSdkVersion 竟然是 16,一定得修改,这个确实太低了。注意,在这里修改会影响到所有项目。
- 修改项目中的 android/app/build.gradle
比如修改 minSdkVersion,把变量换成常就行
defaultConfig {applicationId "com.iam17.app17"minSdkVersion flutter.minSdkVersion...
修改为
defaultConfig {applicationId "com.iam17.app17"minSdkVersion 23...
另外两个已经是最新了,就不用修改了。如果需要修改,修改为 33 就行。虽然有两种方案,但 17 推荐第 2 种,直接在项目中修改。
添加 launcher icon
launcher icon 就是下面图中的这些小方块。
如果要手动设置 launcher icon 还是很麻烦的。幸运的是,这个问题被一个叫 flutter_launcher_icons 的插件解决了。所以原来那个超级麻烦的问题现在就转变为学会使用这个插件就行了。
- 安装 flutter_launcher_icons 插件
flutter pub add dev:flutter_launcher_icons
注意前面的 dev:
表示作为开发期间的依赖,flutter_launcher_icons 会出现在 dev_dependencies 下面。出现在这里的好处是等发版的时候 flutter_launcher_icons 不会被包括里 apk 中。
-
准备一张 1024x1024 的图片。如果你手边没有合适的,17 亲手画了一张,可以 点击这里,打开原图,右键另存下载。在根目录下建一个文件夹 images,把图片命名为 icon1024.png 放入 images 文件夹中。
-
添加配置信息,打开 pubspec.yaml
flutter_icons:image_path: "images/icon1024.png"android: true ios: true
- 执行创建命令
flutter pub run flutter_launcher_icons:main
- 检查生成的图片
打开 android/app/src/main/res/drawable 文件夹查看已经生成好的图片,发现所有图片都已经自动生成好了。
为 ios 生成的图片路径 在 ios/Runner/Assets.xcassets/AppIcon.appiconset
注意事项
- Format: 32-bit PNG
- icon 大小必须是 1024x1024
- 确保在 40px 大小时也能清晰可见,这是 icon 最小的尺寸。
- icon 不能大于 1024KB
- icon 不能是透明的。
- 必须是正方形,不能有圆角。
icon 的边可能会被切掉,因为 Icon 最后展示的可能不是正方形,所以一般边上需要留白。
设置 App Name
App Name 就是 App 安装成功后在图标下面显示的名字。
打开 flutter根目录/android/app/src/main/AndroidManifest.xml,
<applicationandroid:label="App Name" > //在这修改 Android App Name。
设置 Splash Screen
虽然可以手动设置,但对于新手来说,用 flutter_native_splash 这个插件会更方便一些。
安装
flutter pub add flutter_native_splash
在项目根目录下找到文件夹 images,把 splash.png 放入其中。
在 pubspec.yaml 的末尾加上配置
flutter_native_splash:color: "#42a5f5"image: images/splash.png
执行创建命令
flutter pub run flutter_native_splash:create
运行,第一时间就会看到启动画面了。splash.png 会居中显示。
说明
- splash.png 要准备 4x 的。插件会自动为我们生成小分辨率的
- 每次修改配置都得重新执行
flutter pub run flutter_native_splash:create
- 不光是配置了 android 的启动页面,ios,web 的也一起生成好了。
有一个小福利。默认情况下当 flutter 开始绘制的时候, Splash Screen 就自动消失了。但这个时候,首屏的数据可能还没有准备好,还无法显示画面,我们可能还得准备一个 loading 的画面。现在你可以继续显示 Splash Screen,直到首屏数据准备完毕。
代码很好写的。我们用 Timer 来模拟数据准备。
void main() {WidgetsBinding widgetsBinding = WidgetsFlutterBinding.ensureInitialized();FlutterNativeSplash.preserve(widgetsBinding: widgetsBinding);runApp(const MyApp());Timer(Duration(seconds: 10), () {FlutterNativeSplash.remove();});
}
如果你哪天想自己配置可以恢复到默认状态
flutter pub run flutter_native_splash:remove
现在的启动页面只有一张图片,但一般在启动页面的底部还会有一个 品牌 logo。你可以把 logo 合并在主图上,但这样就无法保证 logo 出现在底部的位置。所以 logo 需要单独用一张图片。
在 pubspec.yaml 中增加 branding 的配置。
flutter_native_splash:color: "#42a5f5"image: images/splash.pngbranding: images/logo.png
重新执行 flutter pub run flutter_native_splash:create
就可以看到 logo 了。如果你觉得 logo 太靠下了,可以把 logo 的图片下面留点空白。
签名
要想把 app 发布到商店,必须要对 app 进行签名。签名有两种方式,命令式,和交互式。命令式对新手不大友好,我用交互的方式来说明。
在 IDE 中运行或调试项目时,AS 会自动使用 Android SDK 工具生成的调试证书为我们的应用签名,路径为 $HOME/.android/debug.keystore
,但是应用商店可不接受使用调试证书发布的应用签名。为了给应用签名,需要先创建密钥。
创建密钥
打开 Android Studio ,找到 Build 菜单下面的 Generated Signed Bundle/APK
会弹出下面这个对话框
有两个选项
- Android App Bundle 如果你要上传到 google 商店,选这个。你暂可以理解为 这是 APK 优化方案,可惜不是所有的商店都支持。
- APK 就是传统的打包,为了兼容所有商痁,我们选这个就好了。
接下来又会弹窗
Key store path: 是你要将 key 保存在哪里,可以先找个地方放,后面可以 copy 到其它地方。应在位置路径末尾添加一个扩展名为 .jks
的文件名。点击 Create new 新建一个,比如文件名可以叫 key17.jks。
key store passowrd: 为您的密钥库创建并确认一个安全的密码。设置好了必须要记住。
- Alias:为您的密钥输入一个标识名。 比如可以叫 key17。
- Password:为您的密钥创建并确认一个安全的密码。它应该与密钥库密码相同。具体原因就不用管了,把它设成和 key store passowrd 一样就没问题了。密码要求至少 6 位。
- Validity (years) :以年为单位设置密钥的有效时长。密钥的有效期应至少为 25 年,以便您可以在应用的整个生命期内使用同一密钥为应用更新签名。我直接写 100 年,让它没有过期的可能。
Certificate: 为证书输入一些关于您本人的信息。此信息不会显示在应用中,但会作为 APK 的一部分包含在您的证书中。只有第一项是必须填的,其它的空着就行。当然了,如果写全了更好。
选中 release,点下面的按钮 create。然后签名文件就生成好了。
使用密钥为应用自动签名
先把创建好的 key 文件 copy 到 android 目录下。
建立 keystore.properties 文件
在 android 文件夹下建立名为 keystore.properties 文件,不叫这个名也可以,但一般都这么叫,没有特别的原因,还是用这个名字为好。在文件里放如下内容
storePassword=123456
keyPassword=123456
keyAlias=key17
storeFile=../key17.jks
注意:这里的 keyAlias 要和前面设置的 key alias 保持一致,否则找不到 key。密码当然也要一样。
添加 gradle 配置
在 build.gradle 文件中,按如下方式加载 keystore.properties 文件(必须在 android 代码块前面)
def keystorePropertiesFile = rootProject.file("keystore.properties")
def keystoreProperties = new Properties()
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))android {...
}
输入存储在 keystoreProperties
对象中的签名信息
android {signingConfigs {release {keyAlias keystoreProperties['keyAlias']keyPassword keystoreProperties['keyPassword']storeFile file(keystoreProperties['storeFile'])storePassword keystoreProperties['storePassword']}}buildTypes {release {signingConfig signingConfigs.release}}...
}
...
多渠道统计
apk 会发到不同的商店,你可能想知道哪个商店的下载量最大。这就需要渠道统计,这个功能要用到 dart 参数。
--dart-define
来指定不同的 dart 参数,比如
flutter build apk --dart-define channel=huawei
在 dart 代码里可以通过 String.fromEnvironment
获取到对应的自定义配置参数。
const channel = String.fromEnvironment('channel');
dart 代码拿到 channel 后就可以发统计数据出来。
发布优化
启用缩减、混淆处理和优化功能,
android {buildTypes {release {minifyEnabled trueshrinkResources trueproguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'}}...
}
发布和安装
现在执行发布命令
flutter build apk --release
--release
可以省略,是默认值。
不出意外的话,build 成功可以看到类似如下信息
Built build/app/outputs/flutter-apk/app-release.apk (37.6MB)
apk 已经生成了,怎么装到手机上呢?三种方法。
- 通过网络把 apk 发到手机上。比如有的包就是通过一个 http 链接发布的,下载后可以直接安装。
- 通过 adb 命令安装
adb install build/app/outputs/flutter-apk/app-release.apk
- 通过
flutter install
命令安装
优化
虽然做为新手,能打包成功就已经满心欢喜了,但还可以做的更好。
apk 大小优化
不同的 Android 设备使用不同的 CPU,而不同的 CPU 支持不同的指令集。CPU 与指令集的每种组合都有专属的应用二进制接口 (ABI)。ABI 目前有两大类 arm 和 x86。arm 是当前手机的主流,x86 出现在模拟器上。flutter 默认支持 arm 和 x86 两种 ABI。我们用 android studio 查看一下。把刚才生成的 apk 直接拖到 android studio 上就行。查看 lib 文件夹,发现 flutter 为我们生成了三种 ABI 的文件。
一种手机只用一种 ABI,所以把它们分别打包会更好些,体积会大大减小。只需要加一个参数就好 。
flutter build apk --split-per-abi
再看输出目录,原来的一个 apk 包现在分成三个了,每个只有原来 apk 1/3 大小。
ABI 的更多信息见 这里
到此,基本的打包流程就讲完了,可以发布了。恭喜你,完成了从 0 到 1 的跨越!
如果你的产品只发布为一个版本,下面的内容可以先跳过。虽然有开发环境,测试环境,也需要打不同的包,因为差异不大,可以用简单的办法解决。比如用 --dart-define
设置变量。
用 applicationIdSuffix 为开发版设置不同的 applicationId。
buildTypes{...debug {applicationIdSuffix ".debug"debuggable true}
}
多版本打包
可能你的产品还需要根据不同的用户出不同的版本,比如免费版本,收费版。
多版本要面临的问题主要有三个
- 如何让多版本共存。
- 如何做个性化设置
- 优化打包。你可以在所有版本中把所有的功能都加上,简单的用开启关闭来控制。但这样会导致 apk 过大,也不大安全。
如果都手动配置,学习成本有点高,对于新手来说,可以我们可以用 flutter_flavorizr 这个插件,帮我们处理好许多事情,让我们有一个好的起点。
安装
flutter pub add dev:flutter_flavorizr
如果你这样执行安装命令的是话,会报错,原因是和前面的插件 flutter_launcher_icons,flutter_native_splash 的依赖冲突。解决的方法是把所有冲突的插件的 版本都改成 any,执行 flutter pub get
让 flutter 自己去处理冲突的问题。等安装成功后,再到 pubspec.lock 中找到对应的版本写入 pubspec.yaml 中。
配置
在 pubspec.yaml 的末尾加上如下配置
、
flavorizr:flavors:apple:app:name: "Apple App"android:applicationId: "com.example.apple"ios:bundleId: "com.example.apple"banana:app:name: "Banana App"android:applicationId: "com.example.banana"ios:bundleId: "com.example.banana"
flavors 可以理解为多版本发布。这个配置中有两个版本发布配置,apple 和 banner。
name 指定 App Name 为 apple App,Banana App,App Name 就是 App 安装成功后在图标下面显示的名字。
applicationId 是 app 的身份证。这里我们看到 applicationId 是不同的,代表两个完全不同的 app,但包名是同一个。从这个实例中我们可以更加清楚的看到 applicationId 和 package 是独立的两个设置,不能混为一谈。
执行命令
flutter pub run flutter_flavorizr
等命令执行完成,发现插件为我们做了很多事情。
自动完成 flavor 配置
build.grade 中增加了 flavor 的配置
// ----- BEGIN flavorDimensions (autogenerated by flutter_flavorizr) -----flavorDimensions "flavor-type"productFlavors {apple {dimension "flavor-type"applicationId "com.example.apple"resValue "string", "app_name", "Apple App"}banana {dimension "flavor-type"applicationId "com.example.banana"resValue "string", "app_name", "Banana App"}}// ----- END flavorDimensions (autogenerated by flutter_flavorizr) -----
注意:BEGIN ,END 这两行注释不要做修改,也不能删除,否则插件无法维护这里面的配置了。
自动添加资源文件
在 android/app/src 下多了两个文件夹 banner,apple 里面有启动 icon。
自动增加不同版本的入口文件
lib 下面增加了四个文件:main_apple.dart,main_banana.dart,app.dart,flavios.dart
原来的 main.dart 还是在的,我们执行 flutter run 还是会启动 main.dart,就像没有执行过 flutter pub run flutter_flavorizr
一样。
flavor 打包
为了能进行 flavor 打包,可以这样执行命令
flutter run --flavor apple -t lib/main_apple.dart
你会发现,这次启动入口是 main_apple.dart,启动图标也换了。这次会安装一个新的 app,和原来 main.dart 生成的 app 共存。
我们把 banner app 也装一下。
flutter run --flavor banana -t lib/main_banana.dart
这次也会新安装一个 app,和前面的 apple app,main app 并存。
main app 的 applicationId 是 com.iam17.app17,apple app 的 applicationId 是 com.example.apple ,它们可以完全不一样,applicationId 就是一个身份标识,只要全球唯一就行。我们做的产品的时候,不可能这样随意设置 applicationId,一般前面的不变,只修改最后的名字。比如可以这样设置
com.iam17.app17.free
com.iam17.app17.pro
com.iam17.app17.test
com.iam17.app17.debug
设置 api url
不同版本的 api 的地址可能是不同的。我们可以打开 flaors.dart,仿照 title 增加一个apiUrl,在需要的地方,用 F.apiUrl
获取地址。
static String get apiUrl {switch (appFlavor) {case Flavor.APPLE:return 'http://apple/api';case Flavor.BANANA:return 'http://banana/api';default:throw UnimplementedError();}}
flaors.dart 默认是受 插件管理的,如果你再次执行 flutter pub run flutter_flavorizr
,flaors.dart 的内容会被重置。为了避免内容被重置,下次再执行命令的时候,需要挑选 processor 执行。
flutter pub run flutter_flavorizr -p <processor_1>,<processor_2>
vscode 配置 launch.json
为了启用不同的 flavor,需要在执行命令的时候加个 flavor 参数 和 target(-t) 参数,每次写这么长的命令很不方便。我们可以在 vscode 中增加 lauch.json 把命令记录下来。下次运行的时候,只要点个按钮就可以了。
增加 launch.json
如果你的项目里没有 lauch.json 点红框那个图标,就会有添加提示,点击 create a lauch.json file,就会自动为你添加一个。
替换成下面的内容
{"version": "0.2.0","configurations": [{"name": "apple","program": "./lib/main_apple.dart","request": "launch","type": "dart","args": ["--flavor","apple"]},{"name": "banana","program": "./lib/main_banana.dart","request": "launch","type": "dart","args": ["--flavor","banana"]}]
}
同样点左面那个 run and debug 图标,这时会出现我们刚配置好的两从此启动,apple和 banana。apple 前面打勾,表示默认,直接按 F5 会用启动 apple,可以点 banana,把默认改为 banana。
为了齐整,现在可以把默认的 main.dart 删除,把闪屏相关的代码也 copy 到 main_apple.dart 中。把 launcher icon 也 copy 到 apple 下面。为了能让大家看到执行 launcher icon 插件的效果,main 下面的 icon 就不删除 了。
可能有些地方讲的还不是很清楚,17 特地准备了 android flavor 项目示例,完整代码 在这里。
本文用了三个插件来解决三个不方便处理的问题,以便降低打包难度,快速上手。在使用插件的时候,应该了解插件做了什么,最后应该能做到手动也可以处理。
本文到这里就结束了,希望小伙伴们都能顺利 build 成功!如果哪里还有疑问,欢迎留言。
番外
这篇文章要写好很不容易,修改了多少次都不记得了。涉及到的内容非常多,我想把应提到的点都提到,但又不至于过于冗长。我想让学习曲线变得平缓,把多版本打包做为可选内容放在了最后。为了能先看到效果,先讲了基本流程,保证能打出包来。17 写的很辛苦,周末又是两天没有出门。本来想分两篇文章发的,因为内容有点长,但考虑到是全流程,还是一起发了。 这一次 17 又是耗尽所有的力气,直到周一早上才最后定稿,小伙伴快点给 17 在评论区评论加油吧。
相关文章:
Flutter Android 打包保姆式全流程 2023 版
大家好,我是 17。 为什么要写这篇文章呢?对于一没有 android 开发经验,从未有过打包经历的新人来说,要想成功打包,是很困难的。因为受到的阻碍太多,是完全陌生的领域,几乎是寸步难行。如果有老…...

C++笔记之lambda表达式
引言 Lambda表达式是从C 11版本引入的特性,利用它可以很方便的定义匿名函数对象,通常作为回调函数来使用。大家会经常拿它和函数指针,函数符放在一起比较,很多场合下,它们三者都可以替换着用。 语法 [ captures ] (…...

flink大数据处理流式计算详解
flink大数据处理 文章目录flink大数据处理二、WebUI可视化界面(测试用)三、Flink部署3.1 JobManager3.2 TaskManager3.3 并行度的调整配置3.4 区分 TaskSolt和parallelism并行度配置四、Source Operator(资源算子)五、Sink Operator(输出算子)六、Flink滑…...
Java面试题(二十三)DCL单例
懒汉式单例 private static SingletonInstance INSTANCE;private SingletonInstance(){}public static SingletonInstance getInstance() {if (INSTANCE null) {INSTANCE new SingletonInstance();}return INSTANCE;}构造方法私有化,然后判断是否为空,…...
UML-类图
一、类 一个类由三个格子组成,从上至下分别表示: 第一格:类名称(接口和抽象类,使用斜体) 第二格:类的属性(成员变量,可以没有) 第三格:类的操作&…...

PostgreSQL 数据库和 pgAdmin 4
PostgreSQL 数据库和 pgAdmin 4PostgreSQLPostgreSQL 数据库安装PostgreSQL 数据库安装 (Ubuntu)PostgreSQL 数据库其他系统安装PostgreSQL 数据库快速使用入门登录数据库访问数据库参考pgAdmin 4pgAdmin 4 安装使用 pgAdmin 4 登录数据库参考PostgreSQL PostgreSQL 数据库安装…...

quarkus 搭建与基础开发环境配置总结
quarkus搭建与基础开发环境配置总结 大纲 基础概念quarkus2.13.7脚手架工程配置配置maven3.8.7quarkus快速启动quarkus的三种打包方式quarkus将程序打包为二进制文件window环境下quarkus云原生二进制文件打包环境搭建使用GraalVM-java11替换本地java8运行二进制文件 基础概念…...

扩散模型DDPM开源代码的剖析【对应公式与作者给的开源项目,diffusion model】
扩散模型DDPM开源代码的剖析【对应公式与作者给的开源项目,diffusion model】一、简介二、扩散过程:输入是x_0和时刻num_steps,输出是x_t三、逆扩散过程:输入x_t,不断采样最终输出x_0四、具体参考算法流程图五、模型mo…...
C语言 学生记录管理系统
学生记录管理系统 1--添加 2--删除 3--查询:按姓名 4--查询:按班级 5--查询:按学号 0--退出 请选择操作序号(0—5):1 请输入新学生的学号:1 请输入新学生的…...
【独家】华为OD机试 C 语言解题 - 交换字符
最近更新的博客 华为od 2023 | 什么是华为od,od 薪资待遇,od机试题清单华为OD机试真题大全,用 Python 解华为机试题 | 机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南)华为od机试,独家整理 已参加机试人员的实战技巧文章目录 最近更新的博客使用说明本期…...

网络安全平台测试赛 easyphp(phar脏数据处理)
昨天的比赛,14.00-17.00.时间有点紧张,比赛期间没拿下来这道 😭非常痛苦,很顺畅的思路 一步步想下来,卡在最后一步末尾脏数据处理了,最后时间到了 没打通,还需多练 这里本地复现一下࿱…...

【python】XML格式文件读写详解
注:最后有面试挑战,看看自己掌握了吗 文章目录XML介绍格式XML与AJAX与HTML区别联系生成XML文件案例用SAX模块处理XML用DOM模块处理XML🌸I could be bounded in a nutshell and count myself a king of infinite space. 特别鸣谢:…...

理解js的精度问题
参考博客:js精度丢失问题-看这篇文章就够了(通俗易懂)、探寻 JavaScript 精度问题以及解决方案、JavaScript 浮点数陷阱及解法 1 为什么 JavaScript 中所有数字包括整数和小数都只有一种类型 即 Number类型,它的实现遵循 IEEE 754 标准。 符号位S&#…...

蓝桥杯 时间显示
题目 输入输出样例 示例 1 输入 46800999输出 13:00:00示例 2 输入 1618708103123输出 01:08:23评测用例规模与约定 对于所有评测用例,给定的时间为不超过 10^{18}1018 的正整数。 运行限制 最大运行时间:1s最大运行内存: 512M 基础知识 时间的转换…...
qt中设置菜单高度
如题所示,我建立一个菜单,代码如下,但是菜单项的高度太小了, { popupMenu new QMenu(this); QAction *action1 new QAction(tr(“&New1”), this); QAction *action2 new QAction(tr(“&New2”), this); QA…...

测开:前端基础-css页面布局-定位
一 、传统网页布局的三种方式 网页布局的本质–用CSS来摆放盒子,把盒子摆放到相应的位置,css提供了三种传统布局方式,分别是标准流,浮动和定位三种。 二、 定位 2.1 啥是定位 我的理解,就是要把这个元素,…...
Servlet中八个监听器介绍
一、监听对象创建的监听器 1、ServletContextListener /*** 用于监听ServletContext对象创建和销毁的监听器* since v 2.3*/public interface ServletContextListener extends EventListener {/*** 对象创建时执行此方法。该方法的参数是ServletContextEvent事件对象…...

LicenseBox Crack,对服务器的要求最低
LicenseBox Crack,对服务器的要求最低 LicenseBox是用于管理基于PHP的软件、WordPress插件或主题、主题、插件和WordPress的更新和许可的完整软件。它易于安装,对服务器的要求最低,用户友好的界面,无限脚本的使用为您的创造力打开了大门。 Li…...

css中重难点整理(vertical-align)
一、vertical-align 在学习vertical-align的时候,可能会很困惑。即使网上有一大推文章讲veitical-align,感觉看完好像懂了,等自己布局的时候用到vertical-align的时候好像对它又很陌生。这就是我在布局的时候遇到的问题。 本来vertical-align就很不好理…...

javaScript基础面试题 ---宏任务微任务
宏任务微任务一、为什么JS是单线程语言?二、JS是单线程,怎样执行异步代码?1、JS是单线程语言 2、JS代码执行流程,同步执行完,再进行事件循环(微任务、宏任务) 3、清空所有的微任务,再…...
浅谈 React Hooks
React Hooks 是 React 16.8 引入的一组 API,用于在函数组件中使用 state 和其他 React 特性(例如生命周期方法、context 等)。Hooks 通过简洁的函数接口,解决了状态与 UI 的高度解耦,通过函数式编程范式实现更灵活 Rea…...
conda相比python好处
Conda 作为 Python 的环境和包管理工具,相比原生 Python 生态(如 pip 虚拟环境)有许多独特优势,尤其在多项目管理、依赖处理和跨平台兼容性等方面表现更优。以下是 Conda 的核心好处: 一、一站式环境管理:…...

(二)TensorRT-LLM | 模型导出(v0.20.0rc3)
0. 概述 上一节 对安装和使用有个基本介绍。根据这个 issue 的描述,后续 TensorRT-LLM 团队可能更专注于更新和维护 pytorch backend。但 tensorrt backend 作为先前一直开发的工作,其中包含了大量可以学习的地方。本文主要看看它导出模型的部分&#x…...
条件运算符
C中的三目运算符(也称条件运算符,英文:ternary operator)是一种简洁的条件选择语句,语法如下: 条件表达式 ? 表达式1 : 表达式2• 如果“条件表达式”为true,则整个表达式的结果为“表达式1”…...
鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个医院挂号小程序
一、开发准备 环境搭建: 安装DevEco Studio 3.0或更高版本配置HarmonyOS SDK申请开发者账号 项目创建: File > New > Create Project > Application (选择"Empty Ability") 二、核心功能实现 1. 医院科室展示 /…...

2021-03-15 iview一些问题
1.iview 在使用tree组件时,发现没有set类的方法,只有get,那么要改变tree值,只能遍历treeData,递归修改treeData的checked,发现无法更改,原因在于check模式下,子元素的勾选状态跟父节…...
【学习笔记】深入理解Java虚拟机学习笔记——第4章 虚拟机性能监控,故障处理工具
第2章 虚拟机性能监控,故障处理工具 4.1 概述 略 4.2 基础故障处理工具 4.2.1 jps:虚拟机进程状况工具 命令:jps [options] [hostid] 功能:本地虚拟机进程显示进程ID(与ps相同),可同时显示主类&#x…...
DeepSeek 技术赋能无人农场协同作业:用 AI 重构农田管理 “神经网”
目录 一、引言二、DeepSeek 技术大揭秘2.1 核心架构解析2.2 关键技术剖析 三、智能农业无人农场协同作业现状3.1 发展现状概述3.2 协同作业模式介绍 四、DeepSeek 的 “农场奇妙游”4.1 数据处理与分析4.2 作物生长监测与预测4.3 病虫害防治4.4 农机协同作业调度 五、实际案例大…...

10-Oracle 23 ai Vector Search 概述和参数
一、Oracle AI Vector Search 概述 企业和个人都在尝试各种AI,使用客户端或是内部自己搭建集成大模型的终端,加速与大型语言模型(LLM)的结合,同时使用检索增强生成(Retrieval Augmented Generation &#…...
代码随想录刷题day30
1、零钱兑换II 给你一个整数数组 coins 表示不同面额的硬币,另给一个整数 amount 表示总金额。 请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回 0 。 假设每一种面额的硬币有无限个。 题目数据保证结果符合 32 位带…...