Java 바인딩 메타데이터

Important

현재 Xamarin 플랫폼에서 사용자 지정 바인딩 사용을 조사하고 있습니다. 설문 조사에 참여하여 향후 개발 작업에 대해 알려 주시기 바랍니다.

Xamarin.Android의 C# 코드는 바인딩을 통해 JNI(Java Native Interface)에 지정된 하위 수준 세부 정보를 추상화하는 메커니즘인 Java 라이브러리를 호출합니다. Xamarin.Android는 이러한 바인딩을 생성하는 도구를 제공합니다. 개발자는 이 도구를 통해 메타데이터를 사용하여 바인딩을 만드는 방법을 제어할 수 있으며, 이를 통해 네임스페이스 수정이나 멤버 이름 바꾸기 같은 절차를 수행할 수 있습니다 이 문서에서는 메타데이터가 작동하는 방식을 설명하고, 메타데이터에서 지원하는 특성을 요약하고, 이 메타데이터를 수정하여 바인딩 문제를 해결하는 방법을 설명합니다.

개요

Xamarin.Android Java 바인딩 라이브러리바인딩 생성기라고도 하는 도구의 도움을 받아 기존 Android 라이브러리를 바인딩하는 데 필요한 대부분의 작업을 자동화하려고 시도합니다. Java 라이브러리를 바인딩할 때 Xamarin.Android는 Java 클래스를 검사하고 바인딩할 모든 패키지, 형식 및 멤버 목록을 생성합니다. 이 API 목록은 릴리스 빌드의 경우 {project directory}\obj\Release\api.xml, DEBUG 빌드의 경우 {project directory}\obj\Debug\api.xml찾을 수 있는 XML 파일에 저장됩니다.

Location of the api.xml file in the obj/Debug folder

바인딩 생성기는 api.xml 파일을 지침으로 사용하여 필요한 C# 래퍼 클래스를 생성합니다. 이 XML 파일의 콘텐츠는 Google의 Android 오픈 소스 프로젝트 형식의 변형입니다. 다음 코드 조각은 api.xml 콘텐츠의 예입니다.

<api>
    <package name="android">
        <class abstract="false" deprecated="not deprecated" extends="java.lang.Object"
            extends-generic-aware="java.lang.Object" 
            final="true" 
            name="Manifest" 
            static="false" 
            visibility="public">
            <constructor deprecated="not deprecated" final="false"
                name="Manifest" static="false" type="android.Manifest"
                visibility="public">
            </constructor>
        </class>
...
</api>

이 예제의 api.xmlandroid 패키지에서 java.lang.Object를 확장하는 Manifest라는 클래스를 선언합니다.

대부분의 경우에는 Java API를 ".NET과 비슷한 느낌"으로 만들거나 바인딩 어셈블리를 컴파일할 수 없게 만드는 문제를 해결하려면 사람의 도움이 필요합니다. 예를 들어 Java 패키지 이름을 .NET 네임스페이스로 변경하거나, 클래스 이름을 바꾸거나, 메서드의 반환 형식을 변경해야 할 수 있습니다.

이러한 변경 작업은 api.xml을 직접 수정하는 방법으로 수행되지 않습니다. 그 대신, Java 바인딩 라이브러리 템플릿에서 제공하는 특수 XML 파일에 변경 내용이 기록됩니다. Xamarin.Android 바인딩 어셈블리를 컴파일할 때, 바인딩 생성기는 바인딩 어셈블리를 만들 때 이러한 매핑 파일의 영향을 받습니다.

이러한 XML 매핑 파일은 다음과 같은 프로젝트의 Transforms 폴더에서 찾을 수 있습니다.

  • MetaData.xml – 생성된 바인딩의 네임스페이스 변경과 같이 최종 API를 변경할 수 있습니다.

  • EnumFields.xml – Java int 상수와 C# enums 간의 매핑을 포함합니다.

  • EnumMethods.xml – 메서드 매개 변수를 변경하고 형식을 Java int 상수에서 C# enums 으로 변경할 수 있습니다.

