疑难解答提示

获取诊断信息

在追踪各种 bug 时,需要在 Xamarin.Android 中查看以下几项信息。 这些设置包括:

  1. MSBuild 诊断输出。
  2. 设备部署日志。
  3. Android 调试日志输出。

MSBuild 诊断输出

MSBuild 诊断输出可以包含与包生成相关的额外信息,也可以包含某些包部署信息。

若要在 Visual Studio 中启用诊断 MSBuild 输出:

  1. 单击 “工具 > 选项...”
  2. 在左侧树视图中,选择“项目”和“解决方案>生成”和“运行”
  3. 在右侧面板中,将“MSBuild 生成输出详细程度”下拉列表设置为“诊断”
  4. 单击 “确定”
  5. 清除并重新生成包。
  6. 诊断输出显示在“输出”面板中。

在 Visual Studio for Mac/OS X 中启用 MSBuild 诊断输出:

  1. 单击 Visual Studio for Mac > 首选项...
  2. 在左侧树视图中,选择“项目>生成”
  3. 在右侧面板中,将“日志详细级别”下拉列表设置为“诊断”
  4. 单击 “确定”
  5. 重启 Visual Studio for Mac
  6. 清除并重新生成包。
  7. 诊断输出在 Errors Pad(View > Pads > Errors )中可见,方法是单击“生成输出”按钮。

设备部署日志

在 Visual Studio 中启用设备部署日志记录功能:

  1. 工具 > 选项...>
  2. 在左侧树视图中,选择 Xamarin > Android 设置
  3. 在右侧面板中,勾选 [X]“扩展调试日志(将 monodroid.log 写入桌面)”复选框
  4. 日志消息写入桌面上的 monodroid.log 文件。

Visual Studio for Mac 始终编写设备部署日志。 FInding 它们稍微困难一些: AndroidUtils 日志文件是每天创建一个部署的时间,例如: AndroidTools-2012-10-24_12-35-45.log

  • 在 Windows 上,日志文件写入 %LOCALAPPDATA%\XamarinStudio-{VERSION}\Logs
  • 在 OS X 上,日志文件写入 $HOME/Library/Logs/XamarinStudio-{VERSION}

Android 调试日志输出

Android 将向 Android 调试日志写入多条消息。 Xamarin.Android 使用 Android 系统属性来控制 Android 调试日志中额外的消息的生成。 可以通过 Android Debug Bridge (adb) 中的setprop 命令设置 Android 系统属性

adb shell setprop PROPERTY_NAME PROPERTY_VALUE

系统在进程启动期间读取系统属性,因此必须在应用程序启动之前设置这些属性,否则更改系统属性后必须重启应用程序。

Xamarin.Android 系统属性

Xamarin.Android 支持以下系统属性:

  • debug.mono.debug:如果非空字符串,则等效于 *mono-debug*

  • debug.mono.env:在初始化 mono 之前,在应用程序启动时要导出的环境变量的管道分隔列表|。 这允许设置控制 mono 日志记录的环境变量。

    注意

    由于该值是“|分隔的,因此该值必须具有额外的引用级别,因为”adb shell“命令将删除一组引号。

    注意

    Android 系统属性值的长度不能超过 92 个字符。

    示例:

    adb shell setprop debug.mono.env "'MONO_LOG_LEVEL=info|MONO_LOG_MASK=asm'"
    
  • debug.mono.log:应将其他消息打印到 Android 调试日志的组件的逗号分隔列表('')。 默认情况下,不设置任何内容。 组件包括:

    • all:打印所有消息
    • gc:打印 GC 相关消息。
    • gref:打印(弱、全局)引用分配和解除分配消息。
    • lref:打印本地引用分配和解除分配消息。

    注意

    这些消息非常详细。 除非确实需要,否则不要启用。

  • debug.mono.trace:允许设置 mono --trace=PROPERTY_VALUE 设置。

删除 binobj

Xamarin.Android 过去曾遭遇过如下情况:

  • 遇到异常生成或运行时错误。
  • CleanRebuild或手动删除 binobj 目录。
  • 问题就解决了。

由于这些问题对开发人员生产效率的影响,我们对解决这些问题给予了大量投入。

如果出现这样的问题:

  1. 在脑海里思考一下。 使项目进入此状态的最后一个操作是什么?
  2. 保存当前的生成日志。 再次尝试生成,并记录诊断生成日志
  3. 提交 bug 报告

在删除 binobj 目录之前,根据需要对它们进行压缩和保存,以备以后诊断使用。 可能只需要Clean Xamarin.Android 应用程序项目就可以让工作恢复正常。

Xamarin.Android 无法解析 System.ValueTuple

