• flutter开发实战-应用更新apk下载、安装apk、启动应用实现


    flutter开发实战-应用更新apk下载、安装apk、启动应用实现

    在开发过程中,经常遇到需要更新下载新版本的apk文件,之后进行应用更新apk下载、安装apk、启动应用。我们在flutter工程中实现下载apk,判断当前版本与需要更新安装的版本进行比对判断,通过判断VersionCode来确定下载新版版APK

    一、应用更新apk下载

    当应用需要更新的时候,我们需要判断版本号,在flutter工程中versionCode是工程中的pubspec.yaml中的version确定的。

    如version: 1.0.0+1

    version为1.0.0,versionCode为1

    需要我们获取接口,需要判断的就是versionCode确定是否需要下载apk。

    1.1、获取新版本地址接口

    获取新版本的接口使用的是Dio库。dio 是一个强大的 Dart HTTP 请求库,支持全局配置、Restful API、FormData、拦截器、 请求取消、Cookie 管理、文件上传/下载、超时以及自定义适配器等。

    这里的请求为GET请求,

    Response? response = await dio.get(requestUrl,
                      queryParameters: params,
                      options: Options(contentType: Headers.jsonContentType));
    
    • 1
    • 2
    • 3

    我这里就不写请求的逻辑了。
    根据请求,获取到了

    // 获取检查版本

    Future<void> checkVersion() async {
        var params = {};
    
        ApiRepository.checkVersion(
            params: params,
            success: (response) {
              // {"version":"2","url":"http://wwww.laileshuo.com/download/myapp_v1.0.0_release.apk"}
              var object = response.object;
              if (object != null && (object is Map) && object.isNotEmpty) {
                String? versionCode = object['versionCode'];
                String? url = object['url'];
                // 判断是否需要下载更新
                String versionCodeStr = "";
                if (version != null) {
                  versionCodeStr = "${versionCode}";
                }
                checkAppVersionUpdate(versionCodeStr: versionCodeStr, apkUrl: url);
              }
              print(
                  "checkVersion params:${params}, object:${object.toString()}");
            },
            failure: (error) {
              print(
                  "checkVersion params:${params}, error:${error.toString()}");
            });
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    通过检查新版本接口获取到了url及versionCode,这里的versionCode和pubspec.yaml的进行比较看是否需要下载apk。

    判断下载apk

    Future<void> checkAppVersionUpdate({String? versionCodeStr, String? apkUrl}) async {
        try {
          if (versionCodeStr != null &&
              apkUrl != null &&
              versionCodeStr.isNotEmpty &&
              apkUrl.isNotEmpty) {
            String curVersionCodeStr = await PlatformUtils.getBuildNum();
            int versionCode = int.parse(versionCodeStr);
            int curVersionCode = int.parse(curVersionCodeStr);
            if (versionCode > curVersionCode) {
              // 需要更新的版本code,大于当前的版本才更新
              
            }
          }
        } catch (e) {
          print(
              "appVersionUpdate apkUrl:${apkUrl}, version:${version}, exception:${e.toString()}");
        }
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    1.2、下载Apk

    在判断需要更新的时候,我们需要下载新版本的apk。下载的库我们使用的也是Dio。

    下载的代码可参考https://blog.csdn.net/gloryFlow/article/details/131658621

    当获取到新版的下载地址url时候,需要下载apk

    void downApk(String url, String saveDestPath) {
    	HttpApi().doDownload(url, saveDestPath, cancelToken: CancelToken(),
            progress: (int received, int total) {
          // 下载进度
          setState(() {
            _downloadRatio = (received / total);
            if (_downloadRatio == 1) {
              _downloading = false;
            }
            _downloadIndicator = (_downloadRatio * 100).toStringAsFixed(2) + '%';
          });
        }, completion: () {
          // 下载成功
          FlutterLoadingHud.showToast(message: "\"下载完成\"");
        }, failure: (error) {
          // 下载出错
          FlutterLoadingHud.showToast(message: error.message);
        });
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    下载完成后可以执行安装并且启动操作了。

    二、APK安装及启动

    APK安装及启动需要原生插件来实现。

    2.1、创建原生插件flutter_package_manager

    创建flutter plugin,我使用的工具是Android studio。

    配置如下内容:

    • Project name
    • Project location
    • Description
    • Project type: Plugin
    • Android language
    • iOS language
    • Platforms

    如图所示

    在这里插入图片描述

    我们需要实现installThenStart

    /// An implementation of [FlutterPackageManagerPlatform] that uses method channels.
    class MethodChannelFlutterPackageManager extends FlutterPackageManagerPlatform {
      /// The method channel used to interact with the native platform.
      
      final methodChannel = const MethodChannel('flutter_package_manager');
    
      
      Future<String?> getPlatformVersion() async {
        final version = await methodChannel.invokeMethod<String>('getPlatformVersion');
        return version;
      }
    
      
      Future<void> installThenStart(String apkFilePath, String activity) async {
        final result = await methodChannel.invokeMethod<void>('installThenStart');
        return result;
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    可以看到定义了installThenStart,需要apkFilePath与activity作为参数。

    在Android端实现,由于我这边需要静默安装(apk在后台安装,不出现安装界面的提示)

    public class FlutterPackageManager implements MethodCallHandler {
        private static final String TAG = "FlutterPackageManager";
    
        private final Registrar registrar;
    
        /**
         * Plugin registration.
         */
        public static void registerWith(Registrar registrar) {
            final MethodChannel channel = new MethodChannel(registrar.messenger(), "flutter_package_manager");
            channel.setMethodCallHandler(new FlutterPackageManager(registrar));
        }
    
        private FlutterPackageManager(Registrar registrar) {
            this.registrar = registrar;
        }
    
        
        public void onMethodCall(MethodCall call, Result result) {
            if (call.method.equals("getPlatformVersion")) {
                result.success(android.os.Build.VERSION.RELEASE);
            } else if (call.method.equals("installThenStart")) {
                String path = call.arguments['filePath'];
    	    String activity = call.arguments['activity'];
    	    installApk(path, activity);
            } else {
                result.notImplemented();
            }
        }
    
        void installApk(String path, String activity) {
        	// root权限静默安装实现 实现实际使用的是su pm install -r filePath命令。
    	Process process = null; 
       	OutputStream out = null; 
       	InputStream in = null; 
       	try { 
       		// 请求root 
       		process = Runtime.getRuntime().exec("su"); 
       		out = process.getOutputStream(); 
       		// 调用安装 
       		out.write(("pm install -r " + path + "\n").getBytes()); 
       		in = process.getInputStream(); 
       		int len = 0; 
       		byte[] bs = new byte[256]; 
       		while (-1 != (len = in.read(bs))) { 
       		String state = new String(bs, 0, len); 
       		if (state.equals("Success\n")) { 
        			//安装成功后的操作 
    			startActivity(activity);
         		} 
        	   } 
       	} catch (IOException e) { 
        		e.printStackTrace(); 
       	} catch (Exception e) { 
        		e.printStackTrace(); 
       	} finally { 
        		try { 
         			if (out != null) { 
          				out.flush(); 
          				out.close(); 
         			} 
         			if (in != null) { 
          				in.close(); 
         			} 
        		} catch (IOException e) { 
         			e.printStackTrace(); 
        		} 
       	} 
     
        }
    
        void startActivity(String activity) {
            // activity格式为'com.laileshuo.app/com.laileshuo.app.MainActivity'
        	Intent mIntent = new Intent(); 
    	val componentName = ComponentName(this, activity)
    	val intent = Intent().setComponent(componentName)
    	startActivity(intent)
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79

    当然,工程中的AndroidManifest.xml也需要做相应的调整,如下

    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        package="com.laileshuo.app">
       <application
            tools:replace="android:label"
            android:label="我的应用"
            android:name="${applicationName}"
            android:icon="@mipmap/ic_launcher">
            <activity
                android:name="com.laileshuo.app.MainActivity"
                android:exported="true"
                android:launchMode="singleTop"
                android:theme="@style/LaunchTheme"
                android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
                android:hardwareAccelerated="true"
                android:windowSoftInputMode="adjustResize">
                <!-- Specifies an Android theme to apply to this Activity as soon as
                     the Android process has started. This theme is visible to the user
                     while the Flutter UI initializes. After that, this theme continues
                     to determine the Window background behind the Flutter UI. -->
                <meta-data
                  android:name="io.flutter.embedding.android.NormalTheme"
                  android:resource="@style/NormalTheme"
                  />
                <intent-filter>
                    <action android:name="android.intent.action.MAIN"/>
                    <category android:name="android.intent.category.LAUNCHER"/>
                </intent-filter>
            </activity>
            <!-- Don't delete the meta-data below.
                 This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
            <meta-data
                android:name="flutterEmbedding"
                android:value="2" />
        </application>
    </manifest>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    2.2、如果非root环境安装,可以使用open_file插件

    需要在pubspec.yaml引入插件

    dependencies:
      open_file: ^3.3.2
    
    • 1
    • 2

    在可以直接使用代码安装apk

    import 'package:open_file/open_file.dart';
    
    OpenFile.open(apkFilePath);
    
    • 1
    • 2
    • 3

    当与关于FileProvider的其他插件发生冲突时,需要配置AndroidManifest.xml

    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
              xmlns:tools="http://schemas.android.com/tools"
              package="xxx.xxx.xxxxx">
        <application>
            ...
            <provider
                    android:name="androidx.core.content.FileProvider"
                    android:authorities="${applicationId}.fileProvider"
                    android:exported="false"
                    android:grantUriPermissions="true"
                    tools:replace="android:authorities">
                <meta-data
                        android:name="android.support.FILE_PROVIDER_PATHS"
                        android:resource="@xml/filepaths"
                        tools:replace="android:resource" />
            </provider>
        </application>
    </manifest>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    三、小结

    flutter开发实战-应用更新apk下载、安装apk、启动应用实现。在开发过程中,经常遇到需要更新下载新版本的apk文件,之后进行应用更新apk下载、安装apk、启动应用。我们在flutter工程中实现下载apk,判断当前版本与需要更新安装的版本进行比对判断,通过判断VersionCode来确定下载新版版APK。内容较多,描述可能不准确,请见谅。

    https://blog.csdn.net/gloryFlow/article/details/133440529

    学习记录,每天不停进步。

  • 相关阅读:
    使用 OpenGL 渲染会旋转 & 会变色的三角形(LearnOpenGL P3)
    算法竞赛入门【码蹄集新手村600题】(MT1351-1400)
    Linux——指令初识(二)
    ESP8266-Arduino编程实例-TSL2591数光转换器驱动
    APK反编译工具汇总
    vs2019添加库文件
    go开发之个微机器人的二次开发
    分布式事务两阶段提交和三阶段提交有什么区别?
    HarmonyOS-使用router事件跳转到指定UIAbility
    Unity Shader - if 和 keyword 的指令比较
  • 原文地址:https://blog.csdn.net/gloryFlow/article/details/133440529