MetaData.xml 파일은 다음과 같이 일반적인 이유로 바인딩을 변경할 수 있으므로 이러한 파일 중 가장 중요합니다.

  • .NET 규칙을 따르도록 네임스페이스, 클래스, 메서드 또는 필드 이름을 변경합니다.

  • 필요 없는 네임스페이스, 클래스, 메서드 또는 필드를 제거합니다.

  • 클래스를 다른 네임스페이스로 이동합니다.

  • 바인딩 디자인이 .NET 프레임워크 패턴을 따르도록 지원 클래스를 추가합니다.

지금부터 Metadata.xml을 자세히 살펴보겠습니다.

Metadata.xml 변환 파일

이미 배운 것처럼, Metadata.xml 파일은 바인딩 생성기에서 바인딩 어셈블리 생성에 영향을 주는 데 사용됩니다. 메타데이터 형식은 XPath 구문을 사용하며 GAPI 메타데이터 가이드에서 설명한 GAPI 메타데이터와 거의 동일합니다. 이 구현은 거의 완전한 XPath 1.0 구현이며, 따라서 1.0 표준의 항목을 지원합니다. 이 파일은 API 파일의 요소나 특성을 변경, 추가, 숨김 또는 이동하는 강력한 XPath 기반 메커니즘입니다. 메타데이터 사양의 모든 규칙 요소에는 규칙을 적용할 노드를 식별하는 경로 특성이 포함됩니다. 규칙은 다음 순서대로 적용됩니다.

  • add-node – 경로 특성에 지정된 노드에 자식 노드를 추가합니다.
  • attr – 경로 특성에 지정된 요소의 특성 값을 설정합니다.
  • remove-node – 지정된 XPath와 일치하는 노드를 제거합니다.

다음은 Metadata.xml 파일의 예입니다.

<metadata>
    <!-- Normalize the namespace for .NET -->
    <attr path="/api/package[@name='com.evernote.android.job']" 
        name="managedName">Evernote.AndroidJob</attr>

    <!-- Don't  need these packages for the Xamarin binding/public API --> 
    <remove-node path="/api/package[@name='com.evernote.android.job.v14']" />
    <remove-node path="/api/package[@name='com.evernote.android.job.v21']" />

    <!-- Change a parameter name from the generic p0 to a more meaningful one. -->
    <attr path="/api/package[@name='com.evernote.android.job']/class[@name='JobManager']/method[@name='forceApi']/parameter[@name='p0']" 
        name="name">api</attr>
</metadata>

다음은 Java API에 가장 일반적으로 사용되는 XPath 요소 목록입니다.

  • interface – Java 인터페이스를 찾는 데 사용됩니다. 예: /interface[@name='AuthListener'].

  • class – 클래스를 찾는 데 사용됩니다. 예: /class[@name='MapView'].

  • method – Java 클래스 또는 인터페이스에서 메서드를 찾는 데 사용됩니다. 예: /class[@name='MapView']/method[@name='setTitleSource'].

  • parameter – 메서드에 대한 매개 변수를 식별합니다. 예: /parameter[@name='p0']

형식 추가

add-node 요소는 api.xml에 새 래퍼 클래스를 추가하도록 Xamarin.Android 바인딩 프로젝트에 지시합니다. 예를 들어 다음 코드 조각은 생성자와 단일 필드를 사용하여 클래스를 만들도록 바인딩 생성기에 지시합니다.

<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>

형식 제거

Java 형식을 무시하고 바인딩하지 않도록 Xamarin.Android 바인딩 생성기에 지시할 수 있습니다. 이 작업은 remove-node XML 요소를 metadata.xml 파일에 추가하여 수행됩니다.

<remove-node path="/api/package[@name='{package_name}']/class[@name='{name}']" />

멤버 이름 바꾸기

Xamarin.Android는 원래 JNI(Java Native Interface) 이름이 필요하기 때문에 api.xml 파일을 직접 편집하여 멤버 이름을 바꿀 수 없습니다. 따라서 //class/@name 특성을 변경하면 안 됩니다. 변경할 경우 바인딩이 작동하지 않습니다.