发生此错误的原因是与 Visual Studio 不兼容。

  • Visual Studio 2017 Update 1(15.1 或以下版本)仅与 System.ValueTuple NuGet 4.3.0(或以下版本)兼容

  • Visual Studio 2017 Update 2(15.2 或以上版本)仅与 System.ValueTuple NuGet 4.3.1(或以上版本)兼容

请选择与 Visual Studio 2017 安装相对应的正确 System.ValueTuple NuGet。

GC 消息

将 debug.mono.log 系统属性设置为包含 gc 的值,即可查看 GC 组件消息。

每当 GC 执行并提供有关 GC 执行了多少工作的信息时,系统生成 GC 消息:

I/monodroid-gc(12331): GC cleanup summary: 81 objects tested - resurrecting 21.

MONO_LOG_LEVEL 环境变量设置为 debug,即可生成其他 GC 信息(例如计时信息):

adb shell setprop debug.mono.env MONO_LOG_LEVEL=debug

这将产生(大量)额外的 Mono 消息,包括以下三个结果:

D/Mono (15723): GC_BRIDGE num-objects 1 num_hash_entries 81226 sccs size 81223 init 0.00ms df1 285.36ms sort 38.56ms dfs2 50.04ms setup-cb 9.95ms free-data 106.54ms user-cb 20.12ms clenanup 0.05ms links 5523436/5523436/5523096/1 dfs passes 1104 6883/11046605
D/Mono (15723): GC_MINOR: (Nursery full) pause 2.01ms, total 287.45ms, bridge 225.60 promoted 0K major 325184K los 1816K
D/Mono ( 2073): GC_MAJOR: (user request) pause 2.17ms, total 2.47ms, bridge 28.77 major 576K/576K los 0K/16K

GC_BRIDGE 消息中,num-objects 是此次传递考虑使用的桥对象数,num_hash_entries 是此次桥代码调用期间处理的对象数。

GC_MINORGC_MAJOR 消息中,total 是环境暂停的时间量(没有线程正在执行),而 bridge 是桥处理代码(处理 Java VM)所用的时间量。 发生桥处理时,环境未暂停

通常,num_hash_entries 的值越大,bridge 集合花费的时间就越多,收集所花费的 total 时间就越多

全局引用消息

若要启用全局引用 (GREF) 日志记录,debug.mono.log 系统属性必须包含 gref,例如

adb shell setprop debug.mono.log gref

Xamarin.Android 使用 Android 全局引用来提供 Java 实例和相关托管实例之间的映射,就像调用 Java 方法时,需要向 Java 提供 Java 实例一样。

遗憾的是,Android 仿真器一次只允许使用 2000 个全局引用。 硬件具有的全局引用限制高得多,是 52000 个。 在仿真器上运行应用程序时,限制较低会出现问题,因此知道实例的来源非常有用

注意

全局引用计数限于 Xamarin.Android 内部,不包括(也不能包括)由加载到进程中的其他本机库提取出的全局引用。 将全局引用计数用作估计值。

I/monodroid-gref(12405): +g+ grefc 108 gwrefc 0 obj-handle 0x40517468/L -> new-handle 0x40517468/L from    at Java.Lang.Object.RegisterInstance(IJavaObject instance, IntPtr value, JniHandleOwnership transfer)
I/monodroid-gref(12405):    at Java.Lang.Object.SetHandle(IntPtr value, JniHandleOwnership transfer)
I/monodroid-gref(12405):    at Java.Lang.Object..ctor(IntPtr handle, JniHandleOwnership transfer)
I/monodroid-gref(12405):    at Java.Lang.Thread+RunnableImplementor..ctor(System.Action handler, Boolean removable)
I/monodroid-gref(12405):    at Java.Lang.Thread+RunnableImplementor..ctor(System.Action handler)
I/monodroid-gref(12405):    at Android.App.Activity.RunOnUiThread(System.Action action)
I/monodroid-gref(12405):    at Mono.Samples.Hello.HelloActivity.UseLotsOfMemory(Android.Widget.TextView textview)
I/monodroid-gref(12405):    at Mono.Samples.Hello.HelloActivity.<OnCreate>m__3(System.Object o)
I/monodroid-gref(12405): handle 0x40517468; key_handle 0x40517468: Java Type: `mono/java/lang/RunnableImplementor`; MCW type: `Java.Lang.Thread+RunnableImplementor`
I/monodroid-gref(12405): Disposing handle 0x40517468
I/monodroid-gref(12405): -g- grefc 107 gwrefc 0 handle 0x40517468/L from    at Java.Lang.Object.Dispose(System.Object instance, IntPtr handle, IntPtr key_handle, JObjectRefType handle_type)
I/monodroid-gref(12405):    at Java.Lang.Object.Dispose()
I/monodroid-gref(12405):    at Java.Lang.Thread+RunnableImplementor.Run()
I/monodroid-gref(12405):    at Java.Lang.IRunnableInvoker.n_Run(IntPtr jnienv, IntPtr native__this)
I/monodroid-gref(12405):    at System.Object.c200fe6f-ac33-441b-a3a0-47659e3f6750(IntPtr , IntPtr )
I/monodroid-gref(27679): +w+ grefc 1916 gwrefc 296 obj-handle 0x406b2b98/G -> new-handle 0xde68f4bf/W from take_weak_global_ref_jni
I/monodroid-gref(27679): -w- grefc 1915 gwrefc 294 handle 0xde691aaf/W from take_global_ref_jni

