android aar项目_介绍如何调试Xamarin.Android的binding项目
背景
Xamarin的開發的一個無法避免的弊端就是在很多Android原生態應用中被普遍用到的庫,未必有.NET的實現版本。這個問題就如同當時微軟WinPhone失敗的原因一樣,在另外兩個平臺中非常普遍的應用,在WinPhone中沒有或者開發進度緩慢。
Xamarin為了解決這個問題,在Android和iOS的平臺下都設計了一個叫做binding library的項目類型,這個項目類型就是為了將user找到的任何已經發布成類庫的jar包等,或者iOS下的objective C的類庫包轉換成一個DLL文件。
本文主要講述的是Android端的關于jar包的binding。Android還有一種aar包,這種包是帶有resource內容的jar包,所以通常理解上來說原理應該是一樣的。
內容
本文我主要分兩部分,第一部分是通過一個微軟資深員工寫的troubleshooting的文檔來做一個整理歸納,以便用于以后的參考。第二部分是我自己實際做過的troubleshooting中我寫過的一些東西,以及對照第一部分中知識點中的原理。
Xamarin.Android binding的troubleshooting技巧與原理
1. 調查 (準備工作)
在著手開始解決這類問題之前,我們首先要安裝和準備一些工具,以下是一些有用的工具:
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)
當準備好這些工具后,我們做以下步驟來預調查一下這個問題。
Build發生問題的binding project
Build完會得到full的Diagnostic Build log
根據Diagnostic Build log來檢查錯誤
這個時候我們可以暫時先不看非常具體的錯誤信息等,我們要做的是先看一下出錯的library的一些基本信息。這個是因為很多情況下,我們binding會build成功,但是其實會缺少類型或者接口等,導致該DLL并無法被正常使用。我遇到過的最極端的情況是,build成功但是沒有類生成。
首先,我們要decompiler這個Android的類庫。
如果是一個.jar文件,直接拖曳或者在Java Decompiler中打開
如果是一個.aar文件,先extract/unzip這個文件,并且找到classes.jar文件,再在Java Decompiler中打開
得到反編譯信息之后,我們去檢查這個類庫本身的時候,可以從以下幾個方面著手,并且看這些方面與我們Diagnostic output中得到的信息有沒有關系。
是否有任何具有被混淆(obfuscation)特征的類?(只有小寫字母/數字/$)例:a.class / a$.class
是否有帶有import語句的類庫并沒有被我們的binding項目引用?
Binding SDK使用到的依賴項(dependencies)的版本是什么?
這個.jar/.aar所支持的Android API level是多少?
這個.jar/.aar是由什么版本的Java SDK編譯的?
2. 修復問題
選擇適合的AndroidClassParser
Xamarin一共有兩種AndroidClassParser可供binding項目使用:
jar2xml?使用Java reflection來從一個.jar文件中提取類型和成員
class-parse?直接解析Java字節碼
設置方法:可以在csproj文件中設置相應需要使用的AndroidClassParser
i.e.
class-parse?- 開啟Class Parse
jar2xml?- 開啟jar2xml
注意:默認的方式是jar2xml。但是,根據我的測試,你無法直接從項目屬性中去設置,因為一個默認的項目是用jar2xml,但是項目屬性中反而會寫是class-parse,如圖:
我的做法是使用Notepad++打開.csproj文件,并且在項目的PropertyGroup中添加上述的設置命令。如圖:
官方介紹文檔:https://docs.microsoft.com/en-us/xamarin/android/deploy-test/building-apps/build-process#binding-project-build-properties
調查api.xml文件
該文件在你第一次build完binding項目就會產生,通常的路徑是Binding項目的obj\Debug目錄下。這個文件反應了Xamarin binding project是怎么來解析這個jar包的,從這個當中我們可以很大程度上分析出,現在生成的DLL已經有了什么,并且缺少什么。同樣,即便是已經成功解析的類型,也可以給我們一個參考,關于其他丟失的類型可以怎么改啊,怎么添加啊等等。
下面是一些常見情況的分析。
缺少引用(Missing Reference)
這個問題主要就是針對上述所說的一種情況,在這個需要綁定的jar包中,引用了其他的類庫作為依賴項,并且我們手中只有這個jar包,沒有填寫其依賴項的情況。
如果缺少的引用是一個NuGet的類庫,直接在Binding項目中添加這個NuGet的包即可。例如Android的那些Support的libraries等等。
如果引用的是其他的第三方jar包或者aar包,將他們添加到binding項目的Jars文件夾下,并且設置相應的文件屬性類型為ReferenceJar,?EmbeddedReferenceJar或者LibraryProjectZip。
Java library is required
如果收到錯誤代碼at least one Java library is required即使你添加了一個jar包。
可能的原因
這個問題很可能是因為添加了jar包后直接build,忘記設置該jar包的build action,binding generator不會去猜應該使用EmbeddedJar還是用其他方式,所以需要手動設定。
Java Version Mismatch (Java版本不匹配)
有時候類型壓根沒有生成,或者發生unexpected的crash的時候,可能是由于我們在Xamarin.Android binding項目中使用的Java SDK版本要比當時編譯這個jar包的版本要更新或者更低。我們需要確保的是Java SDK的版本至少兼容,比如說,這個jar包是用Java SDK 8的161版本編譯的,那我們使用Java SDK 8的171是可以的我認為,但是如果你使用的是Java SDK 7或者9,那就不行了。
認識和修改Metadata.xml文件方法 (重中之重)
接下去是我們重中之重的話題。之所以我們會需要這篇文檔,是因為在轉換jar包到DLL的過程中,由于Java語言和C#語言本身的不同性,加上寫jar包的人可能也只是一個帶有自己編碼習慣的程序員,我們在解析jar包的過程中會需要改變一些解析出來的屬性。
如同我們剛才說的,api.xml是我們修改Metadata.xml文件的依據,因為它告訴我們目前binding項目是怎么解析這個jar包的。每當我們增加一條設置語句在Metadata.xml文件中,build之后,api.xml文件會發生相應的變化。
首先介紹一下修改Metadata.xml文件我們必須要知道的知識。
常用路徑(Common Path)
這個路徑表示我們如何定位到api.xml中的某個類型,接口,方法,甚至方法的參數。要知道,我們是可以任意修改當前binding項目解析這個jar包的結果的。
/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)
這個是和上面的常用路徑一起使用的,當使用路徑定位到api.xml中的元素之后,你要修改的任何屬性值就是用接下來的這些來定義并且賦值。
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)
當我們看到jar包或者aar包中有混淆類型的時候,我們必須要unobfuscate,這樣我們的binding generator才可以正確生成相應的C#類型。
語法:
| 1 | <attr?path="api/package[@name='{package_name}']/class[@name='{name}']"?name="obfuscated">falseattr> |
重復命名或標準化命名(Duplicate Names or Normalizing Names)
有時會碰到duplicate的managedName或者原本的jar包中的命名不符合你的編程習慣,你可以使用此attribute來修改。
注意只有前一種情況會影響我們生成DLL,這個情況會報錯,錯誤類似于如下截圖:
如果你點擊進入這個錯誤,你可能會發現后臺生成的類大概會變成如下的樣子:
解決方法:
| 1 | <attr?path="/api/package[@name='{package_name}']/class[@name='{name}']"?name="managedName">NewManagedNameattr> |
類可見性(Class Visibility)
Binding Generator不會生成non-public的類或者派生類。通常情況下只要將類的可見性變成public就可以修復這個問題。
語法:
| 1 | <attr?path="/api/package[@name='{package_name}']/class[@name='{name}']"?name="visibility">publicattr> |
重復的自定義EventArgs類型 (Duplicate custom EventArgs types)
這個類型的問題會導致build錯誤。你會看到類似如下的錯誤信息:
| 1 | `error CS0102: The type `Com.Google.Ads.Mediation.DismissScreenEventArgs' already contains a definition for `p0'` |
可能的原因
這個的最大可能的原因是有一些Interface會有一些共同的事件(Event),并且很可能這些事件的命名是一樣的。在Java中會自動幫忙處理這些命名,但是binding generator不會。
假設現在我們有以下兩個Java的Interface,分別為MediationBannerListener以及MediationInserstitialListener,他們各自擁有自己的onDismissScreen方法并且方法參數命名為p0,這時binding generator會給這兩個接口同時創建DismissScreenEventArgs,這樣就會最終導致錯誤。
| 1234567 | public interface MediationBannerListener{ void onDismissScreen(MediationBannerAdapter p0);}public interface MediationInterstitialListener{ void onDismissScreen(MediationInterstitialAdapter p0);} |
解決方法
這個本身是一個by design的情況,Java會avoid掉長命名之類的。為了解決這個問題,我們還是修改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> |
類沒有實現接口中的方法(Class does not implement interface method)
這個在C#中的意思就是有一個接口,里面比如定義了一些方法,有個類繼承這個接口,那它就要實現所有這個接口中的方法,如果漏掉了,那么編譯器會報這個錯。
但是在Binding的項目中,往往我們會在api.xml中的該類中找到這個方法,表明確實是實現了。這個時候也許你看到的error會是如下的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' |
可能的原因
這個問題的原因其實是因為使用協變(covariant)返回類型來綁定Java方法。在上述的錯誤代碼中,方法Oauth.Signpost.Http.IHttpRequest.UnWrap()需要返回的是Java.Lang.Object。但是我們的binding generator其實是認為它返回的是HttpURLConnection
解決方法
增加一個部分類(partial class)的聲明,這個類為HttpURLConnectionRequestAdapter,并且顯示地實現IHttpRequest.Unwrap():
1234567 namespace Oauth.Signpost.Basic{ partial class HttpURLConnectionRequestAdapter{ Java.Lang.Object OauthSignpost.Http.IHttpRequest.Unwrap(){ return Unwrap(); } }} 這個方法的原理其實就是你原本的binding generator怎么生成這個方法,我不管,我手動提供給你一個這樣的方法,因為是部分類,所以會build的時候被合并,相當于給這個方法多一個簽名。
去除這個方法的協變性,也就是說,通過改Metadata.xml文件去將這個方法的return類型給改掉。
1234 <attrpath="api/package[@name='oauth.signpost.basic']/class[@name='HttpURLConnectionRequestAdapter']/method[@name='unwrap']"?name="managedReturn">Java.Lang.Objectattr>
添加類型(Adding Types)
可以使用來添加任何東西在api.xml文件中。不過通常來說只會添加類,改變構造函數,或者更改一個泛型類型。
以下例子是創建一個類,并且該類具有一個構造函數和字段。
| 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)
刪除一個類型是非常簡單的,通常情況就是你知道你不會使用這個類。但是需要注意的點是在刪除之前你必須檢查一下其他你需要用到的類中是否有引用這個類的地方,否則可能會出錯。
語法:
| 1 | <remove-node?path="api/package[@name='{package_name}']/class[@name='{name}']"?/> |
幾個成功并且Common的Metadata修改的實例
本來是想使用自己的,但是發現GitHub上的這幾個實例更好,因為這些實例中給出了一些常見的問題寫在注釋中,并且下面根據這個問題怎么處理。
Binding ADTECH MobileBinding Brother Print SDK for Androidg NeoReaderSDKBinding Java WebSocketBinding Socialize Android SDK使用Java注釋(Using Java Annotations)
確保使用[Export]到相應的方法/類/其他。
并且確保你引用了Mono.Android.Export到Xamarin.Android項目
Java.Interop.ExportAttribute Class
3. 一些術語
這里我就直接拷貝了那個senior member的內容了,英語版看起來比較專業。
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
總結
以上是生活随笔為你收集整理的android aar项目_介绍如何调试Xamarin.Android的binding项目的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 日本发布十大畅销相机排行榜 尼康Z f刚
- 下一篇: 银川对高德打车开出首张 10000 元罚