android.Manifest 형식의 이름을 바꾸려 한다고 가정해 봅시다. 이 작업을 수행하기 위해, 다음과 같이 api.xml 파일을 직접 편집하여 클래스 이름을 바꾸려고 합니다.

<attr path="/api/package[@name='android']/class[@name='Manifest']" 
    name="name">NewName</attr>

그러면 바인딩 생성기가 래퍼 클래스에 대한 다음 C# 코드를 만듭니다.

[Register ("android/NewName")]
public class NewName : Java.Lang.Object { ... }

래퍼 클래스의 이름이 NewName으로 바뀌었지만 원래 Java 형식은 여전히 Manifest입니다. 이제 Xamarin.Android 바인딩 클래스가 더 이상 android.Manifest의 메서드에 액세스할 수 없습니다. 래퍼 클래스는 존재하지 않는 Java 형식에 바인딩됩니다.

래핑된 형식(또는 메서드)의 관리되는 이름을 적절하게 변경하려면 다음 예제와 같이 managedName 특성을 설정해야 합니다.

<attr path="/api/package[@name='android']/class[@name='Manifest']" 
    name="managedName">NewName</attr>

EventArg 래퍼 클래스 이름 바꾸기

Xamarin.Android 바인딩 생성기가 수신기 형식onXXX setter 메서드를 발견하면 Java 기반 수신기 패턴에 .NET 종류의 API를 지원하기 위해 C# 이벤트 및 EventArgs 서브클래스가 생성됩니다. 예를 들어 다음과 같은 Java 클래스 및 메서드를 생각해 볼 수 있습니다.

com.someapp.android.mpa.guidance.NavigationManager.on2DSignNextManuever(NextManueverListener listener);

Xamarin.Android는 setter 메서드에서 on 접두사를 삭제하고, 그 대신 2DSignNextManueverEventArgs 서브클래스 이름의 기준으로 사용합니다. 서브클래스의 이름은 다음과 비슷합니다.

NavigationManager.2DSignNextManueverEventArgs

이것은 올바른 C# 클래스 이름이 아닙니다. 이 문제를 해결하려면 바인딩 작성자가 다음과 같이 argsType 특성을 사용하여 EventArgs 서브클래스에 올바른 C# 이름을 제공해야 합니다.

<attr path="/api/package[@name='com.someapp.android.mpa.guidance']/
    interface[@name='NavigationManager.Listener']/
    method[@name='on2DSignNextManeuver']" 
    name="argsType">NavigationManager.TwoDSignNextManueverEventArgs</attr>

지원되는 특성

다음 섹션에서는 Java API를 변환하는 데 사용되는 몇 가지 특성을 설명합니다.

argsType

이 특성은 Java 수신기를 지원하기 위해 생성되는 EventArg 서브클래스의 이름을 지정할 수 있도록 setter 메서드에 배치됩니다. 자세한 내용은 이 가이드의 뒷부분에 나오는 EventArg 래퍼 클래스 이름 바꾸기 섹션에서 설명하겠습니다.

eventName

이벤트의 이름을 지정합니다. 비워 두면 이벤트를 생성할 수 없습니다. 자세한 내용은 EventArg 래퍼 클래스 이름 바꾸기 섹션에서 설명하겠습니다.

managedName

패키지, 클래스, 메서드 또는 매개 변수의 이름을 변경하는 데 사용됩니다. 예를 들어 Java 클래스 이름 MyClassNewClassName으로 변경하는 방법은 다음과 같습니다.

<attr path="/api/package[@name='com.my.application']/class[@name='MyClass']" 
    name="managedName">NewClassName</attr>

다음 예제에서는 java.lang.object.toString 메서드를 Java.Lang.Object.NewManagedName으로 바꾸는 XPath 식을 보여줍니다.

<attr path="/api/package[@name='java.lang']/class[@name='Object']/method[@name='toString']" 
    name="managedName">NewMethodName</attr>

managedType

