flutter app內更新升級

用flutter開發了一個簡單的跨平臺app,在網上找了很多 app內升級的博客,大部分都是複製的,講的不全,不過有一篇好文章推薦給大家,重點是引用插件的版本儘量和博客中的一致,否則編譯時各種報錯,Flutter 項目 app迭代更新,我開始引用各個插件最新版本,結果一直編譯失敗

下面將分詳細說說,爲那些初學者提供思路,並把demo分享出來

1.有兩種方法實現app升級
  • 1.直接使用url_launcher插件跳轉本地瀏覽器下載,版本號沒有要求
  • 2.使用多個flutter插件,一起達到下載升級效果
2.大概思路
  • 1.進入頁面,請求服務端接口,對比服務端的版本與當前手機版本是否一致
  • 2.不一致則提示用戶可以升級
  • 3.如果是android:則下載apk文件,存入手機某個位置,完成後打開該apk進行安裝
  • 4.如果是ios:則通過url_launcher插件,打開 app_store中你們應用的鏈接地址,用戶自己選擇是否升級

下面看看手機上的效果
在這裏插入圖片描述 在這裏插入圖片描述

  • 使用 url_launcher插件方式,會提示使用本地瀏覽器下載
    在這裏插入圖片描述 在這裏插入圖片描述
    最後都能進入安裝界面
    在這裏插入圖片描述
3.代碼實現
  • 1.引用插件,千萬要注意版本,我最開始使用的最新版,結果一直編譯失敗
dependencies:
  flutter:
    sdk: flutter
  dio: ^3.0.8
  package_info: ^0.4.0+13
  permission_handler: 3.2.0
  flutter_downloader: 1.1.7
  path_provider: 1.5.1
  open_file: ^1.3.0
  url_launcher: 5.1.2
  progress_dialog: 1.2.0
  • 2.在android/app/src/main/AndroidManifest.xml中添加配置
   <uses-permission android:name="android.permission.INTERNET"/>
   <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
   <uses-permission android:name="android.permission.RECORD_AUDIO" />
   <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
   <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

   <application .....>
      .....其他已有內容省略了
	   <provider
		    android:name="vn.hunghd.flutterdownloader.DownloadedFileProvider"
		    android:authorities="${applicationId}.flutter_downloader.provider"
		    android:exported="false"
		    android:grantUriPermissions="true">
		    <meta-data
		        android:name="android.support.FILE_PROVIDER_PATHS"
		        android:resource="@xml/provider_paths"/>
		</provider>
		
		<provider
		    android:name="androidx.work.impl.WorkManagerInitializer"
		    android:authorities="${applicationId}.workmanager-init"
		    android:enabled="false"
		    android:exported="false" />
		
		<provider
		    android:name="vn.hunghd.flutterdownloader.FlutterDownloaderInitializer"
		    android:authorities="${applicationId}.flutter-downloader-init"
		    android:exported="false">
		    <meta-data
		        android:name="vn.hunghd.flutterdownloader.MAX_CONCURRENT_TASKS"
		        android:value="5" />
		</provider>
   </application>
  • 3.服務端返回的數據集如下:
{
    "android_version" : "1.0.2",
    "android_msg" : "對系統進行了優化,修復了多個bug",
    "android_url" : "http://www.xxx.com/flutterApp.apk"
}
  • 4.代碼實現,代碼比較長,都很簡單且有註釋,請細心閱讀一下
import 'dart:convert';
import 'dart:io';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_downloader/flutter_downloader.dart';
import 'package:open_file/open_file.dart';
import 'package:package_info/package_info.dart';
import 'package:path_provider/path_provider.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:permission_handler/permission_handler.dart';
import 'Http.dart';
import 'package:progress_dialog/progress_dialog.dart';

//定義apk的名稱,與下載進度dialog
String apkName ='flutterApp.apk';
ProgressDialog pr;

class Upgrade extends StatefulWidget {
  Upgrade({Key key}) : super(key: key);

  @override
  _UpgradeState createState() => _UpgradeState();
}

class _UpgradeState extends State<Upgrade> {

  @override
  void initState() {
    super.initState();
    //檢查是否有更新
    checkUpdate(context);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('app upgrade'),
      ),
      body: Container(
        alignment: Alignment.center,
        child: Text('Checking...' , style: TextStyle(
          fontSize: 16,
        ),),
      ),
    );
  }

  ///檢查是否有更新
