This commit is contained in:
parent
eca489cee9
commit
00fc47c653
88
README.md
88
README.md
|
|
@ -275,14 +275,20 @@ if (opened) {
|
|||
- **多重降级**:确保在各种情况下都能成功跳转到相关设置页面
|
||||
|
||||
```dart
|
||||
// 使用新方法精确跳转到安装权限设置
|
||||
final success = await AppUpgradePlugin().openInstallPermissionSettings();
|
||||
if (success) {
|
||||
print('成功跳转到安装权限设置页面');
|
||||
// 方法1:使用系统流程安装(推荐,类似主流应用)
|
||||
final systemSuccess = await AppUpgradePlugin().installApkWithSystemFlow(apkPath);
|
||||
if (systemSuccess) {
|
||||
print('使用系统流程启动安装');
|
||||
} else {
|
||||
print('跳转失败,请检查设备兼容性');
|
||||
print('系统流程安装失败');
|
||||
}
|
||||
|
||||
// 方法2:传统安装方式(需要预先检查权限)
|
||||
final traditionalSuccess = await AppUpgradePlugin().installApk(apkPath);
|
||||
|
||||
// 如果需要手动管理权限,可以使用精确跳转
|
||||
final settingsSuccess = await AppUpgradePlugin().openInstallPermissionSettings();
|
||||
|
||||
// 获取设备信息用于调试
|
||||
final deviceInfo = await AppUpgradePlugin().getDeviceInfo();
|
||||
print('设备制造商: ${deviceInfo?['manufacturer']}');
|
||||
|
|
@ -300,6 +306,78 @@ print('Android版本: ${deviceInfo?['release']}');
|
|||
| 三星 | One UI标准方式 | 直接跳转到安装权限 |
|
||||
| 原生Android | 标准Intent | 直接跳转到安装权限 |
|
||||
|
||||
### 两种安装方式对比
|
||||
|
||||
| 方法 | 权限检查 | 用户体验 | 适用场景 |
|
||||
|------|---------|----------|----------|
|
||||
| `installApk()` | 预先检查权限,无权限时返回错误 | 需要开发者处理权限申请 | 需要完全控制安装流程 |
|
||||
| `installApkWithSystemFlow()` | 让系统处理权限检查 | 类似主流应用,直接弹出安装确认 | 推荐使用,体验更好 |
|
||||
|
||||
**`installApkWithSystemFlow()` 的优势**:
|
||||
- ✅ 类似盒马、美团等主流应用的安装体验
|
||||
- ✅ 系统自动处理权限检查和用户确认
|
||||
- ✅ 无需开发者手动管理"安装未知应用"权限
|
||||
- ✅ 弹出"XX正尝试安装应用"对话框,用户点击"继续"即可
|
||||
|
||||
```dart
|
||||
// 推荐的安装流程
|
||||
Future<void> installUpdate(String apkPath) async {
|
||||
final success = await AppUpgradePlugin().installApkWithSystemFlow(apkPath);
|
||||
if (success) {
|
||||
print('安装程序已启动,等待用户确认');
|
||||
} else {
|
||||
print('启动安装程序失败');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 动态配置安装策略
|
||||
|
||||
插件提供了灵活的配置系统,可以根据需要选择不同的安装策略:
|
||||
|
||||
```dart
|
||||
import 'package:app_upgrade_plugin/app_upgrade_plugin.dart';
|
||||
|
||||
// 方式1:使用预定义配置
|
||||
await AppUpgradePlugin().installApkWithConfig(apkPath, config: InstallConfig.systemFlow);
|
||||
await AppUpgradePlugin().installApkWithConfig(apkPath, config: InstallConfig.preCheckPermission);
|
||||
await AppUpgradePlugin().installApkWithConfig(apkPath, config: InstallConfig.smart);
|
||||
|
||||
// 方式2:自定义配置
|
||||
final customConfig = InstallConfig(
|
||||
strategy: InstallStrategy.smart,
|
||||
autoOpenSettings: true,
|
||||
showPermissionRationale: true,
|
||||
rationaleTitle: '需要安装权限',
|
||||
rationaleContent: '为了完成应用更新,需要允许安装未知来源的应用。',
|
||||
);
|
||||
await AppUpgradePlugin().installApkWithConfig(apkPath, config: customConfig);
|
||||
|
||||
// 方式3:根据用户设置动态选择
|
||||
InstallStrategy getUserPreferredStrategy() {
|
||||
// 从用户设置或配置文件读取
|
||||
final userPreference = getSettingsFromStorage();
|
||||
switch (userPreference) {
|
||||
case 'system': return InstallStrategy.systemFlow;
|
||||
case 'permission': return InstallStrategy.preCheckPermission;
|
||||
case 'smart': return InstallStrategy.smart;
|
||||
default: return InstallStrategy.systemFlow;
|
||||
}
|
||||
}
|
||||
|
||||
final strategy = getUserPreferredStrategy();
|
||||
final config = InstallConfig(strategy: strategy, autoOpenSettings: true);
|
||||
await AppUpgradePlugin().installApkWithConfig(apkPath, config: config);
|
||||
```
|
||||
|
||||
**三种策略对比**:
|
||||
|
||||
| 策略 | 行为 | 适用场景 |
|
||||
|------|------|----------|
|
||||
| `systemFlow` | 让系统处理所有权限检查 | 希望类似主流应用的体验 |
|
||||
| `preCheckPermission` | 预先检查权限,无权限时跳转设置 | 需要完全控制权限流程 |
|
||||
| `smart` | 有权限用预检查,无权限用系统流程 | 平衡控制和体验 |
|
||||
|
||||
## 🐛 故障排除
|
||||
|
||||
### 常见问题
|
||||
|
|
|
|||
|
|
@ -110,6 +110,14 @@ class AppUpgradePlugin: FlutterPlugin, MethodCallHandler, ActivityAware {
|
|||
"getDeviceInfo" -> {
|
||||
getDeviceInfo(result)
|
||||
}
|
||||
"installApkWithSystemFlow" -> {
|
||||
val filePath = call.argument<String>("filePath")
|
||||
if (filePath != null) {
|
||||
installApkWithSystemFlow(filePath, result)
|
||||
} else {
|
||||
result.error("INVALID_ARGUMENT", "File path is required", null)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
result.notImplemented()
|
||||
}
|
||||
|
|
@ -124,13 +132,8 @@ class AppUpgradePlugin: FlutterPlugin, MethodCallHandler, ActivityAware {
|
|||
return
|
||||
}
|
||||
|
||||
// Check if we have permission to install unknown apps (Android 8.0+)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
if (!context.packageManager.canRequestPackageInstalls()) {
|
||||
result.error("PERMISSION_DENIED", "No permission to install unknown apps. Please grant permission in settings.", null)
|
||||
return
|
||||
}
|
||||
}
|
||||
// Note: 不在这里检查权限,让系统处理安装流程
|
||||
// 如果没有权限,系统会自动弹出权限请求或安装确认对话框
|
||||
|
||||
val intent = Intent(Intent.ACTION_VIEW)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
|
|
@ -395,6 +398,48 @@ class AppUpgradePlugin: FlutterPlugin, MethodCallHandler, ActivityAware {
|
|||
result.success(deviceInfo)
|
||||
}
|
||||
|
||||
private fun installApkWithSystemFlow(filePath: String, result: Result) {
|
||||
try {
|
||||
val file = File(filePath)
|
||||
if (!file.exists()) {
|
||||
result.error("FILE_NOT_FOUND", "APK file not found at: $filePath", null)
|
||||
return
|
||||
}
|
||||
|
||||
// 使用系统安装流程,让系统处理权限和安装确认
|
||||
val intent = Intent(Intent.ACTION_VIEW)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
|
||||
val uri: Uri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
// Android 7.0及以上使用FileProvider
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
try {
|
||||
val authority = "${context.packageName}.fileprovider"
|
||||
FileProvider.getUriForFile(context, authority, file)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
result.error("FILEPROVIDER_ERROR", "FileProvider configuration error. Please check AndroidManifest.xml", e.message)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// Android 6.0及以下直接使用file://
|
||||
Uri.fromFile(file)
|
||||
}
|
||||
|
||||
intent.setDataAndType(uri, "application/vnd.android.package-archive")
|
||||
|
||||
// 直接启动安装Intent,让系统处理所有权限和确认流程
|
||||
try {
|
||||
context.startActivity(intent)
|
||||
result.success(true)
|
||||
} catch (e: Exception) {
|
||||
result.error("INTENT_ERROR", "Failed to start install intent: ${e.message}", null)
|
||||
}
|
||||
|
||||
} catch (e: Exception) {
|
||||
result.error("INSTALL_FAILED", "Install failed: ${e.message}", null)
|
||||
}
|
||||
}
|
||||
|
||||
private fun calculateMD5(file: File): String {
|
||||
val digest = MessageDigest.getInstance("MD5")
|
||||
val inputStream = FileInputStream(file)
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import 'dart:io';
|
|||
import 'app_upgrade_plugin_method_channel.dart';
|
||||
import 'app_upgrade_plugin_platform_interface.dart';
|
||||
import 'core/http_config.dart';
|
||||
import 'models/install_strategy.dart';
|
||||
import 'models/upgrade_info.dart';
|
||||
|
||||
// 简化版API(推荐使用)
|
||||
|
|
@ -13,6 +14,7 @@ export 'app_upgrade_simple.dart';
|
|||
export 'core/http_config.dart';
|
||||
// 权限帮助类
|
||||
export 'core/permission_helper.dart';
|
||||
export 'models/install_strategy.dart';
|
||||
export 'models/upgrade_info.dart';
|
||||
export 'widgets/widgets.dart';
|
||||
|
||||
|
|
@ -126,6 +128,38 @@ class AppUpgradePlugin {
|
|||
return AppUpgradePluginPlatform.instance.installApk(filePath);
|
||||
}
|
||||
|
||||
/// 使用系统流程安装APK(仅Android)
|
||||
///
|
||||
/// 类似市面上主流应用的安装流程:
|
||||
/// 1. 直接调用系统安装程序
|
||||
/// 2. 系统自动处理权限检查
|
||||
/// 3. 弹出"XX正尝试安装应用"对话框
|
||||
/// 4. 用户点击"继续"即可安装
|
||||
///
|
||||
/// [filePath] APK文件路径
|
||||
Future<bool> installApkWithSystemFlow(String filePath) {
|
||||
if (!Platform.isAndroid) {
|
||||
return Future.value(false);
|
||||
}
|
||||
return AppUpgradePluginPlatform.instance.installApkWithSystemFlow(filePath);
|
||||
}
|
||||
|
||||
/// 使用配置策略安装APK(仅Android)
|
||||
///
|
||||
/// 根据[config]配置动态选择安装策略:
|
||||
/// - [InstallStrategy.systemFlow]: 系统流程安装
|
||||
/// - [InstallStrategy.preCheckPermission]: 预检查权限
|
||||
/// - [InstallStrategy.smart]: 智能选择策略
|
||||
///
|
||||
/// [filePath] APK文件路径
|
||||
/// [config] 安装配置,默认使用系统流程
|
||||
Future<bool> installApkWithConfig(String filePath, {InstallConfig config = InstallConfig.systemFlow}) {
|
||||
if (!Platform.isAndroid) {
|
||||
return Future.value(false);
|
||||
}
|
||||
return AppUpgradePluginPlatform.instance.installApkWithConfig(filePath, config);
|
||||
}
|
||||
|
||||
/// 跳转到应用商店
|
||||
/// [url] 应用商店地址
|
||||
Future<bool> goToAppStore(String url) {
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ import 'package:url_launcher/url_launcher.dart';
|
|||
|
||||
import 'app_upgrade_plugin_platform_interface.dart';
|
||||
import 'core/http_config.dart';
|
||||
import 'core/permission_helper.dart';
|
||||
import 'models/install_strategy.dart';
|
||||
import 'models/upgrade_info.dart';
|
||||
|
||||
/// An implementation of [AppUpgradePluginPlatform] that uses method channels.
|
||||
|
|
@ -377,6 +379,119 @@ class MethodChannelAppUpgradePlugin extends AppUpgradePluginPlatform {
|
|||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> installApkWithSystemFlow(String filePath) async {
|
||||
if (!Platform.isAndroid) {
|
||||
debugPrint('installApkWithSystemFlow: 非Android平台');
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
debugPrint('开始使用系统流程安装APK: $filePath');
|
||||
|
||||
// Check if file exists
|
||||
final file = File(filePath);
|
||||
if (!await file.exists()) {
|
||||
debugPrint('安装失败: APK文件不存在 - $filePath');
|
||||
return false;
|
||||
}
|
||||
|
||||
final result = await methodChannel.invokeMethod<bool>('installApkWithSystemFlow', {'filePath': filePath});
|
||||
debugPrint('系统流程安装APK结果: $result');
|
||||
return result ?? false;
|
||||
} catch (e) {
|
||||
debugPrint('系统流程安装APK异常: $e');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> installApkWithConfig(String filePath, InstallConfig config) async {
|
||||
if (!Platform.isAndroid) {
|
||||
debugPrint('installApkWithConfig: 非Android平台');
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
debugPrint('开始使用配置策略安装APK: $filePath, 策略: ${config.strategy}');
|
||||
|
||||
// Check if file exists
|
||||
final file = File(filePath);
|
||||
if (!await file.exists()) {
|
||||
debugPrint('安装失败: APK文件不存在 - $filePath');
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (config.strategy) {
|
||||
case InstallStrategy.systemFlow:
|
||||
return await _installWithSystemFlow(filePath);
|
||||
|
||||
case InstallStrategy.preCheckPermission:
|
||||
return await _installWithPreCheck(filePath, config);
|
||||
|
||||
case InstallStrategy.smart:
|
||||
return await _installWithSmartStrategy(filePath, config);
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('配置策略安装APK异常: $e');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> _installWithSystemFlow(String filePath) async {
|
||||
final result = await methodChannel.invokeMethod<bool>('installApkWithSystemFlow', {'filePath': filePath});
|
||||
return result ?? false;
|
||||
}
|
||||
|
||||
Future<bool> _installWithPreCheck(String filePath, InstallConfig config) async {
|
||||
// 先检查权限
|
||||
final permissionStatus = await PermissionHelper.checkInstallPermission();
|
||||
|
||||
if (permissionStatus != InstallPermissionStatus.granted) {
|
||||
debugPrint('安装权限未授予,状态: $permissionStatus');
|
||||
|
||||
if (config.autoOpenSettings) {
|
||||
// 自动跳转到权限设置
|
||||
final opened = await openInstallPermissionSettings();
|
||||
if (opened) {
|
||||
debugPrint('已跳转到权限设置页面');
|
||||
// 等待用户操作
|
||||
await Future.delayed(const Duration(seconds: 2));
|
||||
// 重新检查权限
|
||||
final newStatus = await PermissionHelper.checkInstallPermission();
|
||||
if (newStatus != InstallPermissionStatus.granted) {
|
||||
debugPrint('用户未授予权限');
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
debugPrint('无法跳转到权限设置页面');
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 有权限时使用传统安装方式
|
||||
final result = await methodChannel.invokeMethod<bool>('installApk', {'filePath': filePath});
|
||||
return result ?? false;
|
||||
}
|
||||
|
||||
Future<bool> _installWithSmartStrategy(String filePath, InstallConfig config) async {
|
||||
// 智能策略:先检查权限状态
|
||||
final permissionStatus = await PermissionHelper.checkInstallPermission();
|
||||
|
||||
if (permissionStatus == InstallPermissionStatus.granted) {
|
||||
// 有权限时使用预检查方式
|
||||
debugPrint('智能策略: 检测到已有权限,使用预检查方式');
|
||||
return await _installWithPreCheck(filePath, config);
|
||||
} else {
|
||||
// 无权限时使用系统流程
|
||||
debugPrint('智能策略: 检测到无权限,使用系统流程');
|
||||
return await _installWithSystemFlow(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> goToAppStore(String url) async {
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import 'package:plugin_platform_interface/plugin_platform_interface.dart';
|
|||
|
||||
import 'app_upgrade_plugin_method_channel.dart';
|
||||
import 'core/http_config.dart';
|
||||
import 'models/install_strategy.dart';
|
||||
import 'models/upgrade_info.dart';
|
||||
|
||||
abstract class AppUpgradePluginPlatform extends PlatformInterface {
|
||||
|
|
@ -68,6 +69,18 @@ abstract class AppUpgradePluginPlatform extends PlatformInterface {
|
|||
throw UnimplementedError('installApk() has not been implemented.');
|
||||
}
|
||||
|
||||
/// 使用系统流程安装APK(仅Android)
|
||||
/// 让系统处理权限检查和用户确认,类似市面上主流应用的安装流程
|
||||
Future<bool> installApkWithSystemFlow(String filePath) {
|
||||
throw UnimplementedError('installApkWithSystemFlow() has not been implemented.');
|
||||
}
|
||||
|
||||
/// 使用配置策略安装APK(仅Android)
|
||||
/// 根据配置动态选择安装策略
|
||||
Future<bool> installApkWithConfig(String filePath, InstallConfig config) {
|
||||
throw UnimplementedError('installApkWithConfig() has not been implemented.');
|
||||
}
|
||||
|
||||
/// 跳转到应用商店
|
||||
Future<bool> goToAppStore(String url) {
|
||||
throw UnimplementedError('goToAppStore() has not been implemented.');
|
||||
|
|
|
|||
|
|
@ -0,0 +1,100 @@
|
|||
/// 安装策略枚举
|
||||
enum InstallStrategy {
|
||||
/// 系统流程:让系统处理权限检查和用户确认(推荐)
|
||||
/// 类似主流应用的安装体验,直接弹出"XX正尝试安装应用"对话框
|
||||
systemFlow,
|
||||
|
||||
/// 预检查权限:安装前先检查权限,无权限时返回错误
|
||||
/// 需要开发者手动处理权限申请流程
|
||||
preCheckPermission,
|
||||
|
||||
/// 智能模式:根据设备和权限状态自动选择最佳策略
|
||||
/// 有权限时使用预检查,无权限时使用系统流程
|
||||
smart,
|
||||
}
|
||||
|
||||
/// 安装配置类
|
||||
class InstallConfig {
|
||||
/// 安装策略
|
||||
final InstallStrategy strategy;
|
||||
|
||||
/// 是否在权限被拒绝时自动跳转到权限设置页面
|
||||
final bool autoOpenSettings;
|
||||
|
||||
/// 是否显示权限说明对话框
|
||||
final bool showPermissionRationale;
|
||||
|
||||
/// 权限说明对话框的标题
|
||||
final String? rationaleTitle;
|
||||
|
||||
/// 权限说明对话框的内容
|
||||
final String? rationaleContent;
|
||||
|
||||
const InstallConfig({
|
||||
this.strategy = InstallStrategy.systemFlow,
|
||||
this.autoOpenSettings = false,
|
||||
this.showPermissionRationale = false,
|
||||
this.rationaleTitle,
|
||||
this.rationaleContent,
|
||||
});
|
||||
|
||||
/// 系统流程配置(推荐)
|
||||
static const InstallConfig systemFlow = InstallConfig(
|
||||
strategy: InstallStrategy.systemFlow,
|
||||
);
|
||||
|
||||
/// 预检查权限配置
|
||||
static const InstallConfig preCheckPermission = InstallConfig(
|
||||
strategy: InstallStrategy.preCheckPermission,
|
||||
autoOpenSettings: true,
|
||||
showPermissionRationale: true,
|
||||
rationaleTitle: '需要安装权限',
|
||||
rationaleContent: '为了完成应用更新,需要允许安装未知来源的应用。',
|
||||
);
|
||||
|
||||
/// 智能模式配置
|
||||
static const InstallConfig smart = InstallConfig(
|
||||
strategy: InstallStrategy.smart,
|
||||
autoOpenSettings: true,
|
||||
showPermissionRationale: true,
|
||||
rationaleTitle: '需要安装权限',
|
||||
rationaleContent: '为了完成应用更新,需要允许安装未知来源的应用。',
|
||||
);
|
||||
|
||||
/// 创建自定义配置
|
||||
InstallConfig copyWith({
|
||||
InstallStrategy? strategy,
|
||||
bool? autoOpenSettings,
|
||||
bool? showPermissionRationale,
|
||||
String? rationaleTitle,
|
||||
String? rationaleContent,
|
||||
}) {
|
||||
return InstallConfig(
|
||||
strategy: strategy ?? this.strategy,
|
||||
autoOpenSettings: autoOpenSettings ?? this.autoOpenSettings,
|
||||
showPermissionRationale: showPermissionRationale ?? this.showPermissionRationale,
|
||||
rationaleTitle: rationaleTitle ?? this.rationaleTitle,
|
||||
rationaleContent: rationaleContent ?? this.rationaleContent,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toMap() {
|
||||
return {
|
||||
'strategy': strategy.index,
|
||||
'autoOpenSettings': autoOpenSettings,
|
||||
'showPermissionRationale': showPermissionRationale,
|
||||
'rationaleTitle': rationaleTitle,
|
||||
'rationaleContent': rationaleContent,
|
||||
};
|
||||
}
|
||||
|
||||
factory InstallConfig.fromMap(Map<String, dynamic> map) {
|
||||
return InstallConfig(
|
||||
strategy: InstallStrategy.values[map['strategy'] ?? 0],
|
||||
autoOpenSettings: map['autoOpenSettings'] ?? false,
|
||||
showPermissionRationale: map['showPermissionRationale'] ?? false,
|
||||
rationaleTitle: map['rationaleTitle'],
|
||||
rationaleContent: map['rationaleContent'],
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue