多数情况下,绑定 Android 库(.aar 或 .jar)文件都绝非易事;通常它需要花费额外的精力来缓解 Java 和 .NET 之间的差异导致的问题。 这些问题将阻止 .NET for Android 绑定 Android 库,并在生成日志中显示为错误消息。 此指南将提供解决这些问题的一些技巧,列出一些较为常见的问题/场景,并提供成功绑定 Android 库的可能的解决方案。
绑定现有 Android 库时,需要注意以下几点:
库 的外部依赖项 – Android 库所需的任何 Java 依赖项都必须通过 NuGet 包或 AndroidLibrary 包含在 .NET for Android 项目中。
Android 库面向 的 Android API 级别 – 无法“降级”Android API 级别;确保 .NET for Android 绑定项目面向与 Android 库相同的 API 级别(或更高)。
提示
绑定工具 GitHub 存储库 Wiki 是一个很好的资源,包含其他故障排除信息,可帮助解决特定情况。
排查 .NET for Android 库绑定问题的第一步是启用 诊断 MSBuild 输出。 启用诊断输出后,请重新生成适用于 Android 绑定项目的 .NET,并检查生成日志以查找有关问题原因的线索。
它还有助于反编译 Android 库,并检查 .NET for Android 尝试绑定的类型和方法。 本指南后面的部分将更详细地介绍这一做法。
反编译 Android 库
检查 Java 类的类和方法可提供有助于绑定库的有用信息。 JD-GUI 是一个图形实用程序,它可以显示 JAR 中包含的类文件的 Java 源代码。
使用 Java 反向编译器打开 .JAR 文件,以反向编译 Android 库。 如果库是 .AAR 文件,Java 源代码将位于 存档文件的classes.jar 条目中。 下面是使用 JD-GUI 分析 Picasso JAR 的示例屏幕截图:
反向编译 Android 库后,查看源代码。 通常情况下,查找以下内容:
具有模糊处理特性的类 - 模糊处理的类的特性包括:
- 类名包括 $,即 a$.class
- 类名完全由小写字母组成,例如:a.class
import未引用库的语句 – 标识未引用的库,并将这些依赖项添加到 .NET for Android 绑定项目中,其中包含来自 NuGet 的相应绑定或 AndroidLibrary 的生成操作。
注意
根据当地法规或发布 Java 库所依托的许可证,反向编译 Java 库可能会被禁止或者受到法律限制。 必要时,先向法律从业者寻求服务,然后再反向编译 Java 库并查看源代码。
检查api.xml
作为生成绑定项目的一部分,适用于 Android 的 .NET 将生成 XML 文件名 obj/Debug/api.xml:
此文件提供 .NET for Android 正在尝试绑定的所有 Java API 的列表。 此文件中的内容有助于确定缺失的类型或方法,以及重复的绑定。 查看此文件的过程乏味并且耗时,但可以提供相关线索,从而确定导致绑定问题的可能原因。 例如,api.xml 可能会显示某个属性返回的类型不正确,或者两个类型的托管名称相同。
已知问题
此部分将列出绑定 Android 库时可能会出现的一些常见错误消息或症状。
问题:生成的输出中缺少 C# 类型。
绑定的 .dll 能够构建但缺少一些 Java 类型,或者由于缺少类型的错误导致生成的 C# 源代码无法构建。
可能的原因:
此错误可能由多种原因导致,如下所示:
要绑定的库可能引用了第二个 Java 库。 若绑定的库的公共 API 使用第二个库中的类型,必须也引用第二个库的托管绑定。
Java 允许从非公共类派生公共类,但 .NET 中不支持此操作。 绑定生成器无法从非公共类生成绑定,因此无法正确生成诸如这些的派生类。 若要修复此问题,请使用 Metadata.xml 中的删除节点删除这些派生类的元数据项,或修复使非公共类成为公共类的元数据。 后面的解决方案会创建绑定从而生成 C# 源,但不应使用非公共类。
例如:
<attr path="/api/package[@name='com.some.package']/class[@name='SomeClass']" name="visibility">public</attr>混淆 Java 库的工具可能会干扰 .NET for Android 绑定生成器及其生成 C# 包装器类的功能。 下面的代码片段显示如何更新 Metadata.xml 以解混淆类名称:
<attr path="/api/package[@name='{package_name}']/class[@name='{name}']" name="obfuscated">false</attr>
问题:参数类型不匹配导致生成的 C# 源无法生成
生成的 C# 源代码无法编译。 重写的方法的参数类型不匹配。
可能的原因:
适用于 Android 的 .NET 包括映射到 C# 绑定中的枚举的各种 Java 字段。 这些可能会导致生成的绑定中的类型不兼容。 若要解决此问题,必须修改绑定生成器创建的方法签名以使用枚举。 有关详细信息,请参阅 “创建枚举”。
问题:重复的自定义 EventArgs 类型
自定义 EventArgs 类型重复导致构建失败。 出现如下错误:
error CS0102: The type `Com.Google.Ads.Mediation.DismissScreenEventArgs' already contains a definition for `p0'
可能的原因:
这是因为,如果事件类型来自多个共享相同名称的方法的接口“侦听器”类型,它们之间会存在一些冲突。 例如,当存在以下示例所示的两个 Java 接口时,生成器同时为 DismissScreenEventArgs 和 MediationBannerListener 生成 MediationInterstitialListener,导致错误。
// Java:
public interface MediationBannerListener {
void onDismissScreen(MediationBannerAdapter p0);
}
public interface MediationInterstitialListener {
void onDismissScreen(MediationInterstitialAdapter p0);
}
这是有意的设计,以避免事件参数类型的名称冗长。 要避免这些冲突,必须完成一些数据转换。 编辑 Transforms\Metadata.xml,并在其中的一个接口(或接口方法)中添加 属性:argsType
<attr path="/api/package[@name='com.google.ads.mediation']/
interface[@name='MediationBannerListener']/method[@name='onDismissScreen']"
name="argsType">BannerDismissScreenEventArgs</attr>
<attr path="/api/package[@name='com.google.ads.mediation']/
interface[@name='MediationInterstitialListener']/method[@name='onDismissScreen']"
name="argsType">IntersitionalDismissScreenEventArgs</attr>
<attr path="/api/package[@name='android.content']/
interface[@name='DialogInterface.OnClickListener']"
name="argsType">DialogClickEventArgs</attr>
问题:类无法实现接口方法
生成错误消息,提示生成的类无法实现生成的类实现的接口需要的方法。 但是查看生成的代码时,可以看到实现了该方法。
下面是此错误的示例:
obj\Debug\generated\src\Oauth.Signpost.Basic.HttpURLConnectionRequestAdapter.cs(8,23):
error CS0738: 'Oauth.Signpost.Basic.HttpURLConnectionRequestAdapter' does not
implement interface member 'Oauth.Signpost.Http.IHttpRequest.Unwrap()'.
'Oauth.Signpost.Basic.HttpURLConnectionRequestAdapter.Unwrap()' cannot implement
'Oauth.Signpost.Http.IHttpRequest.Unwrap()' because it does not have the matching
return type of 'Java.Lang.Object'
可能的原因:
这是绑定具有协变返回类型的 Java 方法时会发生的问题。 在此示例中,方法 Oauth.Signpost.Http.IHttpRequest.UnWrap() 需要返回 Java.Lang.Object。 但是方法 Oauth.Signpost.Basic.HttpURLConnectionRequestAdapter.UnWrap() 具有返回类型 HttpURLConnection。 可通过两种方法解决此问题:
添加
HttpURLConnectionRequestAdapter的分部类声明,并显式实现IHttpRequest.Unwrap():namespace Oauth.Signpost.Basic { partial class HttpURLConnectionRequestAdapter { Java.Lang.Object OauthSignpost.Http.IHttpRequest.Unwrap() { return Unwrap(); } } }从生成的 C# 代码删除此协变。 这涉及将以下转换添加到 Transforms\Metadata.xml,从而使生成的 C# 代码具有返回类型
Java.Lang.Object:<attr path="/api/package[@name='oauth.signpost.basic']/class[@name='HttpURLConnectionRequestAdapter']/method[@name='unwrap']" name="managedReturn">Java.Lang.Object </attr>
问题:内部类/属性上的名称冲突
继承对象的可见性冲突。
在 Java 中,派生类与其父类的可见性无需相同。 Java 会修复此问题。 在 C# 中,这一点必须是显式的,因此你需要确保层次结构中的所有类拥有适当的可见性。 下面的示例说明了如何将 Java 程序包名称从 com.evernote.android.job 更改为 Evernote.AndroidJob:
<!-- Change the visibility of a class -->
<attr path="/api/package[@name='namespace']/class[@name='ClassName']" name="visibility">public</attr>
<!-- Change the visibility of a method -->
<attr path="/api/package[@name='namespace']/class[@name='ClassName']/method[@name='MethodName']" name="visibility">public</attr>
问题:绑定所需的 .so 库无法加载
一些绑定项目可能还依赖 .so 库中的功能。 .NET for Android 可能无法自动加载 .so 库。 执行包装的 Java 代码时,适用于 Android 的 .NET 将无法进行 JNI 调用,并且错误消息 java.lang.UnsatisfiedLinkError:找不到本机方法: 将在应用程序的 logcat 中显示。
解决此问题的方法是通过调用Java.Lang.JavaSystem.LoadLibrary手动加载.so库。 例如,假设适用于 Android 项目的 .NET 具有具有 EmbeddedNativeLibrary 生成操作的绑定项目中的共享库libpocketsphinx_jni.so,则以下代码片段(在使用共享库之前执行)将加载 .so 库:
Java.Lang.JavaSystem.LoadLibrary("pocketsphinx_jni");