Future<void> checkUpdate(BuildContext context) async{
  //Android , 需要下載apk包
  if(Platform.isAndroid){
    print('is android');
      PackageInfo packageInfo = await PackageInfo.fromPlatform();
      String localVersion = packageInfo.version;
      //此處使用了dio,封裝了httpGet方法,獲取服務端中最新的app版本信息
      String versionInfo = await httpGet('/appversion.json');
      if(versionInfo != ""){
        Map<String, dynamic> map = json.decode(versionInfo);
        String serverAndroidVersion = map['android_version'].toString();
        String serverMsg = map['android_msg'].toString();
        String url = map['android_url'].toString();
        print(url);
        print('本地版本: ' + localVersion + ',最新版本: ' + serverAndroidVersion );
        int c = serverAndroidVersion.compareTo(localVersion);
        //如果服務端版本大於本地版本則提示更新
        if(c == 1){
          showUpdate(context, serverAndroidVersion, serverMsg, url);
        }
      }
  }
  
  //Ios , 只能跳轉到 AppStore , 直接採用url_launcher就可以了
  //android也可以採用此方法,會跳轉到手機瀏覽器中下載
  if(Platform.isIOS){
    print('is ios');
    final url = "https://itunes.apple.com/cn/app/id1380512641"; // id 後面的數字換成自己的應用 id 就行了
    if (await canLaunch(url)) {
      await launch(url, forceSafariVC: false);
    } else {
      throw 'Could not launch $url';
    }
  }
}

  ///2.顯示更新內容
  Future<void> showUpdate(BuildContext context , String version, String data, String url) async {
    return showDialog<void>(
      context: context,
      barrierDismissible: true,
      builder: (BuildContext context) {
        return CupertinoAlertDialog(
          title: Text('檢測到新版本 v$version'),
          content : Text('是否要更新到最新版本?') ,
          actions: <Widget>[
            FlatButton(
              child: Text('下次在說'),
              onPressed: () {
                Navigator.of(context).pop();
              },
            ),
            FlatButton(
              child: Text('立即更新'),
              onPressed: ()=>doUpdate(context,version,url)
              ,
            ),
          ],
        );
      },
    );
  }

  ///3.執行更新操作
  doUpdate(BuildContext context , String version,String url) async {
    //關閉更新內容提示框
    Navigator.pop(context);
    //獲取權限
    var per = await checkPermission();
    if(per != null && !per){
      return null;
    }
    //開始下載apk
    executeDownload(context , url);
  }

  ///4.檢查是否有權限
  Future<bool> checkPermission() async {
    //檢查是否已有讀寫內存權限
    PermissionStatus status = await PermissionHandler().checkPermissionStatus(PermissionGroup.storage);
    print(status);
    
    //判斷如果還沒擁有讀寫權限就申請獲取權限
    if(status != PermissionStatus.granted){
      var map = await PermissionHandler().requestPermissions([PermissionGroup.storage]);
      if(map[PermissionGroup.storage] != PermissionStatus.granted){
        return false;
      }
    }
    return true;
  }

  ///5.下載apk
  Future<void> executeDownload(BuildContext context ,String url) async {
    //下載時顯示下載進度dialog
    pr = new ProgressDialog(context,type: ProgressDialogType.Download, isDismissible: true, showLogs: true);
    if (!pr.isShowing()) {
      pr.show();
    }
    //apk存放路徑
    final path = await _apkLocalPath;
    File file = File(path + '/' + apkName);
    if (await file.exists()) await file.delete();
    
    //下載
    final taskId = await FlutterDownloader.enqueue(
        url: url,//下載最新apk的網絡地址
        savedDir: path,
        fileName: apkName,
        showNotification: true,
        openFileFromNotification: true);
        
    FlutterDownloader.registerCallback((id, status, progress) {
      if (status == DownloadTaskStatus.running) {
        pr.update(progress: progress.toDouble(), message: "下載中,請稍後…");
      }
      if (status == DownloadTaskStatus.failed) {
        if (pr.isShowing()) {
          pr.hide();
        }
      }
      if (taskId == id && status == DownloadTaskStatus.complete) {
        if (pr.isShowing()) {
          pr.hide();
        }
        _installApk();
      }
    });
  }

  //6.安裝app
  Future<Null> _installApk() async {
    String path = await _apkLocalPath;
    await OpenFile.open(path + '/' + apkName);
  }
  // 獲取apk存放地址(外部路徑)
  Future<String> get _apkLocalPath async {
    final directory = await getExternalStorageDirectory();
    return directory.path;
  }
}

謝謝

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章