1. 目录结构:
(1)_imports.razor是一个全局using namespace的地方
(2)Platforms下的代码,虽然都放在同一个项目下,但是Platforms\Android下的.cs类,不能被其他地方访问,相当于是一个个独立的子项目
2. .razor 最终会被编译为一个类。
(1)可以为 xx.razor,添加一个文件 xx.razor.cs,里面定义public partial class xx
3. 也可以为 xx.razor,添加一个文件 xx.razor.css,里面设置html标签的样式,提供给xx.razor使用
4. Main.razor,其实就是 class Main了
5. 放在Shared中的.razor有如下特点:
(1). 是可以被【嵌入】到放在Pages的.razor中使用的。
(2). 在xxx.razor文件中,是没有@page "/xxx"这样的
6. 在Pages的.razor有如下特点:
(1) 在xxx.razor文件中,有标记@page "/xxx",等价于 :
[Route("/xxx")]
public partial class xx { }
(2) 使用@page "/",指定第一个弹出来的page
7. .razor中,html标签和c#属性关联起来,参考:https://www.cnblogs.com/jimsfriend/p/14017016.html
8. <NavLink>标签——用于页面跳转
(1) <NavLink>标签,相当于<a>标签,是跳转到某页面c。
(2) 跳转过程:
1. 点击<NavLink>,会“广播”路由消息,携带href参数;
2. Main.razor类会指定MainLayout.razor来处理;
3. MainLayout.razor,会在@body处,嵌入c页面
(3)每次页面跳转,.razor所生成的类,都会实例化一次;(重点)
9. 属性依赖注入,在.razor页面中,使用@inject NavigationManager nav,实例化的时候注入nav对象。在.razor.cs文件中,就可以直接使用nav这个对象了
10.NavigationManager nav,可以实现页面跳转,原理也是和 <NavLink>标签触发一样的
11. 使用bootstrap之外的图标:
(1)在“阿里巴巴”图标库中,将项目打包,下载解压,得到如下文件:
1. iconfont.js
2. iconfont.css
3. demo_index.html
(假设文件都放在css/open-iconic/font/alifonts/ 目录下)
(2)在css/index.html中,加入:
1. <link rel="stylesheet" href="css/open-iconic/font/alifonts/iconfont.css" />
2. <script src="css/open-iconic/font/alifonts/iconfont.js"></script>
(3)在要使用图标的时候,例如:
<span class="iconfont" aria-hidden="true"></span>
class 是 “iconfont”,其中的内容就是demo.html中的代码
12. 嵌入js:
<button type="button"
class="btn btn-primary" id="liveToastBtn"
onclick="@jstext">Show live toast</button>
@code{
const string jstext = "alert('hhhh');"; //这里就可以为所欲为了,直接写js给botton响应
}
13. 使用bootstrap
1. 需要下载bootstrap,里面包含各种 css, js文件
2. index.html中加入 <link rel="stylesheet" href="css/bootstrap/css/bootstrap.css" />
3. index.html中加入 <script src="css/bootstrap/js/bootstrap.js"></script>
14. 用户控件:
1. 用户控件,可以放在shared/UserControl.razor中,没有使用@page 路由
2. 在UserControl.razor中,可以声明,用于传递参数:
[Parameter]
public string Message { get; set; }
3. 在page.razor中,可以,xxx是传递的参数:
<UserControl Message="xxxx"></UserControl>
15. 需要自行创建 Resource\mipmap文件夹,将*.png放里面,才能在Android清单中选择图标
16. 需要自行创建Platforms\Android\Resources\drawable、mipmap-hdpi等等文件夹,并且将png全部拷贝进去
17. 需要设置Android应用程序图标,不要通过属性设置,仅仅设置MainActivity就可以, 例如 : mipmap/fmos2
[Activity(Theme = "@style/Maui.SplashTheme", Icon = "@mipmap/fmos2", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)]
18. 针对不同平台实现不同的接口,例如Android要实现接口 IGetServiceNames,那么,应该:
1. 在Platforms/Android/Models下,新建类实现该接口
(不能像Xamarin该类的namespace上加上[assembly: Dependency(typeof(FmosMobile.Droid.Models.GetServiceNames))]) !!!!
2. 在MainApplication.cs中,的构造函数中,加入:
DependencyService.Register<IGetServiceNames, GetServiceNames>();
3. 使用的时候,就可以: var obj = DependencyService.Get<IExteralFileHelper>();
19. 关于数据双向绑定,查看:
1. https://learn.microsoft.com/zh-cn/aspnet/core/blazor/components/data-binding?view=aspnetcore-6.0
2. https://blog.csdn.net/sD7O95O/article/details/127399331
比较重要一点,务必要记住:
1. 绑定不像xaml,标签属性绑定关系要显式写出,而是根据“命名约定”,由框架编译的时候做绑定。
2. 以下 [Parameter],最好要成双出现,比如:T Property,一定要有对应的 “EventCallback<T> YearChanged”
假设这里是子控件 Shared\ChildBind.razor 的代码
<button @onclick="UpdateYearFromChild"> 点击触发UpdateYearFromChild() </button>
[Parameter]
public int Year { get; set; }
//这里是“命名约定”,必须要 Year + Changed,重点是,父page不需要对其显示赋值,由框架完成的。
//****重点,不成对出现会报错!!
[Parameter]
public EventCallback<int> YearChanged { get; set; }
void UpdateYearFromChild(){
Year = new Random().Next(1950, 2021);
await YearChanged.InvokeAsync(Year);
}
3. 父控件Parent.razor如何不通过显式对YearChanged赋值,达到收到ChildBind.Year值改变的通知:
<!--@bind-Year中的Year,是子控件的Year,year是Parent.year-->
<!--@bind-Year:event,其实可以不用写,因为根据“命名约定”规则,会默认是YearChanged。-->
<!--@bind-Year:event,意思应该是,由ChildBind哪个事件,触发Parent.year改变-->
<ChildBind @bind-Year="year"
@bind-Year:event="YearChanged"
/>
private int year{ get;set;}
4. 如果要在构造得时候填值,不用@,例如:<ChildBind Year="常量" />
20. 一般绑定:
<p role="status">Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
private int currentCount = 0;
private void IncrementCount()
{
//无需要自行调用javascript来更新<p>的内容,能自动更新
currentCount++;
}
}
21. 表单绑定:
<input @bind="InputValue" />
private string InputValue { get; set; }
这样绑定貌似只是单向的,字段改了不会通知表单,代码改了InputValue需要使用 this.StateHasChanged();通知UI
30. 标签事件:
不带参数: @onclick="UpdateYearFromChild"
带参数: @onclick="@(e => getSelectProject(i))"
31. page.razor通过@ref获取子组件引用:
<InnerMessageBox @ref="innerMessageBox"></InnerMessageBox>
InnerMessageBox innerMessageBox { get; set; }
32. 使用java的.jar包,需要:
1. 必须使用.net7 来创建项目
2. 创建Android绑定项目:
(1)并添加Jars文件夹,将.jar文件拷贝到里面;
(2)修改Android绑定库的配置文件:
在<Project>标签内,加入:
<ItemGroup>
<EmbeddedJar Include="Jars\RuideApi.jar" />
</ItemGroup>
3. MAUI的项目,要想引入Android绑定项目,只能将项目修改为只支持安卓的:
(1)<!--<TargetFrameworks>net7.0-android;net7.0-ios;net7.0-maccatalyst</TargetFrameworks>--> 改为:
<TargetFrameworks>net7.0-android</TargetFrameworks>
(2) 注释掉:
<!--<TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows'))">$(TargetFrameworks);net7.0-windows10.0.19041.0</TargetFrameworks>-->
<!-- <TargetFrameworks>$(TargetFrameworks);net7.0-tizen</TargetFrameworks> -->
33. 如果编译时报 PE image does not have metadata,就是有一个包有问题,可能是引用了本地的dll导致的,要找出来干掉,改用nuget的
34. 要使用javascript动态移除【列表】【表格】元素的,要设置 protected override bool ShouldRender(){ return false ;} ,否则会出错
35. 替换启动画面 :https://learn.microsoft.com/zh-cn/dotnet/maui/user-interface/images/splashscreen?tabs=android
同时也改项目文件
36. 关于X3dom
1. X3dom默认Y轴向上, X轴向屏幕右方,Z轴向屏幕外边
2. 具体教程:http://x3dgraphics.com/examples/X3dForWebAuthors/
3. css文件,也必须弄到razor.css中,不能单独在index.html中引入
37. 如果使用<select class="form-select"></>标签,那么弹出选择窗口后,依然会触发一次OnAfterRender
弹出输入法,也会触发!!!!!!!!!!!!1
OnAfterRender(bool firstRender); //使用 NavigationManager.NavigateTo导航到其他页面,也是会触发的!!
38. 关于返回键
1. 如果弹出模态框,使用返回键(不加限制)会导致页面后退,或者模态框消失但是页面不能操作。
2. 常规方法在mainactivity监控是无效的在Blazor项目中无效,因为webview会优先接管返回按钮的事件,并阻止向上传播到 MainActivity中,需要重写
public override bool DispatchKeyEvent(KeyEvent e)
{
if (e.KeyCode == Keycode.Back)
if (e.Action == KeyEventActions.Down)
}
参考:https://www.cnblogs.com/sunday866/p/17331304.html
使用返回键关闭模态框
var bc = DependencyService.Get<IBackKeyBroadcaster>();
bc.OnBackKeyPress = () =>
{
this.isVisable = false;
bc.OnBackKeyPress = null;
JS.InvokeVoidAsync(CssConst.CloseDialog, "bubledialog");
return true;
};
39. 工作负载:
error NETSDK1147: 要构建此项目,必须安装以下工作负载: wasm-tools
error NETSDK1147: 要安装这些工作负载,请运行以下命令: dotnet workload restore
打开CMD,运行:
dotnet workload install maui
dotnet workload install maui-android
dotnet workload install android
重启vs
39. 错误排查:
1. MAUI [chromium] [INFO:CONSOLE(1)] "Failed to compare two elements in the array. [chromium] at System.Collections.Generic.ArraySortHelper`1[[Microsoft.AspNetCore.Components.Routing.RouteEntry, Microsoft.AspNetCore.Components, Version=7.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60]].Sort(Span`1 keys, IComparer`1 comparer)
解决办法:1. 肯定是有@page "/SingleStMs/SingleStTaskPage" 类似的重复
40. 权限设置
1. 必须在AndroidManifest.xml中声明需要哪些权限。
2. 必须使用代码检查权限,如下:
async Task CheckPermission<T>() where T : BasePermission, new()
{
PermissionStatus status = await Permissions.CheckStatusAsync<T>();
if (status != PermissionStatus.Granted)
{
await Permissions.RequestAsync<T>();
}
}
调用:await CheckPermission<Permissions.NetworkState>();
3. 如果是蓝牙,还需要自定义权限:
/// <summary>
/// 资料 https://segmentfault.com/a/1190000042518175
/// </summary>
public class BluetoothPermissions : Permissions.BasePlatformPermission
{
public override (string androidPermission, bool isRuntime)[] RequiredPermissions =>
new List<(string androidPermission, bool isRuntime)>
{
(global::Android.Manifest.Permission.AccessFineLocation, true),
(global::Android.Manifest.Permission.AccessCoarseLocation, true),
(global::Android.Manifest.Permission.Bluetooth, true),
(global::Android.Manifest.Permission.BluetoothAdmin, true),
}.ToArray();
}
调用:await CheckPermission<BluetoothPermissions>();
adb指令:
https://blog.csdn.net/m0_57098592/article/details/129441707
日志输出: adb logcat -d | findstr com.southgz.FmosMobile.MAUI > C:\Users\pyl\Desktop\appError.txt
查看:
https://learn.microsoft.com/zh-cn/xamarin/android/deploy-test/debugging/android-debug-log?tabs=windows
无线调试
adb tcpip 5555
adb connect 192.168.1.111
adb shell ip -f inet addr 查看已连接wifi的设备的Ip
要想用JavaScript中设置元素中文,要先转为Unicode
https://www.bejson.com/convert/unicode_chinese/
比如:“操作” 的Unicode 为 \u64cd\u4f5c,那么:
<div>\u64cd\u4f5c</div>
41.[推荐] 使用命令行发布,可以得到签名的.apk:参考:https://www.zhihu.com/tardis/zm/art/576654157?source_id=1005
1. 最好要决定使用命令行,就一开始就使用。连 .keystore文件也使用命令行创建
在.csproj下,CMD:
keytool -genkey -v -keystore myapp.keystore -alias key -keyalg RSA -keysize 2048 -validity 10000
按照提示就可以创建了
正常的话,会生成:
myapp.keystore,在项目文件夹下
2. 在.csproj文件所在位置,CMD,运行:
项目文件加入,事先加上这个,声明使用哪个keystore
<PropertyGroup Condition="$(TargetFramework.Contains('-android')) and '$(Configuration)' == 'Release'">
<AndroidKeyStore>True</AndroidKeyStore>
<!--设置为 True 对应用进行签名-->
<AndroidSigningKeyStore>myapp.keystore</AndroidSigningKeyStore>
<!--在上一部分中创建的密钥存储文件:myapp.keystore-->
<AndroidSigningKeyAlias>key</AndroidSigningKeyAlias>
<!--传递给 keytool 工具的参数值:键-->
<AndroidSigningKeyPass>southgz123</AndroidSigningKeyPass>
<!--创建密钥存储文件时提供的密码-->
<AndroidSigningStorePass>southgz123</AndroidSigningStorePass>
<!--创建密钥存储文件时提供的密码-->
</PropertyGroup>
在.csproj文件夹下,CMD
3. dotnet publish -f:net7.0-android -c:Release /p:AndroidSigningKeyPass=southgz123 /p:AndroidSigningStorePass=southgz123 /p:AndroidSdkDirectory=C:\Android\android-sdk
4. 遇到错误:
1. Xamarin Forms error MSB6006: “java.exe”已退出,代码为 2 解决办法,参见:https://www.cnblogs.com/mschen/p/9525209.html
在“项目-属性 - Android - 勾选Multi-Dex”
2. [注意] 调试的时候,需要还原:不要勾选R8优化、不要勾快速部署。
42. error NETSDK1181: 获取包版本时出错: 包“Microsoft.Android.Ref.33”在工作负载清单中不存在。
修改<TargetFrameworks>net8.0-android34.0</TargetFrameworks>
error XA1036: AndroidManifest.xml //uses-sdk/@android:minSdkVersion '28' does not match the $(SupportedOSPlatformVersion) value '24.0' in the project file (if there is no $(SupportedOSPlatformVersion) value in the project file, then a default value has been assumed).
在AndroidManifest.xml,改为<uses-sdk android:minSdkVersion="29" android:targetSdkVersion="34" />
43. Java.Lang.SecurityException: 'com.southgz.FmosMobile.MAUI: One of RECEIVER_EXPORTED or RECEIVER_NOT_EXPORTED should be specified when a receiver isn't being registered exclusively for system broadcasts'
//注册广播时,必须包含“(ActivityFlags)2”
RegisterReceiver(fmosBroadcastReceiver, new IntentFilter(FmosBroadcastReceiver.Action), (ActivityFlags)2);
45. 如果不允许再公共目录的download目录下存储,需要:
只要在AndroidManifest.xml中的application标签中添加
android:requestLegacyExternalStorage="true"
46. 发布出现“ android存档无效 不是.apk文件 ”, 要重新生成项目再试
47. 发布出现“无法存档文件,因为打包进程失败”,需要将项目设为“release”
使用快速部署,不勾
每个ABI生成,不勾
使用增量打包,勾
多dex,不勾
R8代码收缩器,不勾
AOT,Debug不勾,Release勾
LLVM,不勾
启动跟踪,Debug不勾,Release勾
垃圾回收,勾
跟踪修整,Debug不勾,Release勾
重启VS
49. //必须使用注入才行
//var JS = DependencyService.Get<IJSRuntime>(); 这样子拿不到JS实例
[Inject]
public IJSRuntime JS { get; set; } //这样子才可以
50. java.lang.unsatisfiedlinkerror: dlopen failed: library "libmonosgen-2.0.so" not found
修复方法是取消选中 .csproj 文件属性窗口的 Android 选项部分中的“使用共享运行时”。
<EmbedAssembliesIntoApk>False</EmbedAssembliesIntoApk>
51. maui didn't find class "mainapplication" on path: dexpathlist
不要听微软,在MainApplication后面,using加任何标签!!!