Flutter1.9.1+hotfix.2升级到Flutter1.12.13+hotfix.8指南(含自动升级功能)

Flutter1.9.1+hotfix.2升级到Flutter1.12.13+hotfix.8指南

升级目的

1、使用package_info包内方法获取flutter程序版本和版本构建号;
2、使用permission_handler包内方法获取手机权限;
3、使用ota_update包内方法进行软件下载、进度加载、下载状态区分;
4、使用path_provider包内方法获取app安装路径;
5、使用install_plugin包内方法进行软件安装;

Flutter版本下载

  1. 登录网站自行下载
  2. 配置当前Flutter项目的FlutterSDK:File–>Settings–>Languages & Frameworks–>Flutter->SDK面板:Flutter SDK path 路径调整(指向下载后的flutter包即可);
  3. 配置当前Flutter项目的DartSDK:File–>Settings–>Languages & Frameworks–>Dart主面板:Dart SDK path路径调整(指向下载后的flutter包下的bin\cache\dart-sdk包);
  4. 找到项目下local.properties文件,更改flutter.sdk为下载后的flutter包;

引入自动升级功能所需资源

  1. permission_handler: ^4.0.0
  2. path_provider: ^1.5.1
  3. ota_update: ^2.0.2
  4. install_plugin: ^2.0.0
  5. package_info: ^0.4.0+3;
  6. 运行 Package get命令(下载资源)
  7. 若因为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

如图所示:
根据命令执行操作,其中 D:\my-release-key.keystore 文件为 最终生成的秘钥文件
根据命令执行操作(其中信息可忽略不填,点击回车即可,最终输入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、安卓高版本需处理问题

详见:高版本明文处理方式

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值