MAUI Blazor学习18-自动升级
MAUI Blazor学习18-自动升级
MAUI Blazor系列目录
- MAUI Blazor学习1-移动客户端Shell布局 - SunnyTrudeau - 博客园 (cnblogs.com)
- MAUI Blazor学习2-创建移动客户端Razor页面 - SunnyTrudeau - 博客园 (cnblogs.com)
- MAUI Blazor学习3-绘制ECharts图表 - SunnyTrudeau - 博客园 (cnblogs.com)
- MAUI Blazor学习4-绘制BootstrapBlazor.Chart图表 - SunnyTrudeau - 博客园 (cnblogs.com)
- MAUI Blazor学习5-BLE低功耗蓝牙 - SunnyTrudeau - 博客园 (cnblogs.com)
- MAUI Blazor学习6-扫描二维码 - SunnyTrudeau - 博客园 (cnblogs.com)
- MAUI Blazor学习7-实现登录跳转页面 - SunnyTrudeau - 博客园 (cnblogs.com)
- MAUI Blazor学习8-支持多语言 - SunnyTrudeau - 博客园 (cnblogs.com)
- MAUI Blazor学习9-VS Code开发调试MAUI入门 - SunnyTrudeau - 博客园 (cnblogs.com)
- MAUI Blazor学习10-BarcodeScanner扫描二维码 - SunnyTrudeau - 博客园 (cnblogs.com)
- MAUI Blazor学习11-百度地图定位 - SunnyTrudeau - 博客园 (cnblogs.com)
- MAUI Blazor学习12-文件另存为 - SunnyTrudeau - 博客园 (cnblogs.com)
- MAUI Blazor学习13-打开文件 - SunnyTrudeau - 博客园 (cnblogs.com)
- MAUI Blazor学习14-选择目录 - SunnyTrudeau - 博客园 (cnblogs.com)
- MAUI Blazor学习15-采用html2pdf.js生成pdf - SunnyTrudeau - 博客园 (cnblogs.com)
- MAUI Blazor学习16-连续按BACK退出APP - SunnyTrudeau - 博客园 (cnblogs.com)
- MAUI Blazor学习17-NavigationLock阻止页面回退 - SunnyTrudeau - 博客园 (cnblogs.com)
消费类的APP一般通过应用市场发行,便于广大消费者下载和升级。专业工具类的APP可以由制造商自行发布给特定范围的终端用户,APP需要具备自动检测新版本,自动升级的功能,便于管控。网上有很多介绍安卓APP自动升级方案的文章,大致如下:
1,部署一个云服务器,存放新版APP安装包,可以下载文件;
2,APP访问云服务器,检测最新版本和当前版本,如果发现有版本,询问用户是否升级;
3,APP从云服务器下载安装包,调用安卓系统的安装APP功能;
新建APP升级文件云服务
基于MaBlaApp解决方案,新建AppFileLib类库。新建APP文件信息记录,描述APP升级文件的关键信息。
D:\Software\gitee\mauiblazorapp\AppFileLib\AppFileInfo.cs
namespace AppFileLib; /// <summary> /// APP文件信息 /// </summary> /// <param name="AppName">APP名称</param> /// <param name="NumericVersion">数字版本号,例如2,用于比较,等于Microsoft.Maui.ApplicationModel.AppInfo.BuildString</param> /// <param name="DisplayVersion">显示版本号,例如1.0.2,用于显示,等于Microsoft.Maui.ApplicationModel.AppInfo.VersionString</param> /// <param name="Description">说明</param> /// <param name="HashCode">哈希码</param> public record AppFileInfo(string AppName, int NumericVersion, string DisplayVersion, string Description, string HashCode);
新建Asp.Net Core Web Api项目AppFileServer。提供查询APP新版本,下载APP文件的控制器。
D:\Software\gitee\mauiblazorapp\AppFileServer\Controllers\AppFileInfoController.cs
/// <summary> /// APP文件信息 /// </summary> [Route("api/[controller]")] [ApiController] public class AppFileInfoController : ControllerBase { private readonly IWebHostEnvironment _webHostEnvironment; private readonly ILogger<AppFileInfoController> _logger; public AppFileInfoController(IWebHostEnvironment webHostEnvironment, ILogger<AppFileInfoController> logger) { _webHostEnvironment = webHostEnvironment; _logger = logger; } /// <summary> /// 获取APP文件信息 /// </summary> /// <returns></returns> [HttpGet("{appName}/FileInfo")] public async Task<ActionResult<AppFileInfo?>> GetFileInfo([FromRoute] string appName) { string filePath = Path.Combine(_webHostEnvironment.WebRootPath, appName, "fileinfo.json"); if (!System.IO.File.Exists(filePath)) return NotFound(); string fileInfoJson = await System.IO.File.ReadAllTextAsync(filePath); var fileInfo = System.Text.Json.JsonSerializer.Deserialize<AppFileInfo>(fileInfoJson); return fileInfo; } /// <summary> /// 获取APP升级文件 /// </summary> /// <returns></returns> [HttpGet("{appName}/AppFile")] public ActionResult GetAppFile([FromRoute] string appName) { string fileName = "app.apk"; string filePath = Path.Combine(_webHostEnvironment.WebRootPath, appName, fileName); if (!System.IO.File.Exists(filePath)) return NotFound(); return File(new FileStream(filePath, FileMode.Open), "application/octet-stream", fileName); } }
在AppFileServer目录新建wwwroot目录,下边再新建MaBlaApp目录,用来存放MaBlaApp升级文件和版本说明文件。
新建fileinfo.json,描述APP文件信息,跟AppFileInfo记录匹配。
D:\Software\gitee\mauiblazorapp\AppFileServer\wwwroot\MaBlaApp\fileinfo.json
{ "AppName": "MaBlaApp", "NumericVersion": 2, "DisplayVersion": "1.0.2", "Description": "增加检测升级APP功能", "HashCode": "xxx" }
把AppFileServer侦听端口改为8500,本机调试运行,通过swagger测试Web Api接口,确认可用。
D:\Software\gitee\mauiblazorapp\AppFileServer\Properties\launchSettings.json
"https": { "commandName": "Project", "dotnetRunMessages": true, "launchBrowser": true, "launchUrl": "swagger", "applicationUrl": "https://localhost:8500", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } },
MaBlaApp项目增加检查APP升级文件功能
MaBlaApp项目引用AppFileLib类库。
新建升级软件服务接口IUpgradeAppService。
D:\Software\gitee\mauiblazorapp\MaBlaApp\Interfaces\IUpgradeAppService.cs
using AppFileLib; namespace MaBlaApp.Interfaces; /// <summary> /// 升级软件服务接口 /// </summary> public interface IUpgradeAppService { /// <summary> /// 检查新版软件 /// </summary> /// <param name="appName">APP名称</param> /// <returns></returns> Task<(AppFileInfo?, string Msg)> CheckNewVersionAsync(string appName); /// <summary> /// 检查获取安装APP权限 /// </summary> /// <returns></returns> Task<bool> CheckAndRequestInsatllAppPermission(); /// <summary> /// 下载安装文件 /// </summary> /// <param name="appName">APP名称</param> /// <param name="appHashCode">哈希码</param> /// <param name="processHandler">进度条处理方法</param> /// <returns></returns> Task<(bool Success, string Err)> DownloadFileAsync(string appName, string appHashCode, Action<long, long> processHandler); /// <summary> /// 安装新版软件 /// </summary> Task InstallNewVersion(); }
在安卓平台目录下面实现UpgradeAppService。
D:\Software\gitee\mauiblazorapp\MaBlaApp\Platforms\Android\UpgradeAppService.cs
using Android.Content; using Android.OS; using AppFileLib; using MaBlaApp.Interfaces; using System.Net.Http.Json; using System.Security.Cryptography; namespace MaBlaApp; /// <summary> /// 升级软件服务接口 /// </summary> public class UpgradeAppService : IUpgradeAppService { readonly string ApkName = "com.companyname.mablaapp.apk"; //来自AndroidManifest.xml readonly string FileAuthorities = "com.companyname.mablaapp.fileprovider"; private readonly IHttpClientFactory _httpClientFactory; public UpgradeAppService(IHttpClientFactory httpClientFactory) { _httpClientFactory = httpClientFactory; } /// <summary> /// 检查新版软件 /// </summary> /// <param name="appName">APP名称</param> /// <returns></returns> public async Task<(AppFileInfo?, string Msg)> CheckNewVersionAsync(string appName) { try { //获取当前版本号 //Microsoft.Maui.ApplicationModel.VersionTracking.CurrentVersion=csproj项目配置文件ApplicationDisplayVersion=1.0.1 //Microsoft.Maui.ApplicationModel.AppInfo.VersionString=1.0.1,AppInfo.BuildString=1 var currentVersion = int.Parse(AppInfo.BuildString); string url = $"api/AppFileInfo/{appName}/FileInfo"; System.Diagnostics.Debug.WriteLine($"检查更新, url={url}"); var _client = _httpClientFactory.CreateClient("AppFileClient"); //获取服务器最新软件版本 var latestVersion = await _client.GetFromJsonAsync<AppFileInfo>(url); System.Diagnostics.Debug.WriteLine($"检查更新, currentVersion={currentVersion}, latestVersion={latestVersion?.NumericVersion}"); if (latestVersion is null) { return (null, "获取服务器软件信息失败"); } //如果服务器版本号跟当前版本号不一致,返回新版软件信息 if (currentVersion != latestVersion!.NumericVersion) return (latestVersion, ""); else return (null, "已经是最新版本"); } catch (Exception ex) { string err = $"检查更新出错, {ex}"; System.Diagnostics.Debug.WriteLine(err); return (null, err); } } #region 获取安装APP权限 /// <summary> /// 检查获取安装APP权限 /// </summary> /// <returns></returns> public async Task<bool> CheckAndRequestInsatllAppPermission() { if (Build.VERSION.SdkInt >= BuildVersionCodes.O) { bool canRequestPackageInstalls = Android.App.Application.Context.PackageManager.CanRequestPackageInstalls(); var status = await Permissions.CheckStatusAsync<InstallAppPermissions>(); //VS2022安卓模拟器,[0:] 获取安装未知应用权限, canRequestPackageInstalls=True, RequestInstallPackagesStatus=Denied System.Diagnostics.Debug.WriteLine($"获取安装未知应用权限, canRequestPackageInstalls={canRequestPackageInstalls}, RequestInstallPackagesStatus={status}"); if (!canRequestPackageInstalls) { //var status = await Permissions.CheckStatusAsync<InstallAppPermissions>(); //if (status == PermissionStatus.Granted) // return true; //没有触发用户同意,弹出允许安装未知来源软件页面 //status = await Permissions.RequestAsync<InstallAppPermissions>(); requestInstallPermission(); if (status == PermissionStatus.Granted) return true; } else { return true; } } return false; } //public const int REQUEST_CODE_INSTALL_PERMISSION = 100; private void requestInstallPermission() { if (Build.VERSION.SdkInt >= BuildVersionCodes.O) { //Intent intent = new Intent(Android.Content.Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES, Android.Net.Uri.Parse("package:" + Android.App.Application.Context.PackageName)); //Intent intent = new Intent(Intent.ExtraNotUnknownSource, Android.Net.Uri.Parse("package:" + Android.App.Application.Context.PackageName)); Intent intent = new Intent(Android.Provider.Settings.ActionManageUnknownAppSources, Android.Net.Uri.Parse("package:" + Android.App.Application.Context.PackageName)); //Android.App.Application.Context.StartActivityForResult(intent, REQUEST_CODE_INSTALL_PERMISSION); //registerForActivityResult(intent, REQUEST_CODE_INSTALL_PERMISSION); intent.AddFlags(ActivityFlags.NewTask); intent.AddFlags(ActivityFlags.ResetTaskIfNeeded); Android.App.Application.Context.StartActivity(intent); } } #endregion /// <summary> /// 下载安装文件 /// </summary> /// <param name="appName">APP名称</param> /// <param name="appHashCode">哈希码</param> /// <param name="processHandler">进度条处理方法</param> /// <returns></returns> public async Task<(bool Success, string Err)> DownloadFileAsync(string appName, string appHashCode, Action<long, long> processHandler) { try { var _client = _httpClientFactory.CreateClient("AppFileClient"); string url = $"api/AppFileInfo/{appName}/AppFile"; var req = new HttpRequestMessage(HttpMethod.Get, url); //获取到http头就开始处理 var response = await _client.SendAsync(req, HttpCompletionOption.ResponseHeadersRead); var fileLen = response.Content.Headers.ContentLength; var responseStream = await response.Content.ReadAsStreamAsync(); var filePath = Path.Combine(FileSystem.AppDataDirectory, ApkName); System.Diagnostics.Debug.WriteLine($"下载文件保存路径: {filePath}"); //[0:] 下载文件保存路径: /data/user/0/com.companyname.mablaapp/files/com.companyname.mablaapp.apk using var fileStream = new FileStream(filePath, FileMode.Create); var buffer = new byte[10240]; var downloadLen = 0; int readLen = 0; while ((readLen = await responseStream.ReadAsync(buffer)) != 0) { // 写入到文件 await fileStream.WriteAsync(buffer, 0, readLen); downloadLen += readLen; processHandler(downloadLen, fileLen!.Value); System.Diagnostics.Debug.WriteLine($"下载文件, downloadLen={downloadLen:N0}, fileLen={fileLen!.Value:N0}"); } //检查文件哈希吗 string fileHash = GetFileHash(fileStream); if (fileHash == appHashCode) { return (true, ""); } else { string err = $"下载文件哈希吗错误, fileHash={fileHash}, appHashCode={appHashCode}"; System.Diagnostics.Debug.WriteLine(err); return (false, err); } } catch (Exception ex) { string err = $"下载文件出错, {ex}"; System.Diagnostics.Debug.WriteLine(err); return (false, err); } } //计算文件哈希吗 private string GetFileHash(Stream stream) { stream.Position = 0; using var sha256 = SHA256.Create(); var hash = sha256.ComputeHash(stream); string fileHash = BitConverter.ToString(hash).Replace("-", "").ToLower(); return fileHash; } /// <summary> /// 安装新版软件 /// </summary> public async Task InstallNewVersion() { try { var filePath = Path.Combine(FileSystem.AppDataDirectory, ApkName); var intent = new Intent(Intent.ActionView); Android.Net.Uri? uri; // 判断Android版本 if (Build.VERSION.SdkInt >= BuildVersionCodes.N) { //给临时读取权限 intent.SetFlags(ActivityFlags.GrantReadUriPermission); var apkFile = new Java.IO.File(filePath); uri = FileProvider.GetUriForFile(Android.App.Application.Context, FileAuthorities, apkFile); } else { uri = Android.Net.Uri.FromFile(new Java.IO.File(filePath)); } // 设置显式 MIME 数据类型 intent.SetDataAndType(uri, "application/vnd.android.package-archive"); //指定以新任务的方式启动Activity intent.AddFlags(ActivityFlags.NewTask); System.Diagnostics.Debug.WriteLine($"开始安装软件, filePath={filePath}, uri={uri}, intent={intent}"); //激活一个新的Activity Android.App.Application.Context.StartActivity(intent); System.Diagnostics.Debug.WriteLine($"完成安装软件"); } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"安装软件出错, {ex}"); } } }
添加申请安装APP权限InstallAppPermissions。
D:\Software\gitee\mauiblazorapp\MaBlaApp\Platforms\Android\InstallAppPermissions.cs
using Android.OS; namespace MaBlaApp; /// <summary> /// 申请安装APP权限 /// </summary> public class InstallAppPermissions : Permissions.BasePlatformPermission { public override (string androidPermission, bool isRuntime)[] RequiredPermissions => GetRequiredPermissions(); //根据安卓平台版本,返回对应的申请权限 private (string androidPermission, bool isRuntime)[] GetRequiredPermissions() { //https://blog.51cto.com/u_16213404/9289944 //《android 11静默安装失败_mob64ca12e9cad4的技术博客_51CTO博客.mhtml》 var permissions = new List<string>(); //Android 9 API 28 Pie (Android P) 2018 //Android 8 API 26 Oreo(Android O) 2017 if (Build.VERSION.SdkInt >= BuildVersionCodes.O) { permissions.Add(global::Android.Manifest.Permission.RequestInstallPackages); } var result = new List<(string androidPermission, bool isRuntime)>(); foreach (var permission in permissions) { result.Add((permission, true)); } return result.ToArray(); } }
注册UpgradeAppService。
D:\Software\gitee\mauiblazorapp\MaBlaApp\MauiProgram.cs
#if ANDROID builder.Services.AddSingleton<IUpgradeAppService, UpgradeAppService>(); #endif //注册访问升级文件服务器的HttpClient builder.Services.AddHttpClient("AppFileClient") .ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler { ServerCertificateCustomValidationCallback = delegate { return true; }//忽略https证书检查 }) //.ConfigureHttpClient(c => c.BaseAddress = new Uri("https://www.myappfile.com:8500"));//发布到手机,访问云服务器 .ConfigureHttpClient(c => c.BaseAddress = new Uri("https://10.0.2.2:8500"));//VS2022调试安卓模拟器
在安卓配置文件添加文件服务声明。
D:\Software\gitee\mauiblazorapp\MaBlaApp\Platforms\Android\AndroidManifest.xml
<application android:allowBackup="true" android:icon="@mipmap/appicon" android:roundIcon="@mipmap/appicon_round" android:supportsRtl="true"> <provider android:name="androidx.core.content.FileProvider" android:authorities="com.companyname.mablaapp.fileprovider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/provider_paths" /> </provider> </application> <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
添加provider_paths.xml文件。
D:\Software\gitee\mauiblazorapp\MaBlaApp\Platforms\Android\Resources\xml\provider_paths.xml
<?xml version="1.0" encoding="utf-8" ?> <resources> <paths> <root-path name="root" path="" /> <files-path name="files" path="" /> <cache-path name="cache" path="" /> <external-path name="camera_photos" path="" /> <external-files-path name="external_file_path" path="" /> <external-cache-path name="external_cache_path" path="" /> </paths> </resources>
在MaBlaApp项目,添加UpgradeApp.razor页面,点击【检查新版本】菜单可以访问服务器,可以下载新版APP然后启动安装。在实际产品项目中,也可以在APP启动后,在后台访问服务器,检查新版本。推荐采用数字类型的Microsoft.Maui.ApplicationModel.AppInfo.BuildString比较版本号,比较方便,如果采用字符串类型的AppInfo.VersionString,需要把多个小数点分隔的版本号转换为数字再比较,比较麻烦。
D:\Software\gitee\mauiblazorapp\MaBlaApp\Pages\UpgradeApp.razor
@page "/upgradeapp" @using MaBlaApp.Interfaces @using AppFileLib @inject IUpgradeAppService UpgradeAppService <h3>检查升级APP</h3> <ul class="list-group"> <li class="list-group-item d-flex justify-content-between"> <strong>当前显示版本</strong> <small>@AppInfo.VersionString</small> </li> <li class="list-group-item d-flex justify-content-between"> <strong>当前数字版本</strong> <small>@AppInfo.BuildString</small> </li> </ul> <div class="d-flex justify-content-between m-1"> <button class="btn btn-primary mx-2 @BtnStartCss" @onclick=CheckNewVersion>检查新版本</button> </div> @if (FileInfo is not null) { <ul class="list-group"> <li class="list-group-item d-flex justify-content-between"> <strong>最新显示版本</strong> <small>@FileInfo.DisplayVersion</small> </li> <li class="list-group-item d-flex justify-content-between"> <strong>最新数字版本</strong> <small>@FileInfo.NumericVersion</small> </li> </ul> } @if (IsShowProgress) { <div class="m-2"> <label class="control-label">@ProcessTitle</label> <div class="progress" style="height:10px"> <div class="progress-bar bg-primary" style="width:@ProcessVal"></div> </div> </div> } <label class="control-label m-2">@Msg</label> @code { string BtnStartCss => IsBusy ? "disabled" : ""; bool IsBusy = false; CancellationTokenSource Cts; AppFileInfo? FileInfo = null; bool IsShowProgress = false; string ProcessVal = ""; string ProcessTitle = ""; string Msg = ""; DateTimeOffset LastUpdateProcessTime = DateTimeOffset.Now; async void CheckNewVersion() { //检查获取安装APP权限 bool hasInsatllAppPermission = await UpgradeAppService.CheckAndRequestInsatllAppPermission(); if (!hasInsatllAppPermission) return; IsShowProgress = false; Msg = "获取服务器软件信息..."; await InvokeAsync(() => StateHasChanged()); //检查更新 (FileInfo, Msg) = await UpgradeAppService.CheckNewVersionAsync("MaBlaApp"); await InvokeAsync(() => StateHasChanged()); if (FileInfo is not null) { string msg = $"当前版本 V{AppInfo.VersionString}, {Environment.NewLine}检测到新版本 V{FileInfo.DisplayVersion},{Environment.NewLine}说明: {FileInfo.Description}, {Environment.NewLine}是否升级?"; var confirm = await Application.Current.MainPage.DisplayAlert("信息", msg, "是", "否"); if (confirm) { IsShowProgress = true; //下载安装文件 var result = await UpgradeAppService.DownloadFileAsync(FileInfo.AppName, FileInfo.HashCode, DownloadProgressChanged); if (result.Success) { //安装新版软件 await UpgradeAppService.InstallNewVersion(); } else { Msg = result.Err; } } } // else // { // await Application.Current.MainPage.DisplayAlert("信息", $"当前版本已经是最新版", "确定"); // } await InvokeAsync(() => StateHasChanged()); } private async void DownloadProgressChanged(long downloadLen, long fileLen) { int currentProcess = (int)(downloadLen * 100 / fileLen); if (currentProcess < 100) { //定期刷新进度 if (DateTimeOffset.Now.Subtract(LastUpdateProcessTime).TotalSeconds < 1) return; LastUpdateProcessTime = DateTimeOffset.Now; ProcessTitle = $"正在下载软件 {downloadLen:N0}/{fileLen:N0}, 进度{currentProcess}%"; ProcessVal = $"{currentProcess}%"; } else { ProcessTitle = $"完成下载软件 {downloadLen:N0}/{fileLen:N0}, 进度{currentProcess}%"; ProcessVal = $"{currentProcess}%"; } await InvokeAsync(() => StateHasChanged()); } }
发布MaBlaApp安装包
把MaBlaApp项目的显示版本号设置为1.0.1,数字版本号设置为1
D:\Software\gitee\mauiblazorapp\MaBlaApp\MaBlaApp.csproj
<ApplicationDisplayVersion>1.0.1</ApplicationDisplayVersion>
<ApplicationVersion>1</ApplicationVersion>
注意csproj对版本号格式有限制,我本来想设置为
<ApplicationDisplayVersion>1.0.0.1</ApplicationDisplayVersion>
<ApplicationVersion>1001</ApplicationVersion>
调试运行没问题,但是发布apk报错
1>C:\Program Files\dotnet\packs\Microsoft.Maui.Resizetizer.Sdk\7.0.101\targets\Microsoft.Maui.Resizetizer.targets(655,9): error : ApplicationDisplayVersion '1.0.0.1' was not a valid 3 part semver version number and/or ApplicationVersion '1001' was not a valid integer.
1>已完成生成项目“MaBlaApp.csproj”的操作 - 失败。
发布一个MaBlaApp的apk,注意要先把项目改为release模式,把项目属性的android包格式设置为APK,再发布apk。
在VS2022存档管理器页面,分发apk,选择临时,创建一个秘钥证书,别名mabla,密码mabla1234。选择mabla证书,把apk另存为com.companyname.mablaapp.apk,需要输入证书密码mabla1234,最终把com.companyname.mablaapp.apk文件复制到D:\Software\gitee\mauiblazorapp\AppFileServer\wwwroot\MaBlaApp\app.apk备用。
然后获取app.apk的SHA256,进入win11控制台,进入app.apk所在目录,执行命令
D:\myhome-txy\appfileserver\pub\wwwroot\MaBlaApp>certutil -hashfile app.apk SHA256
SHA256 的 app.apk 哈希:
xxx
把哈希码填写到服务端项目的fileinfo.json文件HashCode字段
D:\Software\gitee\mauiblazorapp\AppFileServer\wwwroot\MaBlaApp\fileinfo.json
在安卓模拟器调试运行
在VS2022自带的安卓模拟器上调试运行MaBlaApp和AppFileServer项目,MaBlaApp可以访问localhost地址的AppFileServer,检测APP新版本。
首次安装MaBlaApp运行,在弹出的申请安装未知应用页面打开【允许来自此来源的应用】。再次运行MaBlaApp,就不会弹出这个页面了。
可以下载APP文件。
但是无法安装。
我本来以为是代码有BUG,调试发现MaBlaApp已经具备了安装APP的权限,但是请求安装APP总是拒绝!我觉得非常离谱,后来查阅了一些资料,说是安卓模拟器不支持应用内安装APP,所以,需要用真机来测试。
bool canRequestPackageInstalls = Android.App.Application.Context.PackageManager.CanRequestPackageInstalls();
var status = await Permissions.CheckStatusAsync<InstallAppPermissions>();
//VS2022安卓模拟器,[0:] 获取安装未知应用权限, canRequestPackageInstalls=True, RequestInstallPackagesStatus=Denied
System.Diagnostics.Debug.WriteLine($"获取安装未知应用权限, canRequestPackageInstalls={canRequestPackageInstalls}, RequestInstallPackagesStatus={status}");
在安卓手机调试运行
把AppFileServer通过容器化方式部署到云服务器。AppFileServer添加Dockerfile。编写docker-compose.yml,为了方便调试,通过环境变量配置SSL证书和密码,注意把SSL证书文件放在docker-compose.yml当前目录下。
D:\Software\gitee\mauiblazorapp\AppFileServer\docker-compose.yml
version: '3' services: appfileserver: container_name: appfileserver image: appfileserverimage build: context: ./pub dockerfile: Dockerfile ports: - "8500:8500" environment: # - ASPNETCORE_ENVIRONMENT=Production - ASPNETCORE_ENVIRONMENT=Development - ASPNETCORE_URLS=https://*:8500 - ASPNETCORE_HTTPS_PORT=8500 #配置SSL证书和密码 - ASPNETCORE_Kestrel__Certificates__Default__Path=/https/myweb.pfx - ASPNETCORE_Kestrel__Certificates__Default__Password=xxx - TZ=Asia/Shanghai volumes: - ./data:/app/data - ./myweb.pfx:/https/myweb.pfx # - ./aspnetkeys:/root/.aspnet/DataProtection-Keys security_opt: - "seccomp:unconfined" restart: always
把MaBlaApp项目的服务器地址改为云服务器(比如www.myappfile.com),再次发布一个apk,复制到AppFileServer的wwwroot\MaBlaApp\app.apk并部署到云服务器。
D:\Software\gitee\mauiblazorapp\MaBlaApp\MauiProgram.cs
public static MauiApp CreateMauiApp()
//注册访问升级文件服务器的HttpClient
builder.Services.AddHttpClient("AppFileClient")
.ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler
{
ServerCertificateCustomValidationCallback = delegate { return true; }//忽略https证书检查
})
.ConfigureHttpClient(c => c.BaseAddress = new Uri("https://www.myappfile.com:8500"));//发布到手机,访问云服务器
//.ConfigureHttpClient(c => c.BaseAddress = new Uri("https://10.0.2.2:8500"));//VS2022调试安卓模拟器
在安卓手机调试运行MaBlaApp,测试手机是华为鸿蒙,兼容安卓12,API 31。测试安装失败了,提示apk签名不一致,因为下载的apk是relaese发布签名的,跟调试运行的debug版本的apk不是同一个软件
。
把release发布的apk,直接安装到手机,然后再运行发布版的MaBlaApp,终于成功实现了安装新版APP功能。
问题小结
1,安卓模拟器不支持应用内安装APP,需要在手机上测试;
2,debug模式运行的APP,不支持升级release模式发布的APK,需要在手机上直接运行release的APK;
DEMO代码地址:https://gitee.com/woodsun/mauiblazorapp