다음을 통해 공유


아키텍처

Xamarin.Android 애플리케이션은 Mono 실행 환경 내에서 실행됩니다. 이 실행 환경은 ART(Android 런타임) 가상 머신과 나란히 실행됩니다. 두 런타임 환경은 모두 Linux 커널 위에서 실행되며 개발자가 기본 시스템에 액세스할 수 있도록 하는 사용자 코드에 다양한 API를 노출합니다. Mono 런타임은 C 언어로 작성됩니다.

시스템, System.IO, System.Net 및 나머지 .NET 클래스 라이브러리를 사용하여 기본 Linux 운영 체제 기능에 액세스할 수 있습니다.

Android에서는 오디오, 그래픽, OpenGL 및 Telephony와 같은 대부분의 시스템 기능을 네이티브 애플리케이션에 직접 사용할 수 없으며 Java.* 네임스페이스 또는 Android.* 네임스페이스 중 하나에 있는 Android 런타임 Java API를 통해서만 노출됩니다. 아키텍처는 다음과 같습니다.

커널 위와 .NET/Java + 바인딩 아래의 Mono 및 ART 다이어그램

Xamarin.Android 개발자는 알고 있는 .NET API(하위 수준 액세스용)로 호출하거나 Android 런타임에서 노출되는 Java API에 대한 브리지를 제공하는 Android 네임스페이스에 노출된 클래스를 사용하여 운영 체제의 다양한 기능에 액세스합니다.

Android 클래스가 Android 런타임 클래스와 통신하는 방법에 대한 자세한 내용은 API 디자인 문서를 참조하세요.

애플리케이션 패키지

Android 애플리케이션 패키지는 파일 확장자를 .apk ZIP 컨테이너입니다. Xamarin.Android 애플리케이션 패키지는 일반 Android 패키지와 동일한 구조 및 레이아웃을 가지며 다음과 같은 추가 사항이 있습니다.

  • 애플리케이션 어셈블리(IL 포함)는 어셈블리 폴더 내에 압축되지 않은 상태로 저장됩니다. 릴리스 빌드 에서 프로세스를 시작하는 동안 .apk 프로세스에 mmap() 가 입력되고 어셈블리가 메모리에서 로드됩니다. 이렇게 하면 실행 전에 어셈블리를 추출할 필요가 없으므로 앱 시작 속도가 빨라집니다.

  • 참고: Assembly.Location 및 Assembly.CodeBase같은 어셈블리 위치 정보는 릴리스 빌드에서 사용할 수 없습니다. 고유 파일 시스템 항목으로 존재하지 않으며 사용 가능한 위치가 없습니다.

  • Mono 런타임을 포함하는 네이티브 라이브러리는 .apk 내에 있습니다. Xamarin.Android 애플리케이션에는 원하는/대상 Android 아키텍처에 대한 네이티브 라이브러리(예: armeabi, armeabi-v7a, x86)가 포함되어야 합니다. Xamarin.Android 애플리케이션은 적절한 런타임 라이브러리를 포함하지 않는 한 플랫폼에서 실행할 수 없습니다.

Xamarin.Android 애플리케이션에는 Android에서 관리 코드를 호출할 수 있도록 하는 Android 호출 가능 래퍼도 포함되어 있습니다.

Android 호출 가능 래퍼

관리형 호출 가능 래퍼

관리형 호출 가능 래퍼는 관리 코드가 Android 코드를 호출하고 가상 메서드를 재정의하고 Java 인터페이스를 구현하기 위한 지원을 제공해야 할 때마다 사용되는 JNI 브리지입니다. 전체 Android.* 및 관련 네임스페이스는 .jar 바인딩을 통해 생성된 관리형 호출 가능 래퍼입니다. 관리형 호출 가능 래퍼는 관리되는 형식과 Android 형식 간에 변환하고 JNI를 통해 기본 Android 플랫폼 메서드를 호출하는 역할을 담당합니다.

생성된 각 관리형 호출 가능 래퍼는 Android.Runtime.IJavaObject.Handle 속성을 통해 액세스할 수 있는 Java 전역 참조를 보유합니다 . 전역 참조는 Java 인스턴스와 관리되는 인스턴스 간의 매핑을 제공하는 데 사용됩니다. 전역 참조는 제한된 리소스입니다. 에뮬레이터는 한 번에 2,000개의 전역 참조만 존재할 수 있지만 대부분의 하드웨어에서는 한 번에 52,000개 이상의 전역 참조가 존재할 수 있습니다.

