APP检查更新基本上是所有APP必备的功能,除了应用市场的提示更新之外,我们还希望APP有应用内检查更新功能,方便第一时间通知用户。
Android的应用内检查更新可以使用第三方平台的检查更新服务,比如友盟和bugly,可以减轻自己服务器的压力;当然也可以把安装包放在自己的服务器上,然后通过接口检查更新;iOS跳转到App Store
下载更新,如果是企业版,需要通过plist文件下载更新包。
下面就说一下Flutter下,安装包放在自己服务器上,APP检查更新的具体实现。
【Android】
- APP通过接口检查更新
- 下载安装包
- 安装
【iOS】
- APP通过接口检查更新
- 打开
App Store
或者打开下载plist网页 - 用户手动下载安装
APP检查更新
首先,android更新包apk,iOS(企业版)更新包ipa和配置文件plist都要上传到我们自己的服务器中,并且提供检查更新的接口。
plist文件
:plist文件的下载地址必须是HTTPS
协议,文件内容如下,ipa下载地址
、包名
、版本号
、APP名称
需要改成自己的:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>items</key>
<array>
<dict>
<key>assets</key>
<array>
<dict>
<key>kind</key>
<string>software-package</string>
<key>url</key>
<string>自己的ipa下载地址</string>
</dict>
</array>
<key>metadata</key>
<dict>
<key>bundle-identifier</key>
<string>包名</string>
<key>bundle-version</key>
<string>版本号</string>
<key>kind</key>
<string>software</string>
<key>title</key>
<string>APP名称</string>
</dict>
</dict>
</array>
</dict>
</plist>
plist文件
的下载网页(itms-services:///?action=download-manifest&url=
是固定写法):
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Install</title>
</head>
<body>
<p align=center>
<font size="10">
<a style="color:#69DEDA" href="itms-services:///?action=download-manifest&url=https://自己的plist文件下载地址">点击安装</a>
</font>
</p>
</body>
</html>
接口的设计如下(可自行设计):
APP上传参数:当前版本号
和手机系统
:
/// 版本号
PackageInfo.fromPlatform().then((PackageInfo packageInfo) {
buildNumber = packageInfo.buildNumber;
});
/// 手机系统
if (Platform.isIOS) {
/// iOS
} else {
/// Android
}
接口返回参数:是否有新版本
,是否强制更新
,下载地址
,更新提示内容
:
class UpdateData {
String isUpload; ///是否有新版本
String url; ///下载地址
String remark; ///更新提示内容
String isForceUpdate; ///是否强制更新
}
下载地址
根据实际情况,返回apk下载地址
、App Store下载地址
、plist文件下载网页
其中一个。
强制更新非必须,一般的处理就是更新Dialog只有确定按钮,并且点击屏幕其他位置不可取消。
下载安装包
- Android直接下载apk,可以使用
Dio
的下载文件或者用第三方的下载文件库,我这里是直接用Dio
下载;
/// 下载文件
Future<bool> downloadFile({
@required String url,
@required String path,
@required onProgress(int count, int total),
@required onError(int code, String message)
}) async {
try {
// 必须加上 否则download 报 can not open file
File file = new File(path);
file.create(recursive: true);
print("--------- 开始下载 ---------");
await dio.download(url, path, onReceiveProgress: (int count, int total) {
//进度
print("$count $total");
onProgress(count, total);
});
print('--------- 下载成功 ---------');
return true;
} catch (e) {
onError(Constants.codeNetError, e.toString());
return false;
}
}
- 企业版iOS用
url_launcher
打开plist
文件的下载网页,用户手动点击下载;
_launchURL(String url) async {
if (await canLaunch(url)) {
await launch(url);
} else {
throw 'Could not launch $url';
}
}
- 非企业版iOS用
url_launcher
打开App Store
:
if (Platform.isIOS) {
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';
}
}
安装
这一步只有Android需要,下载成功后Flutter调用Android原生方法安装apk。
flutter代码(Channel名可自定义,不过要保持一致):
// 调用android代码安装apk
void _installApk(String path) {
const platform = MethodChannel("包名/update");
try {
// 调用app地址
platform.invokeMethod('install', {"bundleId": packageName, "path": path});
} on PlatformException catch (_) {}
}
Android MainActivity.java
:
public class MainActivity extends FlutterActivity {
//渠道名
private static final String CHANNEL = "包名/update";//渠道名
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
GeneratedPluginRegistrant.registerWith(this);
MethodChannel channel = new MethodChannel(getFlutterView(), CHANNEL);//获取渠道
channel.setMethodCallHandler(this::handleMethod);//设置方法监听
}
/**
* 处理方法回调监听
*
* @param methodCall 方法的参数相关
* @param result 方法的返回值相关
*/
private void handleMethod(MethodCall methodCall, MethodChannel.Result result) {
switch (methodCall.method) {//根据方法名进行处理
case "install":
String path = methodCall.argument("path");
String bundleId = methodCall.argument("bundleId");
installApk(path, bundleId);//具体处理
break;
default:
result.notImplemented();
}
}
/**
* 安装apk
*/
private void installApk(String filePath, String bundleId) {
File file = new File(filePath);
Uri fileUri = FileProvider.getUriForFile(MainActivity.this,
bundleId + ".my.fileprovider", file);
Intent it = new Intent();
it.setAction(Intent.ACTION_VIEW);
it.setDataAndType(fileUri, "application/vnd.android.package-archive");
it.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
it.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);// 防止打不开应用
MainActivity.this.startActivity(it);
}
}
AndroidManifest.xml
:
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<application
android:name=".MyApplication"
...>
<provider
android:name=".MyFileProvider"
android:authorities="${applicationId}.my.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_path"/>
</provider>
</application>
新增MyApplication.java
文件:
public class MyApplication extends FlutterApplication implements PluginRegistry.PluginRegistrantCallback {
@Override
public void registerWith(PluginRegistry registry) {
//
// Integration note:
//
// In Flutter, in order to work in background isolate, plugins need to register with
// a special instance of `FlutterEngine` that serves for background execution only.
// Hence, all (and only) plugins that require background execution feature need to
// call `registerWith` in this method.
//
// The default `GeneratedPluginRegistrant` will call `registerWith` of all plugins
// integrated in your application. Hence, if you are using `FlutterDownloaderPlugin`
// along with other plugins that need UI manipulation, you should register
// `FlutterDownloaderPlugin` and any 'background' plugins explicitly like this:
//
// FlutterDownloaderPlugin.registerWith(registry.registrarFor("vn.hunghd.flutterdownloader.FlutterDownloaderPlugin"));
//
GeneratedPluginRegistrant.registerWith(registry);
}
}
在res/xml
目录下新增file_path.xml
文件:
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<root-path
name="root"
path="" />
<files-path
name="files"
path="" />
<cache-path
name="cache"
path="" />
<external-path
name="external"
path="" />
<external-files-path
name="external_file_path"
path="" />
<external-cache-path
name="external_cache_path"
path="" />
</paths>
最后GitHub传送门:FlutterDemo
网友评论