android aar项目_介绍如何调试Xamarin.Android的binding项目
背景
Xamarin的開(kāi)發(fā)的一個(gè)無(wú)法避免的弊端就是在很多Android原生態(tài)應(yīng)用中被普遍用到的庫(kù),未必有.NET的實(shí)現(xiàn)版本。這個(gè)問(wèn)題就如同當(dāng)時(shí)微軟WinPhone失敗的原因一樣,在另外兩個(gè)平臺(tái)中非常普遍的應(yīng)用,在WinPhone中沒(méi)有或者開(kāi)發(fā)進(jìn)度緩慢。
Xamarin為了解決這個(gè)問(wèn)題,在Android和iOS的平臺(tái)下都設(shè)計(jì)了一個(gè)叫做binding library的項(xiàng)目類型,這個(gè)項(xiàng)目類型就是為了將user找到的任何已經(jīng)發(fā)布成類庫(kù)的jar包等,或者iOS下的objective C的類庫(kù)包轉(zhuǎn)換成一個(gè)DLL文件。
本文主要講述的是Android端的關(guān)于jar包的binding。Android還有一種aar包,這種包是帶有resource內(nèi)容的jar包,所以通常理解上來(lái)說(shuō)原理應(yīng)該是一樣的。
內(nèi)容
本文我主要分兩部分,第一部分是通過(guò)一個(gè)微軟資深員工寫(xiě)的troubleshooting的文檔來(lái)做一個(gè)整理歸納,以便用于以后的參考。第二部分是我自己實(shí)際做過(guò)的troubleshooting中我寫(xiě)過(guò)的一些東西,以及對(duì)照第一部分中知識(shí)點(diǎn)中的原理。
Xamarin.Android binding的troubleshooting技巧與原理
1. 調(diào)查 (準(zhǔn)備工作)
在著手開(kāi)始解決這類問(wèn)題之前,我們首先要安裝和準(zhǔn)備一些工具,以下是一些有用的工具:
Enable Diagonostic MSBuild Output (Instructions
Java Decompiler (http://jd.benow.ca
.NET Decompiler (https://www.jetbrains.com/decompiler/
Binding SDK Documentation
Android API Level Documentation
Optional: Beyond Compare (Or a similar tool for comparing files)
當(dāng)準(zhǔn)備好這些工具后,我們做以下步驟來(lái)預(yù)調(diào)查一下這個(gè)問(wèn)題。
Build發(fā)生問(wèn)題的binding project
Build完會(huì)得到full的Diagnostic Build log
根據(jù)Diagnostic Build log來(lái)檢查錯(cuò)誤
這個(gè)時(shí)候我們可以暫時(shí)先不看非常具體的錯(cuò)誤信息等,我們要做的是先看一下出錯(cuò)的library的一些基本信息。這個(gè)是因?yàn)楹芏嗲闆r下,我們binding會(huì)build成功,但是其實(shí)會(huì)缺少類型或者接口等,導(dǎo)致該DLL并無(wú)法被正常使用。我遇到過(guò)的最極端的情況是,build成功但是沒(méi)有類生成。
首先,我們要decompiler這個(gè)Android的類庫(kù)。
如果是一個(gè).jar文件,直接拖曳或者在Java Decompiler中打開(kāi)
如果是一個(gè).aar文件,先extract/unzip這個(gè)文件,并且找到classes.jar文件,再在Java Decompiler中打開(kāi)
得到反編譯信息之后,我們?nèi)z查這個(gè)類庫(kù)本身的時(shí)候,可以從以下幾個(gè)方面著手,并且看這些方面與我們Diagnostic output中得到的信息有沒(méi)有關(guān)系。
是否有任何具有被混淆(obfuscation)特征的類?(只有小寫(xiě)字母/數(shù)字/$)例:a.class / a$.class
是否有帶有import語(yǔ)句的類庫(kù)并沒(méi)有被我們的binding項(xiàng)目引用?
Binding SDK使用到的依賴項(xiàng)(dependencies)的版本是什么?
這個(gè).jar/.aar所支持的Android API level是多少?
這個(gè).jar/.aar是由什么版本的Java SDK編譯的?
2. 修復(fù)問(wèn)題
選擇適合的AndroidClassParser
Xamarin一共有兩種AndroidClassParser可供binding項(xiàng)目使用:
jar2xml?使用Java reflection來(lái)從一個(gè).jar文件中提取類型和成員
class-parse?直接解析Java字節(jié)碼
設(shè)置方法:可以在csproj文件中設(shè)置相應(yīng)需要使用的AndroidClassParser
i.e.
class-parse?- 開(kāi)啟Class Parse
jar2xml?- 開(kāi)啟jar2xml
注意:默認(rèn)的方式是jar2xml。但是,根據(jù)我的測(cè)試,你無(wú)法直接從項(xiàng)目屬性中去設(shè)置,因?yàn)橐粋€(gè)默認(rèn)的項(xiàng)目是用jar2xml,但是項(xiàng)目屬性中反而會(huì)寫(xiě)是class-parse,如圖:
我的做法是使用Notepad++打開(kāi).csproj文件,并且在項(xiàng)目的PropertyGroup中添加上述的設(shè)置命令。如圖:
官方介紹文檔:https://docs.microsoft.com/en-us/xamarin/android/deploy-test/building-apps/build-process#binding-project-build-properties
調(diào)查api.xml文件
該文件在你第一次build完binding項(xiàng)目就會(huì)產(chǎn)生,通常的路徑是Binding項(xiàng)目的obj\Debug目錄下。這個(gè)文件反應(yīng)了Xamarin binding project是怎么來(lái)解析這個(gè)jar包的,從這個(gè)當(dāng)中我們可以很大程度上分析出,現(xiàn)在生成的DLL已經(jīng)有了什么,并且缺少什么。同樣,即便是已經(jīng)成功解析的類型,也可以給我們一個(gè)參考,關(guān)于其他丟失的類型可以怎么改啊,怎么添加啊等等。
下面是一些常見(jiàn)情況的分析。
缺少引用(Missing Reference)
這個(gè)問(wèn)題主要就是針對(duì)上述所說(shuō)的一種情況,在這個(gè)需要綁定的jar包中,引用了其他的類庫(kù)作為依賴項(xiàng),并且我們手中只有這個(gè)jar包,沒(méi)有填寫(xiě)其依賴項(xiàng)的情況。
如果缺少的引用是一個(gè)NuGet的類庫(kù),直接在Binding項(xiàng)目中添加這個(gè)NuGet的包即可。例如Android的那些Support的libraries等等。
如果引用的是其他的第三方j(luò)ar包或者aar包,將他們添加到binding項(xiàng)目的Jars文件夾下,并且設(shè)置相應(yīng)的文件屬性類型為ReferenceJar,?EmbeddedReferenceJar或者LibraryProjectZip。
Java library is required
如果收到錯(cuò)誤代碼at least one Java library is required即使你添加了一個(gè)jar包。
可能的原因
這個(gè)問(wèn)題很可能是因?yàn)樘砑恿薺ar包后直接build,忘記設(shè)置該jar包的build action,binding generator不會(huì)去猜應(yīng)該使用EmbeddedJar還是用其他方式,所以需要手動(dòng)設(shè)定。
Java Version Mismatch (Java版本不匹配)
有時(shí)候類型壓根沒(méi)有生成,或者發(fā)生unexpected的crash的時(shí)候,可能是由于我們?cè)赬amarin.Android binding項(xiàng)目中使用的Java SDK版本要比當(dāng)時(shí)編譯這個(gè)jar包的版本要更新或者更低。我們需要確保的是Java SDK的版本至少兼容,比如說(shuō),這個(gè)jar包是用Java SDK 8的161版本編譯的,那我們使用Java SDK 8的171是可以的我認(rèn)為,但是如果你使用的是Java SDK 7或者9,那就不行了。
認(rèn)識(shí)和修改Metadata.xml文件方法 (重中之重)
接下去是我們重中之重的話題。之所以我們會(huì)需要這篇文檔,是因?yàn)樵谵D(zhuǎn)換jar包到DLL的過(guò)程中,由于Java語(yǔ)言和C#語(yǔ)言本身的不同性,加上寫(xiě)jar包的人可能也只是一個(gè)帶有自己編碼習(xí)慣的程序員,我們?cè)诮馕鰆ar包的過(guò)程中會(huì)需要改變一些解析出來(lái)的屬性。
如同我們剛才說(shuō)的,api.xml是我們修改Metadata.xml文件的依據(jù),因?yàn)樗嬖V我們目前binding項(xiàng)目是怎么解析這個(gè)jar包的。每當(dāng)我們?cè)黾右粭l設(shè)置語(yǔ)句在Metadata.xml文件中,build之后,api.xml文件會(huì)發(fā)生相應(yīng)的變化。
首先介紹一下修改Metadata.xml文件我們必須要知道的知識(shí)。
常用路徑(Common Path)
這個(gè)路徑表示我們?nèi)绾味ㄎ坏絘pi.xml中的某個(gè)類型,接口,方法,甚至方法的參數(shù)。要知道,我們是可以任意修改當(dāng)前binding項(xiàng)目解析這個(gè)jar包的結(jié)果的。
/interface?EX:?/interface[@name='AuthListener']
/class?EX:?/class[@name='MapView']
/method?EX:?/method[@name='setTileSource']
/method(with parameters)?EX:?/method[@name='OnCreate' and count(parameter)=2 and parameter[1][@type='com.my.CustomActivity'] and parameter[2][@type='android.os.Bundle']]
/parameter?EX:?/parameter[@name='p0']
/parameter(with type)?EX:?/parameter[1][@type='com.my.CustomActivity']
常用名稱(Common name)
這個(gè)是和上面的常用路徑一起使用的,當(dāng)使用路徑定位到api.xml中的元素之后,你要修改的任何屬性值就是用接下來(lái)的這些來(lái)定義并且賦值。
name="managedType"?EX:?Java.Lang.Object
name="obfuscated"?- Changes the obfuscation?EX:?true?/?false
name="managedName"?- Changes the managed name?EX:?MyCSharpName
name="propertyName"?- Changes the property name?EX:?MyPropertyName
name="managedReturn"?- Changes the managed return type?EX:?Java.Lang.Object
name="argsType"?- changes the argument type?EX:?MyCustomErrorEventArgs
name="sender"?- Changes which parameter of a method should be the sender parameter when it’s mapped to an event?EX:?true?/?false
name="eventName"?- Changes the event name?EX:?MyEventName
丟失類型 / 混淆類型 (Missing Types / Obfuscated Types)
當(dāng)我們看到j(luò)ar包或者aar包中有混淆類型的時(shí)候,我們必須要unobfuscate,這樣我們的binding generator才可以正確生成相應(yīng)的C#類型。
語(yǔ)法:
| 1 | <attr?path="api/package[@name='{package_name}']/class[@name='{name}']"?name="obfuscated">falseattr> |
重復(fù)命名或標(biāo)準(zhǔn)化命名(Duplicate Names or Normalizing Names)
有時(shí)會(huì)碰到duplicate的managedName或者原本的jar包中的命名不符合你的編程習(xí)慣,你可以使用此attribute來(lái)修改。
注意只有前一種情況會(huì)影響我們生成DLL,這個(gè)情況會(huì)報(bào)錯(cuò),錯(cuò)誤類似于如下截圖:
如果你點(diǎn)擊進(jìn)入這個(gè)錯(cuò)誤,你可能會(huì)發(fā)現(xiàn)后臺(tái)生成的類大概會(huì)變成如下的樣子:
解決方法:
| 1 | <attr?path="/api/package[@name='{package_name}']/class[@name='{name}']"?name="managedName">NewManagedNameattr> |
類可見(jiàn)性(Class Visibility)
Binding Generator不會(huì)生成non-public的類或者派生類。通常情況下只要將類的可見(jiàn)性變成public就可以修復(fù)這個(gè)問(wèn)題。
語(yǔ)法:
| 1 | <attr?path="/api/package[@name='{package_name}']/class[@name='{name}']"?name="visibility">publicattr> |
重復(fù)的自定義EventArgs類型 (Duplicate custom EventArgs types)
這個(gè)類型的問(wèn)題會(huì)導(dǎo)致build錯(cuò)誤。你會(huì)看到類似如下的錯(cuò)誤信息:
| 1 | `error CS0102: The type `Com.Google.Ads.Mediation.DismissScreenEventArgs' already contains a definition for `p0'` |
可能的原因
這個(gè)的最大可能的原因是有一些Interface會(huì)有一些共同的事件(Event),并且很可能這些事件的命名是一樣的。在Java中會(huì)自動(dòng)幫忙處理這些命名,但是binding generator不會(huì)。
假設(shè)現(xiàn)在我們有以下兩個(gè)Java的Interface,分別為MediationBannerListener以及MediationInserstitialListener,他們各自擁有自己的onDismissScreen方法并且方法參數(shù)命名為p0,這時(shí)binding generator會(huì)給這兩個(gè)接口同時(shí)創(chuàng)建DismissScreenEventArgs,這樣就會(huì)最終導(dǎo)致錯(cuò)誤。
| 1234567 | public interface MediationBannerListener{ void onDismissScreen(MediationBannerAdapter p0);}public interface MediationInterstitialListener{ void onDismissScreen(MediationInterstitialAdapter p0);} |
解決方法
這個(gè)本身是一個(gè)by design的情況,Java會(huì)avoid掉長(zhǎng)命名之類的。為了解決這個(gè)問(wèn)題,我們還是修改Metadata.xml,如下:
| 1234567 | <attr?path="api/package[@name='com.google.ads.mediation']/interface[@name='MediationBannerListener']/method[@name='onDismissScreen']"name="argsType">BannerDismissScreenEventArgsattr><attr?path="api/package[@name='com.google.ads.mediation']/interface[@name='MediationInserstitialListener']/method[@name='onDismissScreen']"name="argsType">IntersitionalDismissScreenEventArgsattr> |
類沒(méi)有實(shí)現(xiàn)接口中的方法(Class does not implement interface method)
這個(gè)在C#中的意思就是有一個(gè)接口,里面比如定義了一些方法,有個(gè)類繼承這個(gè)接口,那它就要實(shí)現(xiàn)所有這個(gè)接口中的方法,如果漏掉了,那么編譯器會(huì)報(bào)這個(gè)錯(cuò)。
但是在Binding的項(xiàng)目中,往往我們會(huì)在api.xml中的該類中找到這個(gè)方法,表明確實(shí)是實(shí)現(xiàn)了。這個(gè)時(shí)候也許你看到的error會(huì)是如下的error:
| 1 | 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' |
可能的原因
這個(gè)問(wèn)題的原因其實(shí)是因?yàn)槭褂脜f(xié)變(covariant)返回類型來(lái)綁定Java方法。在上述的錯(cuò)誤代碼中,方法Oauth.Signpost.Http.IHttpRequest.UnWrap()需要返回的是Java.Lang.Object。但是我們的binding generator其實(shí)是認(rèn)為它返回的是HttpURLConnection
解決方法
增加一個(gè)部分類(partial class)的聲明,這個(gè)類為HttpURLConnectionRequestAdapter,并且顯示地實(shí)現(xiàn)IHttpRequest.Unwrap():
1234567 namespace Oauth.Signpost.Basic{ partial class HttpURLConnectionRequestAdapter{ Java.Lang.Object OauthSignpost.Http.IHttpRequest.Unwrap(){ return Unwrap(); } }} 這個(gè)方法的原理其實(shí)就是你原本的binding generator怎么生成這個(gè)方法,我不管,我手動(dòng)提供給你一個(gè)這樣的方法,因?yàn)槭遣糠诸?#xff0c;所以會(huì)build的時(shí)候被合并,相當(dāng)于給這個(gè)方法多一個(gè)簽名。
去除這個(gè)方法的協(xié)變性,也就是說(shuō),通過(guò)改Metadata.xml文件去將這個(gè)方法的return類型給改掉。
1234 <attrpath="api/package[@name='oauth.signpost.basic']/class[@name='HttpURLConnectionRequestAdapter']/method[@name='unwrap']"?name="managedReturn">Java.Lang.Objectattr>
添加類型(Adding Types)
可以使用來(lái)添加任何東西在api.xml文件中。不過(guò)通常來(lái)說(shuō)只會(huì)添加類,改變構(gòu)造函數(shù),或者更改一個(gè)泛型類型。
以下例子是創(chuàng)建一個(gè)類,并且該類具有一個(gè)構(gòu)造函數(shù)和字段。
| 123456 | <add-node?path="api/package[@name='{org.alljoyn.bus}']"> <class?abstract="false"?deprecated="not deprecated"?final="false"?name="AuthListener.AuthRequest"?static="true"?visibility="public"?extends="java.lang.Object"> <constructor?deprecated="not deprecated"?final="false"?name="AuthListener.AuthRequest"?static="false"?type="org.alljoyn.bus.AuthListener.AuthRequest"?visibility="public"?/> <field?name="p0"?type="org.alljoyn.bus.AuthListener.Credentials"?/> class>add-node> |
刪除類型(Removing Types)
刪除一個(gè)類型是非常簡(jiǎn)單的,通常情況就是你知道你不會(huì)使用這個(gè)類。但是需要注意的點(diǎn)是在刪除之前你必須檢查一下其他你需要用到的類中是否有引用這個(gè)類的地方,否則可能會(huì)出錯(cuò)。
語(yǔ)法:
| 1 | <remove-node?path="api/package[@name='{package_name}']/class[@name='{name}']"?/> |
幾個(gè)成功并且Common的Metadata修改的實(shí)例
本來(lái)是想使用自己的,但是發(fā)現(xiàn)GitHub上的這幾個(gè)實(shí)例更好,因?yàn)檫@些實(shí)例中給出了一些常見(jiàn)的問(wèn)題寫(xiě)在注釋中,并且下面根據(jù)這個(gè)問(wèn)題怎么處理。
Binding ADTECH MobileBinding Brother Print SDK for Androidg NeoReaderSDKBinding Java WebSocketBinding Socialize Android SDK使用Java注釋(Using Java Annotations)
確保使用[Export]到相應(yīng)的方法/類/其他。
并且確保你引用了Mono.Android.Export到Xamarin.Android項(xiàng)目
Java.Interop.ExportAttribute Class
3. 一些術(shù)語(yǔ)
這里我就直接拷貝了那個(gè)senior member的內(nèi)容了,英語(yǔ)版看起來(lái)比較專業(yè)。
3. Terms
JNI (Java Native Interface)
In computing, the Java Native Interface (JNI) is a programming framework that enables Java code running in a Java Virtual Machine (JVM) to call and be called by native applications (programs specific to a hardware and operating system platform) and libraries written in other languages such as C, C++ and assembly.
Android Callable Wrappers (ACW)
Android callable wrappers are a JNI bridge that are used whenver the Android runtime needs to invoke managed code.
Managed Callable Wrappers (MCW)
Managed callable wrappers are a JNI bridge that are used whenever managed code needs to invoke Android code and provide support for overriding virtual methods and implementing Java interfaces.
Embedded vs. Non-Embedded
When using a?Build Action?such as?EmbeddedJar?or?EmbeddedReferenceJar, it will embed the respective library into the .apk so it will be available at runtime. Otherwise it is expected that either the Device or the application will provide the .jar at runtime. (I.E. It is already loaded on device or will be provided via a download/etc)
Reference vs. Non-Reference
When using a?Build Action?such as?ReferenceJar?or?EmbeddedReferenceJar, it will not generate Manage Callable Wrappers(ACW) and will not be exposed to the client.
Java is not the same as C#
Because of this limitation, you will need to be aware of the respective generated C# code as there might be certain things that the languages handle differently.
EX: Java -> C#
get/set methods -> properties
fields -> properties * listeners -> events
static nested class -> nested class
inner class -> nested class with an instance constructor
4. Conclusion
Although Xamarin.Android Binding errors might be confusing and the JNI might be intimidating, there is always a few ways to work around the issue at hand.?Documentation:
Xamarin Univeristy Course:
https://university.xamarin.com/classes/track/xamarin-android#and450-binding
總結(jié)
以上是生活随笔為你收集整理的android aar项目_介绍如何调试Xamarin.Android的binding项目的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 日本发布十大畅销相机排行榜 尼康Z f刚
- 下一篇: windows2008开机占用多少内存_