전역 참조가 만들어지고 제거되는 시기를 추적하려면 gref를 포함하도록 debug.mono.log 시스템 속성을 설정할 수 있습니다.

전역 참조는 관리형 호출 가능 래퍼에서 Java.Lang.Object.Dispose()를 호출하여 명시적으로 해제할 수 있습니다. 이렇게 하면 Java 인스턴스와 관리되는 인스턴스 간의 매핑이 제거되고 Java 인스턴스가 수집될 수 있습니다. Java 인스턴스가 관리 코드에서 다시 액세스되는 경우 새 관리형 호출 가능 래퍼가 만들어집니다.

인스턴스를 삭제하면 다른 스레드의 참조에 영향을 주기 때문에 인스턴스를 스레드 간에 실수로 공유할 수 있는 경우 관리형 호출 가능 래퍼를 삭제할 때 주의해야 합니다. 안전을 위해 항상 새 인스턴스를 할당하고 캐시되지 않은 메서드 를 통해 new 또는 메서드에서 할당된 인스턴스만 Dispose() 스레드 간에 우발적인 인스턴스 공유를 발생시킬 수 있습니다.

관리형 호출 가능 래퍼 하위 클래스

관리형 호출 가능 래퍼 서브클래스는 모든 "흥미로운" 애플리케이션별 논리가 있을 수 있는 곳입니다. 여기에는 사용자 지정 Android.App.Activity 하위 클래스(예: 기본 프로젝트 템플릿의 Activity1 형식)가 포함됩니다. (특히 이러한 항목은 RegisterAttribute 사용자 지정 특성 또는 RegisterAttribute.DoNotGenerateAcw를 포함하지 않는 Java.Lang.Object 하위 클래스는 false이며 기본값입니다.)

관리형 호출 가능 래퍼와 마찬가지로 관리형 호출 가능 래퍼 서브클래스에는 Java.Lang.Object.Handle 속성을 통해 액세스할 수 있는 전역 참조도 포함되어 있습니다. 관리형 호출 가능 래퍼와 마찬가지로 Java.Lang.Object.Dispose()를 호출하여 전역 참조를 명시적으로 해제할 수 있습니다. 관리형 호출 가능 래퍼와 달리 인스턴스의 Dispose()-ing은 Java 인스턴스(Android 호출 가능 래퍼의 인스턴스)와 관리되는 인스턴스 간의 매핑을 중단하므로 이러한 인스턴스를 삭제하기 전에 주의해야 합니다.

Java 활성화

Java에서 ACW(Android Callable Wrapper)를 만들면 ACW 생성자가 해당 C# 생성자를 호출합니다. 예를 들어 MainActivity용 ACW에는 MainActivity의 기본 생성자를 호출하는 기본 생성자가 포함됩니다. (이 작업은 다음을 통해 수행됩니다 .ACW 생성자 내에서 TypeManager.Activate() 호출

결과의 다른 생성자 서명은 (IntPtr, JniHandleOwnership) 생성자입니다. Java 개체가 관리 코드에 노출되고 JNI 핸들을 관리하기 위해 관리형 호출 가능 래퍼를 생성해야 할 때마다 (IntPtr, JniHandleOwnership) 생성자가 호출됩니다. 이 작업은 일반적으로 자동으로 수행됩니다.

관리형 호출 가능 래퍼 하위 클래스에서 (IntPtr, JniHandleOwnership) 생성자를 수동으로 제공해야 하는 두 가지 시나리오가 있습니다.

  1. Android.App.Application 은 서브클래스됩니다. 애플리케이션은 특별합니다. 기본 Applicaton 생성자는 호출되지 않으며(IntPtr, JniHandleOwnership) 생성자를 대신 제공해야 합니다.

  2. 기본 클래스 생성자에서 가상 메서드 호출

