Flutter1.9.1+hotfix.2升级到Flutter1.12.13+hotfix.8指南
- 升级目的
- Flutter版本下载
- 引入自动升级功能所需资源
- 配置Flutter项目文件内容
- 1. /android/build.gradle文件 (注释掉的代码是原代码)
- 2. /android/gradle.properties文件(注意:需要把汉字注释删掉!!!)
- 3. /android/app/build.gradle文件
- 4. 调整/main/AndroidManifest.xml文件内容
- 5. 将项目中所有引入FlutterActivity的地方,做检查并替换(如下)
- 6. 将项目中所有重写onCreate方法的代码注释掉,因为该版本的Flutter已做处理
- 7. 将项目中所有以PluginRegistry 为参数的registerWith(PluginRegistry registry)方法注释掉,因为该方法的参数在该版本的Flutter中参数已变为@NonNull FlutterEngine flutterEngine
- 8. 将项目中继承FlutterApplication并实现PluginRegistry.PluginRegistrantCallback的代码段注释掉
- 9. 新建xml文件,名为file_paths (注:该文件名称需要与AndroidManifest.xml里的第三点标签内的android:resource保持一致),内容如下:
- 10.在项目的main.dart文件第一行添加代码: ==WidgetsFlutterBinding.ensureInitialized();==
- 配置命令打包文件
- 编写app代码
- 安卓权限设置
- 更换apk版本
升级目的
1、使用package_info包内方法获取flutter程序版本和版本构建号;
2、使用permission_handler包内方法获取手机权限;
3、使用ota_update包内方法进行软件下载、进度加载、下载状态区分;
4、使用path_provider包内方法获取app安装路径;
5、使用install_plugin包内方法进行软件安装;
Flutter版本下载
- 登录网站自行下载 ;
- 配置当前Flutter项目的FlutterSDK:File–>Settings–>Languages & Frameworks–>Flutter->SDK面板:Flutter SDK path 路径调整(指向下载后的flutter包即可);
- 配置当前Flutter项目的DartSDK:File–>Settings–>Languages & Frameworks–>Dart主面板:Dart SDK path路径调整(指向下载后的flutter包下的bin\cache\dart-sdk包);
- 找到项目下local.properties文件,更改flutter.sdk为下载后的flutter包;
引入自动升级功能所需资源
- permission_handler: ^4.0.0
- path_provider: ^1.5.1
- ota_update: ^2.0.2
- install_plugin: ^2.0.0
- package_info: ^0.4.0+3;
- 运行 Package get命令(下载资源)
- 若因为Flutter版本调整导致其他资源版本无法使用,则按需调整或者暂时调整为: any
(例如:flutter_image_compress: any)
配置Flutter项目文件内容
1. /android/build.gradle文件 (注释掉的代码是原代码)
buildscript {
// ext.kotlin_version = '1.2.71'
ext.kotlin_version = '1.3.0'
repositories {
google()
jcenter()
}
dependencies {
// classpath 'com.android.tools.build:gradle:3.2.1'
classpath 'com.android.tools.build:gradle:3.3.0'
// classpath 'com.android.tools.build:gradle:4.0.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
2. /android/gradle.properties文件(注意:需要把汉字注释删掉!!!)
org.gradle.jvmargs=-Xmx1536M
android.enableR8=true
android.useAndroidX=true
android.enableJetifier=true
3. /android/app/build.gradle文件
//检查默认版本号和版本构建号书写方式:如下 flutterVersionName 为版本号,flutterVersionCode 为版本构建号
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
flutterVersionCode = '1'
}
def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
flutterVersionName = '1.0.0'
}
//添加命令打包所需的秘钥文件声明
def keystorePropertiesFile = rootProject.file("key.properties")
def keystoreProperties = new Properties()
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
//在android{}内的defaultConfig{}中添加 multiDexEnabled 并调整 testInstrumentationRunner值如下:
defaultConfig {
multiDexEnabled true
// testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
//在android{}内添加如下代码
signingConfigs {
release {
keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword']
storeFile file(keystoreProperties['storeFile'])
storePassword keystoreProperties['storePassword']
}
}
//在dependencies{}内 进行 调整、添加 内容如下:
dependencies {
// androidTestImplementation 'com.android.support.test:runner:1.0.2'
// androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
androidTestImplementation 'androidx.test:runner:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
//添加代码:
implementation ('com.github.tbruyelle:rxpermissions:0.10.2'){
exclude group: 'com.android.support', module: 'support-compat'
}
}
4. 调整/main/AndroidManifest.xml文件内容
//<manifest 标签内添加版本号、版本构建号的声明(android:versionName 、android:versionCode )
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.XXX.XXX"
android:versionCode="1"
android:versionName="1.0.0"
>
//调整<application>标签中的内容
//1.将<application 内的android:name="io.flutter.app.FlutterApplication"去掉
<application
android:label="flutter_demo"
android:icon="@mipmap/ic_launcher">
//2.将<activity 内的android:name=".MainActivity" 和 android:launchMode="singleTop" 去掉,
//更改为 android:name="io.flutter.embedding.android.FlutterActivity"
<activity
android:name="io.flutter.embedding.android.FlutterActivity"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
</activity>
//3.添加获取apk安装包文件路径声明
//(注:@xml/file_paths为一个名为file_paths的xml文件,文件路径及内容后续会提到)
<provider android:authorities="${applicationId}.ota_update_provider"
android:name="sk.fourq.otaupdate.OtaUpdateFileProvider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths"/>
</provider>
//4.添加以下标签<meta-data android:name="flutterEmbedding" android:value="2" />
<meta-data android:name="flutterEmbedding" android:value="2" />
</application>
5. 将项目中所有引入FlutterActivity的地方,做检查并替换(如下)
import io.flutter.app.FlutterActivity---->
import io.flutter.embedding.android.FlutterActivity
例如:MainActivity.java、DemoApplication.java、MainActivity.kt
6. 将项目中所有重写onCreate方法的代码注释掉,因为该版本的Flutter已做处理
例如:MainActivity.java、MainActivity.kt
7. 将项目中所有以PluginRegistry 为参数的registerWith(PluginRegistry registry)方法注释掉,因为该方法的参数在该版本的Flutter中参数已变为@NonNull FlutterEngine flutterEngine
例如:DemoApplication.java
8. 将项目中继承FlutterApplication并实现PluginRegistry.PluginRegistrantCallback的代码段注释掉
例如:DemoApplication.java
9. 新建xml文件,名为file_paths (注:该文件名称需要与AndroidManifest.xml里的第三点标签内的android:resource保持一致),内容如下:
<?xml version="1.0" encoding="utf-8"?>
<paths>
//${applicationId} 指的是该项目 android/app/build.gradle文件内的 android{}下的defaultConfig{}下的
//applicationId属性值
<external-path path="Android/data/${applicationId}/" name="files_root" />
<external-path path="." name="external_storage_root" />
</paths>
10.在项目的main.dart文件第一行添加代码: WidgetsFlutterBinding.ensureInitialized();
void main(){
WidgetsFlutterBinding.ensureInitialized();
int empId = null;
sharedUtil.sharedGetData("empId").then((Object data) {
empId = data;
runApp(MyApp(empId));
});
}
配置命令打包文件
因该版本的Flutter程序通过idea编译出来的apk文件无法安装到真机上(提示:安装包无效或版本不兼容),所以需要通过命令进行打包生成apk文件 秘钥文件生成教学
1.找到java安装路径,进入到bin目录下,cmd+enter进入dos命令窗口,输入以下内容:
keytool -genkey -v -keystore D:\my-release-key.keystore -alias my-key-alias -keyalg RSA -keysize 2048 -validity 10000
如图所示:
根据命令执行操作(其中信息可忽略不填,点击回车即可,最终输入Y,生成秘钥),其中 D:\my-release-key.keystore 文件为 最终生成的秘钥文件
2. 关闭当前dos命令窗口,退出到当前bin目录,在java目录下,cmd+enter进入dos命令窗口,输入以下内容
.\keytool -genkey -v -keystore D:\my-release-key.keystore -alias my-key-alias -keyalg RSA -keysize 2048 -validity 10000
3.进入D盘,找到my-release-key.keystore秘钥文件,将该文件复制到项目的/android/app下
4. 在项目的android目录下,新建 key.properties 文件,文件内容如下:
(注:storePassword、keyPassword等号右侧为秘钥文件生成时输入的密码;keyAlias等号右侧对应的是上面第2点写到的命令中 -alias 后, -keyalg前的内容,storeFile即为刚刚添加到该项目下的秘钥文件名称)
storePassword=your password
keyPassword=your password
keyAlias=my-key-alias
storeFile=my-release-key.keystore
编写app代码
1.选择适合的页面,在初始化中进行校验当前版本是否需要升级
@override
void initState() {
//...
//校验是否需要升级
getAppUpgrade(context);
}
//校验是否需要升级
Future getAppUpgrade(BuildContext context) async {
final remoteDataSource = RemoteDataSource();
final response = await remoteDataSource.getAppUpgrade();
if(response.isShowDialog){//提示升级
showDialog(context: context,
barrierDismissible: response.upgradeType != "0",//为true时,无法通过点击空白处关闭升级提示框
builder: (_) =>
UpdateDialog(
title: response.appName,
content: response.upgradeDesc,
apkUrl: response.downloadUrl,
version: response.appVersion,
)
);
}
}
2.创建RemoteDataSourc.dart文件,调取后台处理代码文件
import 'dart:convert';
import 'package:http/http.dart';
import 'package:package_info/package_info.dart';
import 'package:xxxxxx/model/app_upgrade_model.dart';
import 'package:xxxxxx/util/http/HttpUtil.dart';
class RemoteDataSource {
Future<AppUpgradeModel> getAppUpgrade() async {
PackageInfo packageInfo = await PackageInfo.fromPlatform();
String version = packageInfo.version;//版本号
String buildNumber = packageInfo.buildNumber;//版本构建号
Map<String,dynamic> response = await HttpUtil().get("/getVersion",data:{"version":version,"buildNumber":buildNumber});
if (response["statusCode"] == "200") {
final model = AppUpgradeModel.fromJson(response);
return model;
} else {
throw ServerException();
}
}
}
class ServerException implements Exception {}
3.创建封装类
class AppUpgradeModel {
final String appName;
final String appVersion;
final String upgradeDesc;
final String upgradeType;
final String downloadUrl;
final bool isShowDialog;
AppUpgradeModel({this.appName, this.appVersion, this.upgradeDesc, this.upgradeType, this.downloadUrl,this.isShowDialog});
factory AppUpgradeModel.fromJson(Map<String, dynamic> json) {
return AppUpgradeModel(
appName: json['appName'],
appVersion: json['appVersion'],
upgradeDesc: json['upgradeDesc'],
upgradeType: json['upgradeType'],
downloadUrl: json['downloadUrl'],
isShowDialog: json['isShowDialog'],
);
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['appName'] = this.appName;
data['appVersion'] = this.appVersion;
data['upgradeDesc'] = this.upgradeDesc;
data['upgradeType'] = this.upgradeType;
data['downloadUrl'] = this.downloadUrl;
data['isShowDialog'] = this.isShowDialog;
return data;
}
}
4.创建UpdateDialog.dart 升级提示框页面
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:install_plugin/install_plugin.dart';
import 'package:ota_update/ota_update.dart';
import 'package:path_provider/path_provider.dart';
import 'package:permission_handler/permission_handler.dart';
class UpdateDialog extends StatefulWidget {
final String title;
final String content;
final String apkUrl;
final String version;
const UpdateDialog(
{Key key, this.title, this.content, this.apkUrl, this.version})
: super(key: key);
@override
_UpdateDialogState createState() => _UpdateDialogState();
}
class _UpdateDialogState extends State<UpdateDialog> {
String _hint = "立即更新";
@override
Widget build(BuildContext context) {
return Center(
child: Dialog(
elevation: 0,
backgroundColor: Colors.transparent,
child: Container(
height: 340,
child: Stack(
alignment: Alignment.bottomCenter,
children: <Widget>[
Container(
width: 300,
height: 310,
padding: const EdgeInsets.only(
left: 20.0, top: 36.0, bottom: 20.0, right: 20.0),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(10),
bottomLeft: Radius.circular(10),
topRight: Radius.circular(10),
bottomRight: Radius.circular(10))),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
widget.title,
style: TextStyle(
fontSize: 18.0, fontWeight: FontWeight.bold),
),
Text(widget.version),
SizedBox(height: 12.0),
Expanded(
child: Text(
widget.content,
style: TextStyle(
fontSize: 14.0, color: Colors.grey[600], height: 2),
),
),
Center(
child: Material(
borderRadius: BorderRadius.circular(23),
color: Colors.indigoAccent,
elevation: 6,
shadowColor: Colors.grey.withOpacity(0.6),
child: Ink(
child: InkWell(
onTap: () async {
_upgrade();
},
borderRadius: BorderRadius.circular(23),
child: Container(
width: 120,
height: 46,
child: Center(
child: Text(
_hint,
style: TextStyle(
color: Colors.white,
fontSize: 16.0,
fontWeight: FontWeight.w500),
),
),
),
),
),
),
)
]),
),
// Positioned(
// top: 0,
// right: 0,
// child: Image.asset(
// "lib/images/upgrade.png",
// width: 100,
// height: 100,
// ),
// )
],
),
),
));
}
//立即更新
_upgrade() async {
var per = await _checkPermission(context);
if (!per) {
return;
}
//获取app安装路径
Directory appDocDir = await getApplicationDocumentsDirectory();
String appDocPath = appDocDir.path;
try {
OtaUpdate().execute(widget.apkUrl).listen((OtaEvent event){
switch(event.status) {
case OtaStatus.DOWNLOADING:
setState(() {
this._hint = "下载中 ${event.value}%";
});
break;
case OtaStatus.INSTALLING:
InstallPlugin.installApk(appDocPath, 'XXXXXX');//第二个参数例如:com.example.review_app_xt
break;
case OtaStatus.PERMISSION_NOT_GRANTED_ERROR: // 权限错误
print('更新失败,请稍后再试');
break;
default:
print(event.status);
break;
}
});
} catch (e) {
print('更新失败,请稍后再试'+e);
}
}
//校验权限
Future<bool> _checkPermission(BuildContext context) async {
Map<PermissionGroup, PermissionStatus> permissions =
await PermissionHandler().requestPermissions([PermissionGroup.storage]);
// 先对所在平台进行判断
if (Theme.of(context).platform == TargetPlatform.android) {
PermissionStatus permission = await PermissionHandler()
.checkPermissionStatus(PermissionGroup.storage);
if (permission != PermissionStatus.granted) {
Map<PermissionGroup, PermissionStatus> permissions =
await PermissionHandler()
.requestPermissions([PermissionGroup.storage]);
if (permissions[PermissionGroup.storage] == PermissionStatus.granted) {
return true;
}
} else {
return true;
}
} else {
return true;
}
return false;
}
}
安卓权限设置
程序安装不上自行查找权限设置的相关资料~~
更换apk版本
因为每次升级程序之后,会对应的调整新程序的版本号+版本构建号,是为了能够获取当前用户使用的程序版本…
1、pubspec.yaml文件中的version 值调整
version: 1.0.0+1
其中1.0.0为软件版本
1为版本构建号
2、AndroidManifest.xml文件中的android:versionCode、android:versionName值调整
3、local.properties文件中的flutter.versionName、flutter.versionCode值调整
其他问题
1、其中根据报错提示调整了webview_flutter-0.2.0包下的android/src.main/build.gradle文件里的compileSdkVersion为28(原本为27,导致程序无法启动)
2、安卓高版本需处理问题
详见:高版本明文处理方式