有四个重要的消息:

  • 全局引用创建:这些消息行以 +g+ 开头,将为创建代码路径提供堆栈跟踪
  • 全局引用析构:这些消息行以 -g- 开头,可能为全局引用的代码路径处理提供堆栈跟踪。 如果 GC 正在处理 gref,则不会提供堆栈跟踪。
  • 弱全局引用创建:这些行以 +w+ 开头
  • 弱全局引用析构:这些行以 -w- 开头

在所有消息中,grefc 值是 Xamarin.Android 创建的全局引用计数,grefwc 值是 Xamarin.Android 创建的弱全局引用计数。 而 handle 或 obj-handle 值是 JNI 句柄值,“”后面的字符是句柄值的类型:/L 表示本地引用,/G 表示全局引用,/W 表示弱全局引用/

在 GC 过程中,系统将全局引用 (+g+) 转换为弱全局引用(得到 +w+ 和 -g-),逐出 Java 端的 GC,然后检查是否收集了弱全局引用。 如果仍然存在,则围绕弱引用(+g+、-w-)创建一个新的 gref,否则弱引用将被销毁 (-w)。

Java 实例由 MCW 创建和包装

I/monodroid-gref(27679): +g+ grefc 2211 gwrefc 0 obj-handle 0x4066df10/L -> new-handle 0x4066df10/L from ...
I/monodroid-gref(27679): handle 0x4066df10; key_handle 0x4066df10: Java Type: `android/graphics/drawable/TransitionDrawable`; MCW type: `Android.Graphics.Drawables.TransitionDrawable`

正在执行 GC…

I/monodroid-gref(27679): +w+ grefc 1953 gwrefc 259 obj-handle 0x4066df10/G -> new-handle 0xde68f95f/W from take_weak_global_ref_jni
I/monodroid-gref(27679): -g- grefc 1952 gwrefc 259 handle 0x4066df10/G from take_weak_global_ref_jni

对象仍存在,因为句柄 != null

wref 恢复为 gref

I/monodroid-gref(27679): *try_take_global obj=0x4976f080 -> wref=0xde68f95f handle=0x4066df10
I/monodroid-gref(27679): +g+ grefc 1930 gwrefc 39 obj-handle 0xde68f95f/W -> new-handle 0x4066df10/G from take_global_ref_jni
I/monodroid-gref(27679): -w- grefc 1930 gwrefc 38 handle 0xde68f95f/W from take_global_ref_jni

对象不存在,因为句柄 == null

wref 已释放,未创建任何新 gref

I/monodroid-gref(27679): *try_take_global obj=0x4976f080 -> wref=0xde68f95f handle=0x0
I/monodroid-gref(27679): -w- grefc 1914 gwrefc 296 handle 0xde68f95f/W from take_global_ref_jni