(2)는 새는 추상화입니다. C#에서와 같이 Java에서 생성자의 가상 메서드 호출은 항상 가장 파생된 메서드 구현을 호출합니다. 예를 들어 TextView(Context, AttributeSet, int) 생성자는 TextView.DefaultMovementMethod 속성으로 바인딩된 가상 메서드 TextView.getDefaultMovementMethod()를 호출합니다. 따라서 LogTextBox 형식이 (1) 서브클래스 TextView, (2) TextView.DefaultMovementMethod를 재정의하고 (3) XML을 통해 해당 클래스의 인스턴스를 활성화하는 경우 ACW 생성자가 실행될 기회가 있기 전에 재정의된 DefaultMovementMethod 속성이 호출되고 C# 생성자가 실행되기 전에 발생합니다.

이는 ACW LogTextBox 인스턴스가 관리 코드를 처음 입력할 때 LogTextView(IntPtr, JniHandleOwnership) 생성자를 통해 인스턴스 LogTextBox를 인스턴스화한 다음 ACW 생성자가 실행되면 동일한 인스턴스에서 LogTextBox(Context, IAttributeSet, int) 생성자를 호출하여 지원됩니다.

이벤트 순서:

  1. 레이아웃 XML은 ContentView로드됩니다.

  2. Android는 Layout 개체 그래프를 인스턴스화하고 LogTextBox용 ACW인 monodroid.apidemo.LogTextBox 인스턴스를 인스턴스화합니다.

  3. monodroid.apidemo.LogTextBox 생성자는 android.widget.TextView 생성자를 실행합니다.

  4. TextView 생성자는 monodroid.apidemo.LogTextBox.getDefaultMovementMethod()를 호출합니다.

  5. monodroid.apidemo.LogTextBox.getDefaultMovementMethod()는 java.Lang.Object.GetObject<TextView>(handle, JniHandleOwnership.DoNotTransfer)를 호출하는 TextView.n_GetDefaultMovementMethod()를 호출하는 LogTextBox.n_getDefaultMovementMethod()를 호출합니다.

  6. Java.Lang.Object.GetObject<TextView>() 는 핸들에 해당하는 C# 인스턴스 가 이미 있는지 확인합니다 . 있는 경우 반환됩니다. 이 시나리오에서는 개체가 없으므로 Object.GetObject<T>() 가 만들어야 합니다.

  7. Object.GetObject<T>()는 LogTextBox(IntPtr, JniHandleOwneship) 생성자를 찾고, 호출하고, 핸들과 만든 인스턴스 간에 매핑을 만들고, 만든 인스턴스를 반환합니다.

  8. TextView.n_GetDefaultMovementMethod()는 LogTextBox.DefaultMovementMethod 속성 getter를 호출합니다.

  9. 컨트롤은 실행을 완료하는 android.widget.TextView 생성자로 돌아갑니다.

  10. monodroid.apidemo.LogTextBox 생성자가 실행되며 TypeManager.Activate()를 호출합니다.

  11. LogTextBox(Context, IAttributeSet, int) 생성자는 (7)에서 만든 동일한 인스턴스에서 실행됩니다.

  12. (IntPtr, JniHandleOwnership) 생성자를 찾을 수 없는 경우 System.MissingMethodException](xref:System.MissingMethodException)이 throw됩니다.

조기 삭제() 호출

JNI 핸들과 해당 C# 인스턴스 간에 매핑이 있습니다. Java.Lang.Object.Dispose()는 이 매핑을 중단합니다. 매핑이 끊어진 후 JNI 핸들이 관리 코드를 입력하면 Java 활성화처럼 표시되고 (IntPtr, JniHandleOwnership) 생성자를 확인하고 호출합니다. 생성자가 없으면 예외가 throw됩니다.

예를 들어 다음과 같은 관리형 호출 가능 래퍼 하위 클래스를 지정합니다.

class ManagedValue : Java.Lang.Object {

    public string Value {get; private set;}

    public ManagedValue (string value)
    {
        Value = value;
    }

    public override string ToString ()
    {
        return string.Format ("[Managed: Value={0}]", Value);
    }
}

인스턴스를 만드는 경우 인스턴스를 Dispose()로 지정하고 관리형 호출 가능 래퍼를 다시 만듭니다.

var list = new JavaList<IJavaObject>();
list.Add (new ManagedValue ("value"));
list [0].Dispose ();
Console.WriteLine (list [0].ToString ());