managedType은 메서드의 반환 형식을 변경하는 데 사용됩니다. 바인딩 생성기가 Java 메서드의 반환 형식을 잘못 유추하여 컴파일 시간 오류가 발생하는 경우가 있습니다. 이 상황을 해결하는 한 가지 방법은 메서드의 반환 형식을 변경하는 것입니다.

예를 들어 Bindings 생성기는 Java 메서드 de.neom.neoreadersdk.resolution.compareTo() 가 매개 변수를 반환 int 하고 매개 변수로 가져와 Object 야 한다고 생각하므로 오류 메시지 CS0535: 'DE. Neom.Neoreadersdk.Resolution'은 인터페이스 멤버 'Java.Lang.IComparable.CompareTo(Java.Lang.Object)'를 구현하지 않습니다. 다음 코드 조각은 생성된 C# 메서드의 첫 번째 매개 변수 형식을 DE.Neom.Neoreadersdk.Resolution에서 Java.Lang.Object로 변경하는 방법을 보여줍니다.

<attr path="/api/package[@name='de.neom.neoreadersdk']/
    class[@name='Resolution']/
    method[@name='compareTo' and count(parameter)=1 and
    parameter[1][@type='de.neom.neoreadersdk.Resolution']]/
    parameter[1]" name="managedType">Java.Lang.Object</attr> 

managedReturn

메서드의 반환 형식을 변경합니다. 반환 형식을 변경해도 반환 특성은 변경되지 않습니다(반환 특성을 변경하면 JNI 서명에 대해 호환되지 않는 변경이 발생할 수 있음). 다음 예제에서는 append 메서드의 반환 형식이 SpannableStringBuilder에서 IAppendable로 변경됩니다(C#은 공변 반환 형식을 지원하지 않음).

<attr path="/api/package[@name='android.text']/
    class[@name='SpannableStringBuilder']/
    method[@name='append']" 
    name="managedReturn">Java.Lang.IAppendable</attr>

obfuscated

Java 라이브러리를 난독 처리하는 도구는 Xamarin.Android 바인딩 생성기 및 C# 래퍼 클래스 생성 기능을 방해할 수 있습니다. obfuscated 클래스의 특징은 다음과 같습니다.

  • 클래스 이름에는 $(즉, a$.class)가 포함됩니다.
  • 클래스 이름은 완전히 소문자(즉, a.class)로 구성됩니다.

이 코드 조각은 "난독 처리되지 않은" C# 형식을 생성하는 방법의 예입니다.

<attr path="/api/package[@name='{package_name}']/class[@name='{name}']" 
    name="obfuscated">false</attr>

propertyName

이 특성은 관리되는 속성의 이름을 변경하는 데 사용할 수 있습니다.

propertyName을 사용하는 특별한 사례로 Java 클래스에 필드의 getter 메서드만 있는 경우가 있습니다. 이 경우 바인딩 생성기는 .NET에서 권장하지 않는 쓰기 전용 속성을 만들려고 합니다. 다음 코드 조각은 propertyName을 빈 문자열로 설정하여 .NET 속성을 "제거"하는 방법을 보여줍니다.

<attr path="/api/package[@name='org.java_websocket.handshake']/class[@name='HandshakeImpl1Client']/method[@name='setResourceDescriptor' 
    and count(parameter)=1 
    and parameter[1][@type='java.lang.String']]" 
    name="propertyName"></attr>
<attr path="/api/package[@name='org.java_websocket.handshake']/class[@name='HandshakeImpl1Client']/method[@name='getResourceDescriptor' 
    and count(parameter)=0]" 
    name="propertyName"></attr>

setter 및 getter 메서드는 여전히 바인딩 생성기에 의해 생성됩니다.

sender

메서드가 이벤트에 매핑될 때 sender 매개 변수로 사용할 메서드의 매개 변수를 지정합니다. 값은 true, false입니다. 예시:

<attr path="/api/package[@name='android.app']/
    interface[@name='TimePickerDialog.OnTimeSetListener']/
    method[@name='onTimeSet']/
    parameter[@name='view']" 
    name="sender">true</ attr>

