B站視訊
www.bilibili.com/video/BV1Gi… www.bilibili.com/video/BV17t…
本節目標
- app 升級策略
- android 動態授權
- android 裝置目錄
- ios 支援 swift 語言
- 快速提示框
正文
ios 支援 swift 語言
- 出發點
社群第三方包都在用 swift 開發,打包的時候需要加入 swift 語言包。
- 操作
建立一個支援 swift 的新專案,然後把 lib assets pubspec.yaml 覆蓋即可。
app 升級策略
程式碼實現
定義介面
- post /app/update
加入依賴包
- pubspec.yaml
dependencies:
# 裝置資訊
device_info: ^0.4.2+3
# 包資訊
package_info: ^0.4.0+18
# 路徑查詢
path_provider: ^1.6.8
# permission 許可權
permission_handler: ^5.0.0+hotfix.6
# 安裝
install_plugin: ^2.0.1
# 對話方塊
easy_dialog: ^1.0.5
複製程式碼
升級工具類
- lib/common/utils/update.dart
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:easy_dialog/easy_dialog.dart';
import 'package:flutter/material.dart';
import 'package:flutter_ducafecat_news/common/apis/app.dart';
import 'package:flutter_ducafecat_news/common/entitys/entitys.dart';
import 'package:flutter_ducafecat_news/common/widgets/toast.dart';
import 'package:flutter_ducafecat_news/global.dart';
import 'package:install_plugin/install_plugin.dart';
import 'package:path_provider/path_provider.dart';
/// app 升級
class AppUpdateUtil {
static AppUpdateUtil _instance = AppUpdateUtil._internal();
factory AppUpdateUtil() => _instance;
BuildContext _context;
AppUpdateResponseEntity _appUpdateInfo;
AppUpdateUtil._internal();
/// 獲取更新資訊
Future run(BuildContext context) async {
_context = context;
// 提交 裝置型別、發行渠道、架構、機型
AppUpdateRequestEntity requestDeviceInfo = AppUpdateRequestEntity(
device: Global.isIOS == true ? "ios" : "android",
channel: Global.channel,
architecture: Global.isIOS == true
? Global.iosDeviceInfo.utsname.machine
: Global.androidDeviceInfo.device,
model: Global.isIOS == true
? Global.iosDeviceInfo.name
: Global.androidDeviceInfo.brand,
);
_appUpdateInfo =
await AppApi.update(context: context, params: requestDeviceInfo);
_runAppUpdate();
}
/// 檢查是否有新版
Future _runAppUpdate() async {
// 比較版本
final isNewVersion =
(_appUpdateInfo.latestVersion.compareTo(Global.packageInfo.version) ==
1);
// 安裝
if (isNewVersion == true) {
_appUpdateConformDialog(() {
Navigator.of(_context).pop();
if (Global.isIOS == true) {
// 去蘋果店
InstallPlugin.gotoAppStore(_appUpdateInfo.shopUrl);
} else {
// apk 下載安裝
toastInfo(msg: "開始下載升級包");
_downloadAPKAndSetup(_appUpdateInfo.fileUrl);
}
});
}
}
/// 下載檔案 & 安裝
Future _downloadAPKAndSetup(String fileUrl) async {
// 下載
Directory externalDir = await getExternalStorageDirectory();
String fullPath = externalDir.path + "/release.apk";
Dio dio = Dio(BaseOptions(
responseType: ResponseType.bytes,
followRedirects: false,
validateStatus: (status) {
return status < 500;
}));
Response response = await dio.get(
fileUrl,
);
File file = File(fullPath);
var raf = file.openSync(mode: FileMode.write);
raf.writeFromSync(response.data);
await raf.close();
// 安裝
await InstallPlugin.installApk(fullPath, Global.packageInfo.packageName);
}
/// 升級確認對話方塊
void _appUpdateConformDialog(VoidCallback onPressed) {
EasyDialog(
title: Text(
"發現新版本 ${_appUpdateInfo.latestVersion}",
style: TextStyle(fontWeight: FontWeight.bold),
textScaleFactor: 1.2,
),
description: Text(
_appUpdateInfo.latestDescription,
textScaleFactor: 1.1,
textAlign: TextAlign.center,
),
height: 220,
contentList: [
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
new FlatButton(
padding: const EdgeInsets.only(top: 8.0),
textColor: Colors.lightBlue,
onPressed: onPressed,
child: new Text(
"同意",
textScaleFactor: 1.2,
),
),
new FlatButton(
padding: const EdgeInsets.only(top: 8.0),
textColor: Colors.lightBlue,
onPressed: () {
Navigator.of(_context).pop();
},
child: new Text(
"取消",
textScaleFactor: 1.2,
),
),
],
)
]).show(_context);
}
}
複製程式碼
讀取裝置資訊
- 外掛
pub.flutter-io.cn/packages/de…
- 全域性資訊
lib/global.dart
/// 是否 ios
static bool isIOS = Platform.isIOS;
/// android 裝置資訊
static AndroidDeviceInfo androidDeviceInfo;
/// ios 裝置資訊
static IosDeviceInfo iosDeviceInfo;
/// 包資訊
static PackageInfo packageInfo;
/// init
static Future init() async {
...
// 讀取裝置資訊
DeviceInfoPlugin deviceInfoPlugin = DeviceInfoPlugin();
if (Global.isIOS) {
Global.iosDeviceInfo = await deviceInfoPlugin.iosInfo;
} else {
Global.androidDeviceInfo = await deviceInfoPlugin.androidInfo;
}
// 包資訊
Global.packageInfo = await PackageInfo.fromPlatform();
...
複製程式碼
- 定義升級資訊 entity
lib/common/entitys/app.dart
class AppUpdateRequestEntity {
String device;
String channel;
String architecture;
String model;
AppUpdateRequestEntity({
this.device,
this.channel,
this.architecture,
this.model,
});
factory AppUpdateRequestEntity.fromJson(Map<String, dynamic> json) =>
AppUpdateRequestEntity(
device: json["device"],
channel: json["channel"],
architecture: json["architecture"],
model: json["model"],
);
Map<String, dynamic> toJson() => {
"device": device,
"channel": channel,
"architecture": architecture,
"model": model,
};
}
class AppUpdateResponseEntity {
String shopUrl;
String fileUrl;
String latestVersion;
String latestDescription;
AppUpdateResponseEntity({
this.shopUrl,
this.fileUrl,
this.latestVersion,
this.latestDescription,
});
factory AppUpdateResponseEntity.fromJson(Map<String, dynamic> json) =>
AppUpdateResponseEntity(
shopUrl: json["shopUrl"],
fileUrl: json["fileUrl"],
latestVersion: json["latestVersion"],
latestDescription: json["latestDescription"],
);
Map<String, dynamic> toJson() => {
"shopUrl": shopUrl,
"fileUrl": fileUrl,
"latestVersion": latestVersion,
"latestDescription": latestDescription,
};
}
複製程式碼
- api 請求
lib/common/apis/app.dart
/// 系統相關
class AppApi {
/// 獲取最新版本資訊
static Future<AppUpdateResponseEntity> update({
@required BuildContext context,
AppUpdateRequestEntity params,
}) async {
var response = await HttpUtil().post(
'/app/update',
context: context,
params: params,
);
return AppUpdateResponseEntity.fromJson(response);
}
}
複製程式碼
- 提交資訊 獲取版本
lib/common/utils/update.dart
/// 獲取更新資訊
Future run(BuildContext context) async {
_context = context;
// 提交 裝置型別、發行渠道、架構、機型
AppUpdateRequestEntity requestDeviceInfo = AppUpdateRequestEntity(
device: Global.isIOS == true ? "ios" : "android",
channel: Global.channel,
architecture: Global.isIOS == true
? Global.iosDeviceInfo.utsname.machine
: Global.androidDeviceInfo.device,
model: Global.isIOS == true
? Global.iosDeviceInfo.name
: Global.androidDeviceInfo.brand,
);
_appUpdateInfo =
await AppApi.update(context: context, params: requestDeviceInfo);
_runAppUpdate();
}
/// 檢查是否有新版
Future _runAppUpdate() async {
// 比較版本
final isNewVersion =
(_appUpdateInfo.latestVersion.compareTo(Global.packageInfo.version) ==
1);
// 安裝
if (isNewVersion == true) {
_appUpdateConformDialog(() {
Navigator.of(_context).pop();
if (Global.isIOS == true) {
// 去蘋果店
InstallPlugin.gotoAppStore(_appUpdateInfo.shopUrl);
} else {
// apk 下載安裝
toastInfo(msg: "開始下載升級包");
_downloadAPKAndSetup(_appUpdateInfo.fileUrl);
}
});
}
}
複製程式碼
android 動態授權
- 外掛
pub.flutter-io.cn/packages/pe…
- 官方文章
developer.android.com/training/pe…
developer.android.com/training/pe…
- AndroidManifest.xml 中加入許可權
android/app/src/main/AndroidManifest.xml
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
複製程式碼
- flutter 啟動頁中執行授權
lib/pages/index/index.dart
在 initState 是執行
延遲 3 秒,使用者體驗好些
class _IndexPageState extends State<IndexPage> {
@override
void initState() {
super.initState();
if (Global.isRelease == true) {
doAppUpdate();
}
}
Future doAppUpdate() async {
await Future.delayed(Duration(seconds: 3), () async {
if (Global.isIOS == false &&
await Permission.storage.isGranted == false) {
await [Permission.storage].request();
}
if (await Permission.storage.isGranted) {
AppUpdateUtil().run(context);
}
});
}
複製程式碼
android 目錄許可權
- 外掛
pub.flutter-io.cn/packages/pa… pub.flutter-io.cn/packages/in…
- 文章
developer.android.com/reference/a…
- lib/common/utils/update.dart
/// 下載檔案 & 安裝
Future _downloadAPKAndSetup(String fileUrl) async {
// 下載
Directory externalDir = await getExternalStorageDirectory();
String fullPath = externalDir.path + "/release.apk";
Dio dio = Dio(BaseOptions(
responseType: ResponseType.bytes,
followRedirects: false,
validateStatus: (status) {
return status < 500;
}));
Response response = await dio.get(
fileUrl,
);
File file = File(fullPath);
var raf = file.openSync(mode: FileMode.write);
raf.writeFromSync(response.data);
await raf.close();
// 安裝
await InstallPlugin.installApk(fullPath, Global.packageInfo.packageName);
}
複製程式碼
EasyDialog 快速提示框
- 外掛
pub.flutter-io.cn/packages/ea…
- lib/common/utils/update.dart
/// 升級確認對話方塊
void _appUpdateConformDialog(VoidCallback onPressed) {
EasyDialog(
title: Text(
"發現新版本 ${_appUpdateInfo.latestVersion}",
style: TextStyle(fontWeight: FontWeight.bold),
textScaleFactor: 1.2,
),
description: Text(
_appUpdateInfo.latestDescription,
textScaleFactor: 1.1,
textAlign: TextAlign.center,
),
height: 220,
contentList: [
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
new FlatButton(
padding: const EdgeInsets.only(top: 8.0),
textColor: Colors.lightBlue,
onPressed: onPressed,
child: new Text(
"同意",
textScaleFactor: 1.2,
),
),
new FlatButton(
padding: const EdgeInsets.only(top: 8.0),
textColor: Colors.lightBlue,
onPressed: () {
Navigator.of(_context).pop();
},
child: new Text(
"取消",
textScaleFactor: 1.2,
),
),
],
)
]).show(_context);
}
複製程式碼
資源
視訊
藍湖設計稿(加微信給授權 ducafecat)
lanhuapp.com/url/lYuz1 密碼: gSKl
藍湖現在收費了,所以檢視標記還請自己上傳 xd 設計稿 商業設計稿檔案不好直接分享, 可以加微信聯絡 ducafecat
YAPI 介面管理
程式碼
參考
- 文章
developer.android.com/training/pe… developer.android.com/training/pe… developer.android.com/reference/a…
- flutter 外掛
pub.flutter-io.cn/packages/de… pub.flutter-io.cn/packages/pa… pub.flutter-io.cn/packages/pe… pub.flutter-io.cn/packages/in… pub.flutter-io.cn/packages/ea…