프로그램은 다음과 같이 중단됩니다.

E/mono    ( 2906): Unhandled Exception: System.NotSupportedException: Unable to activate instance of type Scratch.PrematureDispose.ManagedValue from native handle 4051c8c8 --->
System.MissingMethodException: No constructor found for Scratch.PrematureDispose.ManagedValue::.ctor(System.IntPtr, Android.Runtime.JniHandleOwnership)
E/mono    ( 2906):   at Java.Interop.TypeManager.CreateProxy (System.Type type, IntPtr handle, JniHandleOwnership transfer) [0x00000] in <filename unknown>:0
E/mono    ( 2906):   at Java.Interop.TypeManager.CreateInstance (IntPtr handle, JniHandleOwnership transfer, System.Type targetType) [0x00000] in <filename unknown>:0
E/mono    ( 2906):   --- End of inner exception stack trace ---
E/mono    ( 2906):   at Java.Interop.TypeManager.CreateInstance (IntPtr handle, JniHandleOwnership transfer, System.Type targetType) [0x00000] in <filename unknown>:0
E/mono    ( 2906):   at Java.Lang.Object.GetObject (IntPtr handle, JniHandleOwnership transfer, System.Type type) [0x00000] in <filename unknown>:0
E/mono    ( 2906):   at Java.Lang.Object._GetObject[IJavaObject] (IntPtr handle, JniHandleOwnership transfer) [0x00000

하위 클래스에 (IntPtr, JniHandleOwnership) 생성자가 포함되어 있으면 형식의 새 인스턴스가 만들어집니다. 따라서 인스턴스는 새 인스턴스이기 때문에 모든 인스턴스 데이터를 "손실"하는 것처럼 보입니다. 값은 null입니다.

I/mono-stdout( 2993): [Managed: Value=]

Java 개체가 더 이상 사용되지 않거나 서브클래스에 인스턴스 데이터가 없고(IntPtr, JniHandleOwnership) 생성자가 제공된 경우 관리형 호출 가능 래퍼 서브클래스의 Dispose()제공됩니다.

애플리케이션 시작

활동, 서비스 등이 시작되면 Android는 먼저 활동/서비스/등을 호스트하기 위해 실행 중인 프로세스가 이미 있는지 확인합니다. 이러한 프로세스가 없으면 새 프로세스가 만들어지고, AndroidManifest.xml 읽고, /manifest/application/@android:name 특성에 지정된 형식이 로드되고 인스턴스화됩니다. 다음으로 /manifest/application/provider/@android:name 특성 값으로 지정된 모든 형식이 인스턴스화되고 ContentProvider.attachInfo%28) 메서드가 호출됩니다. Xamarin.Android는 모노를 추가하여 이를 연결합니다 . 빌드 프로세스 중에 AndroidManifest.xml MonoRuntimeProvider ContentProvider 입니다. 모노입니다 . MonoRuntimeProvider.attachInfo() 메서드는 Mono 런타임을 프로세스에 로드하는 작업을 담당합니다. 이 시점 이전에 Mono를 사용하려는 시도는 실패합니다. (참고: 모노를 초기화하기 전에 애플리케이션 인스턴스가 만들어지기 때문에 Android.App.Application에서 (IntPtr, JniHandleOwnership) 생성자를 제공해야 하는 형식입니다.)

프로세스 초기화가 완료되면 AndroidManifest.xml 시작할 활동/서비스/등의 클래스 이름을 찾기 위해 참조합니다. 예를 들어 /manifest/application/activity/@android:name 특성 은 로드할 활동의 이름을 결정하는 데 사용됩니다. 활동의 경우 이 형식은 android.app.Activity를 상속해야 합니다. 지정된 형식은 Class.forName()(형식이 Java 형식이어야 하므로 Android 호출 가능 래퍼)을 통해 로드된 다음 인스턴스화됩니다. Android 호출 가능 래퍼 인스턴스를 만들면 해당 C# 형식의 인스턴스 만들기가 트리거됩니다. 그러면 Android는 Activity.onCreate(번들)를 호출합니다. 그러면 해당 Activity.OnCreate(번들)가 호출되고 경합이 해제됩니다.