visibility

이 특성은 클래스, 메서드 또는 속성의 표시 여부를 변경하는 데 사용됩니다. 예를 들어 다음과 같이 해당 C# 래퍼가 public이 되도록 protected Java 메서드를 승격해야 하는 경우가 있습니다.

<!-- 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>

EnumFields.xml 및 EnumMethods.xml

Android 라이브러리가 정수 상수를 사용하여 상태를 나타내고, 이 상태가 라이브러리의 속성 또는 메서드에 전달되는 경우가 있습니다. 대부분의 경우 C#에서 이러한 정수 상수를 열거형에 바인딩하는 것이 좋습니다. 이 매핑을 쉽게 하려면 바인딩 프로젝트에서 EnumFields.xmlEnumMethods.xml 파일을 사용합니다.

EnumFields.xml을 사용하여 열거형 정의

EnumFields.xml 파일에는 Java int 상수와 C# enums 간의 매핑이 포함되어 있습니다. int 상수 세트에 대해 생성되는 다음 C# 열거형 예제를 살펴보겠습니다.

<mapping jni-class="com/skobbler/ngx/map/realreach/SKRealReachSettings" clr-enum-type="Skobbler.Ngx.Map.RealReach.SKMeasurementUnit">
    <field jni-name="UNIT_SECOND" clr-name="Second" value="0" />
    <field jni-name="UNIT_METER" clr-name="Meter" value="1" />
    <field jni-name="UNIT_MILIWATT_HOURS" clr-name="MilliwattHour" value="2" />
</mapping>

여기서는 Java 클래스 SKRealReachSettings를 사용하고 Skobbler.Ngx.Map.RealReach 네임스페이스에서 SKMeasurementUnit이라는 C# 열거형을 정의했습니다. field 항목은 Java 상수의 이름(예: UNIT_SECOND), 열거형 항목의 이름(예: Second) 및 두 엔터티로 나타내는 정수 값(예: 0)을 정의합니다.

EnumMethods.xml을 사용하여 Getter/Setter 메서드 정의

EnumMethods.xml 파일은 메서드 매개 변수와 반환 형식을 Java int 상수에서 C# enums로 변경할 수 있습니다. 즉, C# 열거형(EnumFields.xml 파일에 정의됨)의 읽기 및 쓰기를 Java int 상수 getset 메서드에 매핑합니다.

위에서 정의한 SKRealReachSettings 열거형을 고려할 때, 다음 EnumMethods.xml 파일은 이 열거형에 대한 getter/setter 메서드를 정의합니다.

<mapping jni-class="com/skobbler/ngx/map/realreach/SKRealReachSettings">
    <method jni-name="getMeasurementUnit" parameter="return" clr-enum-type="Skobbler.Ngx.Map.RealReach.SKMeasurementUnit" />
    <method jni-name="setMeasurementUnit" parameter="measurementUnit" clr-enum-type="Skobbler.Ngx.Map.RealReach.SKMeasurementUnit" />
</mapping>

첫 번째 method 줄은 Java getMeasurementUnit 메서드의 반환 값을 SKMeasurementUnit 열거형에 매핑합니다. 두 번째 method 줄은 setMeasurementUnit의 첫 번째 매개 변수를 동일한 열거형에 매핑합니다.

이러한 변경 내용을 모두 적용하면 Xamarin.Android에서 다음 코드를 사용하여 MeasurementUnit를 설정할 수 있습니다.

realReachSettings.MeasurementUnit = SKMeasurementUnit.Second;

요약

이 문서에서는 Xamarin.Android가 메타데이터를 사용하여 GoogleAOSP 형식에서 API 정의를 변환하는 방법을 설명했습니다. Metadata.xml을 사용하여 수행 가능한 변경 내용을 살펴본 후, 멤버 이름을 바꿀 때 발생하는 제한 사항을 알아보고 각 특성을 언제 사용해야 하는지 설명하는 지원되는 XML 특성 목록을 살펴보았습니다.