有一个有趣的窍门:在运行 Android 4.0 之前版本的目标上,gref 值等于 Java 对象在 Android 运行时内存中的地址。 (也就是说,GC 是一个不移动、保守、收集器,它正在分发对这些对象的直接引用。因此,在 +g+、+w+、-g-、-w- 序列之后,生成的 gref 将具有与原始 gref 值相同的值。 这使检索整个日志变得非常简单。

不过,Android 4.0 具有移动的收集器,不再提供对 Android 运行时 VM 对象的直接引用。 于是,在 a+g+、+w+、-g-、+g+、-w- 序列之后,gref 值将变得不同。 如果对象在经过多次 GC 后仍然存在,它将有几个 gref 值,这就很难确定分配实例的实际位置。

以编程方式查询

可以通过查询 JniRuntime 对象来查询 GREF 和 WREF 计数。

Java.Interop.JniRuntime.CurrentRuntime.GlobalReferenceCount - 全局引用计数

Java.Interop.JniRuntime.CurrentRuntime.WeakGlobalReferenceCount - 弱引用计数

Android 调试日志

Android 调试日志可能会提供有关你看到的任何运行时错误的其他上下文。

浮点性能非常糟糕!

或者说,“我的应用在调试版本中的运行速度比发布版本快 10 倍!”

Xamarin.Android 支持多个设备 ABI:armeabi、armeabi-v7a 和 x86。 可以在“项目属性>应用程序”选项卡>支持的体系结构指定设备 ABIS。

调试版本使用提供所有 ABI 的 Android 包,因此将为目标设备使用最快的 ABI。

发布版本将仅包括“项目属性”选项卡中选择的 API。可以选择多个。

“armeabi”是默认的 ABI,具有最广泛的设备支持。 然而,armeabi 不支持多 CPU 设备和硬件浮点等。 因此,使用 armeabi 发布运行时的应用将与单个核心绑定,并将使用软浮动实现。 这两种情况都会显著降低应用的性能。

如果应用需要良好的浮点性能(例如游戏),你应该启用 armeabi-v7a ABI。 你可能只想支持 armeabi-v7a 运行时,尽管这意味着只支持 armeabi 的旧设备将无法运行你的应用

找不到 Android SDK

Google 提供了 2 个适用于 Windows 的 Android SDK 下载。 如果选择 .exe 安装程序,它将编写注册表项,告诉 Xamarin.Android 安装位置。 如果选择 .zip 文件并自行解压缩,Xamarin.Android 不知道在何处查找 SDK。 可以通过转到“工具>选项 > Xamarin Android”设置,告知 Xamarin.Android > 在 Visual Studio 中 SDK 的位置:

Android SDK Location in Xamarin Android Settings

IDE 不显示目标设备

有时,你会尝试将应用程序部署到设备,但要部署到的设备未显示在“选择设备”对话框中。 Android Debug Bridge 出现异常时,可能会发生这种情况。

若要诊断此问题,请找到 adb 程序,然后运行:

adb devices

如果设备不存在,则需要重启 Android Debug Bridge 服务器,以便可以找到设备:

adb kill-server
adb start-server

HTC 同步软件可能会阻止“adb start-server”正常运行。 如果“adb start-server”命令没有打印出它启动所用的端口,请退出 HTC 同步软件并尝试重启 adb 服务器

无法运行指定的任务可执行文件“keytool”

这意味着,路径不包含 Java SDK 的 bin 目录所在的目录。 检查是否遵循了安装指南中的步骤。

monodroid.exe 或 aresgen.exe 已退出,显示错误代码 1

若要帮助调试此问题,请转到 Visual Studio 并更改 MSBuild 详细级别,为此,请选择: 工具 > 选项 > 项目解决方案 > 生成运行 > MSBuild 项目生成输出详细程度 ,并将此值设置为 Normal

重新生成,并检查 Visual Studio 的输出窗格,该窗格应包含完整错误。

设备上没有足够的存储空间用于部署包

如果不从 Visual Studio 内启动仿真器,就会发生这种情况。 在 Visual Studio 之外启动仿真器时,需要传递 -partition-size 512 选项,例如

emulator -partition-size 512 -avd MonoDroid

确保使用正确的仿真器名,即配置仿真器时使用的名称

安装包时INSTALL_FAILED_INVALID_APK

Android 包名称必须包含句点(“.”)。 编辑包名称,使其包含句点。

  • 在 Visual Studio 中:
    • 右键单击项目 > 属性
    • 单击左侧的“Android 清单”选项卡。
    • 更新“包名称”字段。
      • 如果看到消息“找不到AndroidManifest.xml。 单击以添加一个。“,单击链接,然后更新”包名称“字段。
  • 在 Visual Studio for Mac 中:
    • 右键单击项目 > 选项。
    • 导航到“生成/Android 应用程序”部分。
    • 将包名称字段更改为包含“.”。

安装包时INSTALL_FAILED_MISSING_SHARED_LIBRARY

此上下文中的“共享库”不是本机共享库 (libfoo.so) 文件;而是必须单独安装在目标设备上的库,如 Google Maps

Android 包 <uses-library/> 元素指定需要哪些共享库。 如果目标设备上不存在必需的库(例如//uses-library/@android:required,默认值为 true),则包安装将失败并INSTALL_FAILED_MISSING_SHARED_LIBRARY

若要确定需要哪些共享库,请查看生成的AndroidManifest.xml文件(例如 obj\Debug\android\AndroidManifest.xml),并查找<uses-library/>元素。 <uses-library/> 元素可以手动添加到项目的 Properties\AndroidManifest.xml 文件中,并通过 UsesLibraryAttribute 自定义属性添加。

例如,添加对 Mono.Android.Google Maps.dll 的程序集引用将隐式地为 Google Maps 共享库添加 <uses-library/>

安装包时INSTALL_FAILED_UPDATE_INCOMPATIBLE

Android 包有三个要求:

  • 它们必须包含“.”(请参阅上一项)
  • 它们必须拥有唯一的字符串包名称(也就是在 Android 应用名中所用的反向 TLD 约定,例如为 Chrome 应用使用名称 com.android.chrome)
  • 升级包时,包必须具有相同的签名密钥。

因此,请设想以下情况:

  1. 你将应用生成并部署为调试应用
  2. 更改签名密钥,例如用作发布应用(或因你不喜欢默认提供的调试签名密钥)
  3. 无需先删除应用即可安装应用,例如在 Visual Studio 中调试 > 启动而不调试

发生这种情况时,包安装将失败并出现INSTALL_FAILED_UPDATE_INCOMPATIBLE错误,因为签名密钥时包名称未更改。 Android 调试日志还将包含类似于以下内容的消息:

E/PackageManager(  146): Package [PackageName] signatures do not match the previously installed version; ignoring!

若要修复此错误,请在重新安装之前从设备中完全删除应用程序。

安装包时INSTALL_FAILED_UID_CHANGED

安装 Android 包时,系统会为其分配一个“用户 ID”(UID)。 有时,由于当前未知的原因,在已安装的应用上安装包时,安装将失败,系统提示 INSTALL_FAILED_UID_CHANGED

ERROR [2015-03-23 11:19:01Z]: ANDROID: Deployment failed
Mono.AndroidTools.InstallFailedException: Failure [INSTALL_FAILED_UID_CHANGED]
   at Mono.AndroidTools.Internal.AdbOutputParsing.CheckInstallSuccess(String output, String packageName)
   at Mono.AndroidTools.AndroidDevice.<>c__DisplayClass2c.<InstallPackage>b__2b(Task`1 t)
   at System.Threading.Tasks.ContinuationTaskFromResultTask`1.InnerInvoke()
   at System.Threading.Tasks.Task.Execute()

若要暂时避开此问题,请完全卸载 Android 包,方法是从 Android 目标的 GUI 安装应用,或者使用 adb

$ adb uninstall @PACKAGE_NAME@

请勿 U标准版adb uninstall -k,因为这将保留应用程序数据,从而保留目标设备上的冲突 UID。

无法在设备上启动发布应用

Android 调试日志输出是否包含类似于以下内容的消息:

D/AndroidRuntime( 1710): Shutting down VM
W/dalvikvm( 1710): threadid=1: thread exiting with uncaught exception (group=0xb412f180)
E/AndroidRuntime( 1710): FATAL EXCEPTION: main
E/AndroidRuntime( 1710): java.lang.UnsatisfiedLinkError: Couldn't load monodroid: findLibrary returned null
E/AndroidRuntime( 1710):        at java.lang.Runtime.loadLibrary(Runtime.java:365)

如果是,则有两个可能的原因:

  1. .apk 不提供目标设备支持的 ABI。 例如,.apk 只包含 armeabi-v7a 二进制文件,而目标设备只支持 armeabi。

  2. Android bug。 如果是这种情况,请卸载应用,祈祷好运,然后重新安装应用。

若要修复 (1),请编辑项目选项/属性,然后向支持的 ABI 列表中添加对所需 ABI 的支持。 若要确定需要添加到的 ABI,请对目标设备运行以下 adb 命令:

adb shell getprop ro.product.cpu.abi
adb shell getprop ro.product.cpu.abi2

输出将包含主 ABI(并且根据需要包含辅助 ABI)。

$ adb shell getprop | grep ro.product.cpu
[ro.product.cpu.abi2]: [armeabi]
[ro.product.cpu.abi]: [armeabi-v7a]

未为项目“MyApp.csproj”设置 OutPath 属性

这通常意味着你有一台 HP 计算机,环境变量“平台”已设置为 MCD 或 HPD 之类的内容。 这与通常设置为“任何 CPU”或“x86”的 MSBuild Platform 属性冲突。 在 MSBuild 可以正常运行之前,你需要从计算机中删除此环境变量:

  • 控制面板>系统>高级>环境变量

重启 Visual Studio 或 Visual Studio for Mac 并尝试重新生成。 现在,一切应当会按预期工作。

无法将 java.lang.ClassCastException: mono.android.runtime.JavaObject 强制转换为...

Xamarin.Android 4.x 未能正确地封送嵌套泛型类型。 例如,请考虑使用 SimpleExpandableListAdapter 的以下 C# 代码:

// BAD CODE; DO NOT USE
var groupData = new List<IDictionary<string, object>> () {
        new Dictionary<string, object> {
                { "NAME", "Group 1" },
                { "IS_EVEN", "This group is odd" },
        },
};
var childData = new List<IList<IDictionary<string, object>>> () {
        new List<IDictionary<string, object>> {
                new Dictionary<string, object> {
                        { "NAME", "Child 1" },
                        { "IS_EVEN", "This group is odd" },
                },
        },
};
mAdapter = new SimpleExpandableListAdapter (
        this,
        groupData,
        Android.Resource.Layout.SimpleExpandableListItem1,
        new string[] { "NAME", "IS_EVEN" },
        new int[] { Android.Resource.Id.Text1, Android.Resource.Id.Text2 },
        childData,
        Android.Resource.Layout.SimpleExpandableListItem2,
        new string[] { "NAME", "IS_EVEN" },
        new int[] { Android.Resource.Id.Text1, Android.Resource.Id.Text2 }
);

问题在于,Xamarin.Android 错误地封送嵌套泛型类型。 List<IDictionary<string, object>> 封送至 java.lang.ArrrayList,但 ArrayList 包含 mono.android.runtime.JavaObject 实例(它引用 Dictionary<string, object> 实例),而不是实现 java.util.Map 的实例,从而导致以下异常:

E/AndroidRuntime( 2991): FATAL EXCEPTION: main
E/AndroidRuntime( 2991): java.lang.ClassCastException: mono.android.runtime.JavaObject cannot be cast to java.util.Map
E/AndroidRuntime( 2991):        at android.widget.SimpleExpandableListAdapter.getGroupView(SimpleExpandableListAdapter.java:278)
E/AndroidRuntime( 2991):        at android.widget.ExpandableListConnector.getView(ExpandableListConnector.java:446)
E/AndroidRuntime( 2991):        at android.widget.AbsListView.obtainView(AbsListView.java:2271)
E/AndroidRuntime( 2991):        at android.widget.ListView.makeAndAddView(ListView.java:1769)
E/AndroidRuntime( 2991):        at android.widget.ListView.fillDown(ListView.java:672)
E/AndroidRuntime( 2991):        at android.widget.ListView.fillFromTop(ListView.java:733)
E/AndroidRuntime( 2991):        at android.widget.ListView.layoutChildren(ListView.java:1622)

解决方法是使用提供的 Java 集合类型 ,而不是 System.Collections.Generic “内部”类型的类型。 这将在封送实例时得到适当的 Java 类型。 (为了减少 gref 生存期,以下代码比必要代码复杂得多。可以简化更改原始代码s/List/JavaList/gs/Dictionary/JavaDictionary/g,如果 gref 生存期不担心。

// insert good code here
using (var groupData = new JavaList<IDictionary<string, object>> ()) {
    using (var groupEntry = new JavaDictionary<string, object> ()) {
        groupEntry.Add ("NAME", "Group 1");
        groupEntry.Add ("IS_EVEN", "This group is odd");
        groupData.Add (groupEntry);
    }
    using (var childData = new JavaList<IList<IDictionary<string, object>>> ()) {
        using (var childEntry = new JavaList<IDictionary<string, object>> ())
        using (var childEntryDict = new JavaDictionary<string, object> ()) {
            childEntryDict.Add ("NAME", "Child 1");
            childEntryDict.Add ("IS_EVEN", "This child is odd.");
            childEntry.Add (childEntryDict);
            childData.Add (childEntry);
        }
        mAdapter = new SimpleExpandableListAdapter (
            this,
            groupData,
            Android.Resource.Layout.SimpleExpandableListItem1,
            new string[] { "NAME", "IS_EVEN" },
            new int[] { Android.Resource.Id.Text1, Android.Resource.Id.Text2 },
            childData,
            Android.Resource.Layout.SimpleExpandableListItem2,
            new string[] { "NAME", "IS_EVEN" },
            new int[] { Android.Resource.Id.Text1, Android.Resource.Id.Text2 }
        );
    }
}

意外的 NullReferenceExceptions

有时,Android 调试日志将提及“无法发生”的 NullReferenceException,或者来自 App 死亡前不久的 Mono for Android 运行时代码:

E/mono(15202): Unhandled Exception: System.NullReferenceException: Object reference not set to an instance of an object
E/mono(15202):   at Java.Lang.Object.GetObject (IntPtr handle, System.Type type, Boolean owned)
E/mono(15202):   at Java.Lang.Object._GetObject[IOnTouchListener] (IntPtr handle, Boolean owned)
E/mono(15202):   at Java.Lang.Object.GetObject[IOnTouchListener] (IntPtr handle, Boolean owned)
E/mono(15202):   at Android.Views.View+IOnTouchListenerAdapter.n_OnTouch_Landroid_view_View_Landroid_view_MotionEvent_(IntPtr jnienv, IntPtr native__this, IntPtr native_v, IntPtr native_e)
E/mono(15202):   at (wrapper dynamic-method) object:b039cbb0-15e9-4f47-87ce-442060701362 (intptr,intptr,intptr,intptr)

E/mono    ( 4176): Unhandled Exception:
E/mono    ( 4176): System.NullReferenceException: Object reference not set to an instance of an object
E/mono    ( 4176): at Android.Runtime.JNIEnv.NewString (string)
E/mono    ( 4176): at Android.Util.Log.Info (string,string)

当 Android 运行时决定中止进程时,可能会发生这种情况,原因可能有很多,包括达到目标的 GREF 限制或对 JNI 执行“错误”操作。

若要了解是否属于这种情况,请查看 Android 调试日志是否含有来自进程的消息与以下消息相似:

E/dalvikvm(  123): VM aborting

因全局引用耗尽而中止

Android 运行时的 JNI 层只支持限定数量的 JNI 对象引用在任何给定的时间点有效。 超过此限制时,就会出现中断。

仿真器中的 GREF(全局引用)限制是 2000 个引用,硬件上的限制是 52000 个

在 Android 调试日志中看到如下消息时,就知道创建了太多的 GREF:

D/dalvikvm(  602): GREF has increased to 1801

达到 GREF 限制时,系统将提示如下消息:

D/dalvikvm(  602): GREF has increased to 2001
W/dalvikvm(  602): Last 10 entries in JNI global reference table:
W/dalvikvm(  602):  1991: 0x4057eff8 cls=Landroid/graphics/Point; (20 bytes)
W/dalvikvm(  602):  1992: 0x4057f010 cls=Landroid/graphics/Point; (28 bytes)
W/dalvikvm(  602):  1993: 0x40698e70 cls=Landroid/graphics/Point; (20 bytes)
W/dalvikvm(  602):  1994: 0x40698e88 cls=Landroid/graphics/Point; (20 bytes)
W/dalvikvm(  602):  1995: 0x40698ea0 cls=Landroid/graphics/Point; (28 bytes)
W/dalvikvm(  602):  1996: 0x406981f0 cls=Landroid/graphics/Point; (20 bytes)
W/dalvikvm(  602):  1997: 0x40698208 cls=Landroid/graphics/Point; (20 bytes)
W/dalvikvm(  602):  1998: 0x40698220 cls=Landroid/graphics/Point; (28 bytes)
W/dalvikvm(  602):  1999: 0x406956a8 cls=Landroid/graphics/Point; (20 bytes)
W/dalvikvm(  602):  2000: 0x406956c0 cls=Landroid/graphics/Point; (20 bytes)
W/dalvikvm(  602): JNI global reference table summary (2001 entries):
W/dalvikvm(  602):    51 of Ljava/lang/Class; 164B (41 unique)
W/dalvikvm(  602):    46 of Ljava/lang/Class; 188B (17 unique)
W/dalvikvm(  602):     6 of Ljava/lang/Class; 212B (6 unique)
W/dalvikvm(  602):    11 of Ljava/lang/Class; 236B (7 unique)
W/dalvikvm(  602):     3 of Ljava/lang/Class; 260B (3 unique)
W/dalvikvm(  602):     4 of Ljava/lang/Class; 284B (2 unique)
W/dalvikvm(  602):     8 of Ljava/lang/Class; 308B (6 unique)
W/dalvikvm(  602):     1 of Ljava/lang/Class; 316B
W/dalvikvm(  602):     4 of Ljava/lang/Class; 332B (3 unique)
W/dalvikvm(  602):     1 of Ljava/lang/Class; 356B
W/dalvikvm(  602):     2 of Ljava/lang/Class; 380B (1 unique)
W/dalvikvm(  602):     1 of Ljava/lang/Class; 428B
W/dalvikvm(  602):     1 of Ljava/lang/Class; 452B
W/dalvikvm(  602):     1 of Ljava/lang/Class; 476B
W/dalvikvm(  602):     2 of Ljava/lang/Class; 500B (1 unique)
W/dalvikvm(  602):     1 of Ljava/lang/Class; 548B
W/dalvikvm(  602):     1 of Ljava/lang/Class; 572B
W/dalvikvm(  602):     2 of Ljava/lang/Class; 596B (2 unique)
W/dalvikvm(  602):     1 of Ljava/lang/Class; 692B
W/dalvikvm(  602):     1 of Ljava/lang/Class; 956B
W/dalvikvm(  602):     1 of Ljava/lang/Class; 1004B
W/dalvikvm(  602):     1 of Ljava/lang/Class; 1148B
W/dalvikvm(  602):     2 of Ljava/lang/Class; 1172B (1 unique)
W/dalvikvm(  602):     1 of Ljava/lang/Class; 1316B
W/dalvikvm(  602):     1 of Ljava/lang/Class; 3428B
W/dalvikvm(  602):     1 of Ljava/lang/Class; 3452B
W/dalvikvm(  602):     1 of Ljava/lang/String; 28B
W/dalvikvm(  602):     2 of Ldalvik/system/VMRuntime; 12B (1 unique)
W/dalvikvm(  602):    10 of Ljava/lang/ref/WeakReference; 28B (10 unique)
W/dalvikvm(  602):     1 of Ldalvik/system/PathClassLoader; 44B
W/dalvikvm(  602):  1553 of Landroid/graphics/Point; 20B (1553 unique)
W/dalvikvm(  602):   261 of Landroid/graphics/Point; 28B (261 unique)
W/dalvikvm(  602):     1 of Landroid/view/MotionEvent; 100B
W/dalvikvm(  602):     1 of Landroid/app/ActivityThread$ApplicationThread; 28B
W/dalvikvm(  602):     1 of Landroid/content/ContentProvider$Transport; 28B
W/dalvikvm(  602):     1 of Landroid/view/Surface$CompatibleCanvas; 44B
W/dalvikvm(  602):     1 of Landroid/view/inputmethod/InputMethodManager$ControlledInputConnectionWrapper; 36B
W/dalvikvm(  602):     1 of Landroid/view/ViewRoot$1; 12B
W/dalvikvm(  602):     1 of Landroid/view/ViewRoot$W; 28B
W/dalvikvm(  602):     1 of Landroid/view/inputmethod/InputMethodManager$1; 28B
W/dalvikvm(  602):     1 of Landroid/view/accessibility/AccessibilityManager$1; 28B
W/dalvikvm(  602):     1 of Landroid/widget/LinearLayout$LayoutParams; 44B
W/dalvikvm(  602):     1 of Landroid/widget/LinearLayout; 332B
W/dalvikvm(  602):     2 of Lorg/apache/harmony/xnet/provider/jsse/TrustManagerImpl; 28B (1 unique)
W/dalvikvm(  602):     1 of Landroid/view/SurfaceView$MyWindow; 36B
W/dalvikvm(  602):     1 of Ltouchtest/RenderThread; 92B
W/dalvikvm(  602):     1 of Landroid/view/SurfaceView$3; 12B
W/dalvikvm(  602):     1 of Ltouchtest/DrawingView; 412B
W/dalvikvm(  602):     1 of Ltouchtest/Activity1; 180B
W/dalvikvm(  602): Memory held directly by tracked refs is 75624 bytes
E/dalvikvm(  602): Excessive JNI global references (2001)
E/dalvikvm(  602): VM aborting

在上面的示例中(顺便说一下,来自 bug 685215),问题在于正在创建过多的 Android.Graphics.Point 实例;有关此特定 bug 的修复列表,请参阅 注释 #2

通常,有用的解决方案是查找分配了太多实例(上述转储中的 Android.Graphics.Point)的类型,然后找到在源代码中创建实例的位置并适当释放它们(以便缩短其 Java 对象生存期)。 这并不总是合适的(#685215是多线程的,所以琐碎的解决方案可以避免 Dispose 调用),但首先需要考虑这一点。

可以启用 GREF 日志记录,查看 GREF 的创建时间以及存在的数量。

因 JNI 类型不匹配而中止

如果手动滚动 JNI 代码,类型有可能匹配错误,例如,如果尝试对不实现 java.lang.Runnable 的类型调用 java.lang.Runnable.run。 出现这种情况时,Android 调试日志中将显示类似的消息:

W/dalvikvm( 123): JNI WARNING: can't call Ljava/Type;;.method on instance of Lanother/java/Type;
W/dalvikvm( 123):              in Lmono/java/lang/RunnableImplementor;.n_run:()V (CallVoidMethodA)
...
E/dalvikvm( 123): VM aborting

动态代码支持

动态代码不能编译

若要在应用程序或库中使用 C# 动态,必须向项目添加System.Core.dll、Microsoft.CSharp.dll和Mono.CSharp.dll。

在发布版本中,动态代码在运行时发生 MissingMethodException。

  • 应用程序项目可能没有对 System.Core.dll、Microsoft.CSharp.dll 或 Mono.CSharp.dll 的引用。 请确保引用了这些程序集。

    • 请记住,动态代码始终会产生费用。 如果需要有效的代码,请考虑不要使用动态代码。
  • 在第一个预览中,排除了这些程序集,除非应用程序代码显式使用每个程序集中的类型。 请查看以下网页,了解变通方法:http://lists.ximian.com/pipermail/mo...il/009798.html

在 x86 设备上使用 AOT+LLVM 生成的项目出现故障

在基于 x86 的设备上部署使用 AOT+LLVM 生成的应用时,可能会看到类似于以下内容的异常错误消息:

Assertion: should not be reached at /Users/.../external/mono/mono/mini/tramp-x86.c:124
Fatal signal 6 (SIGABRT), code -6 in tid 4051 (Xamarin.bug56111)

这是一个已知问题 - 变通方法是禁用 LLVM。