[Hello-NativeAOTFromAndroid] Add NativeAOT+Android sample (#1218)

Context: 2197579478

Commit 21975794 added `samples/Hello-NativeAOTFromJNI`, which
demonstrated the use of NativeAOT to create a native library which
could be loaded and used by a Java application.

What else supports loading native libraries for use by a "Java"
environment?  Android!

Take the core infrastructure from `Hello-NativeAOTFromJNI`, have
the binary output target `linux-bionic-arm64` --
the `$(RuntimeIdentifier)` for .NET using Android's "bionic" libc
while *not* using .NET for Android -- and then use `gradlew` to
package that native library into an Android application.

Add "degrees" of Android and Gradle Integration:

  * `samples/Hello-NativeAOTFromAndroid/app` is an Android project
    with Gradle build scripts.

  * `gradlew :app:processReleaseResources` is executed as a pre-build
    step to get an `R.txt` describing all the Android Resources
    contained within the Android project.  The new
    `<ParseAndroidResources/>` task parses `R.txt` and generates an
    `R.g.cs`, allowing C# code to reference some Android resources,
    such as the Android layout to display.

  * `android.xml` contains a (very!) minimal API description of
    `android.jar`.  It is used in a pre-build step to produce bindings
    of `android.app.Activity` and `android.os.Bundle` (among others),
    allowing us to write a C# subclass of `Activity` and show
    something on-screen.

  * After the C# build, we:
      * use `jcw-gen` to generate Java Callable Wrappers (JCW) of
        `Java.Lang.Object` subclasses,
      * use `jnimarshalmethod-gen` to generate JNI marshal methods
        for methods such as `MainActivity.OnCreate()`, so that
        C# code can override Java methods.

  * As a post-`Publish` step, we copy various artifacts into the
    Android project structure so that they can be packaged into the
    Android `app-release.apk`, then invoke `gradlew assembleRelease`
    to generate `app-release.apk`.

Update `generator` to support using `Java.Base.dll` as a referenced
assembly for binding purposes, in particular by supporting the use
of `[JniTypeSignatureAttribute]` on already bound types.  No usable
bindings of types in `android.xml` could be generated without this.

Update `jcw-gen` to appropriately support
`[JniTypeSignature(GenerateJavaPeer=false)]` when determining the
constructors that a Java Callable Wrapper should contain.  Failure to
do so meant that the JCW for `MainActivity` contained constructors
that shouldn't be there, resulting in `javac` errors.

Update `jcw-gen` to and support `JavaInterop1`-style method overrides.
Failure to do so meant that the JCW for `MainActivity` *didn't*
declare an `onCreate()` method.
This commit is contained in:
Jonathan Pryor 2024-05-08 15:13:54 -04:00 коммит произвёл GitHub
Родитель 4e893bf82b
Коммит 78d59371a9
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
45 изменённых файлов: 1345 добавлений и 4 удалений

Просмотреть файл

@ -0,0 +1,66 @@
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using System;
using System.Linq;
using System.IO;
using System.Collections.Generic;
namespace Java.Interop.BootstrapTasks
{
public class ParseAndroidResources : Task
{
public ITaskItem AndroidResourceFile { get; set; }
public ITaskItem OutputFile { get; set; }
public string DeclaringNamespaceName { get; set; }
public string DeclaringClassName { get; set; }
public override bool Execute ()
{
using (var o = File.CreateText (OutputFile.ItemSpec)) {
o.WriteLine ($"namespace {DeclaringNamespaceName};");
o.WriteLine ();
o.WriteLine ($"partial class {DeclaringClassName} {{");
var resources = ParseAndroidResourceFile (AndroidResourceFile.ItemSpec);
foreach (var declType in resources.Keys.OrderBy (x => x)) {
o.WriteLine ($"\tpublic static class @{declType} {{");
var decls = resources [declType];
foreach (var decl in decls.Keys.OrderBy (x => x)) {
o.WriteLine ($"\t\tpublic const int {decl} = {decls [decl]};");
}
o.WriteLine ("\t}");
}
o.WriteLine ("}");
o.WriteLine ();
}
return !Log.HasLoggedErrors;
}
Dictionary<string, Dictionary<string, string>> ParseAndroidResourceFile (string file)
{
var resources = new Dictionary<string, Dictionary<string, string>> ();
using (var reader = File.OpenText (file)) {
string line;
while ((line = reader.ReadLine ()) != null) {
if (line.StartsWith ("#"))
continue;
var items = line.Split (' ');
if (items.Length != 4)
continue;
var type = items [0];
if (string.Compare (type, "int", StringComparison.Ordinal) != 0)
continue;
var decl = items [1];
var name = items [2];
var value = items [3];
if (!resources.TryGetValue (decl, out var declResources))
resources.Add (decl, declResources = new Dictionary<string, string>());
declResources.Add (name, value);
}
}
return resources;
}
}
}

2
samples/Hello-NativeAOTFromAndroid/.gitignore поставляемый Normal file
Просмотреть файл

@ -0,0 +1,2 @@
.gradle
android.xml.fixed

Просмотреть файл

@ -0,0 +1,49 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>$(DotNetTargetFramework)</TargetFramework>
</PropertyGroup>
<Import Project="..\..\TargetFrameworkDependentValues.props" />
<PropertyGroup>
<RootNamespace>Java.Interop.Samples.NativeAotFromAndroid</RootNamespace>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PublishAot>true</PublishAot>
<PublishAotUsingRuntimePack>true</PublishAotUsingRuntimePack>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<NativeLib>Shared</NativeLib>
<RuntimeIdentifier Condition=" '$(RuntimeIdentifier)' == '' ">linux-bionic-arm64</RuntimeIdentifier>
<!-- Needed for cross-compilation, e.g. build linux-bionic-arm64 from osx-x64 -->
<PlatformTarget>AnyCPU</PlatformTarget>
</PropertyGroup>
<ItemGroup>
<!-- https://github.com/exelix11/SysDVR/blob/master/Client/Client.csproj -->
<!-- Android needs a proper soname property or it will refuse to load the library -->
<LinkerArg Include="-Wl,-soname,lib$(AssemblyName)$(NativeBinaryExt)" />
<TrimmerRootAssembly Include="Hello-NativeAOTFromAndroid" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Java.Interop\Java.Interop.csproj" />
<ProjectReference Include="..\..\src\Java.Runtime.Environment\Java.Runtime.Environment.csproj" />
<ProjectReference Include="..\..\src\Java.Base\Java.Base.csproj" />
<ProjectReference Include="..\..\src\Java.Interop.Export\Java.Interop.Export.csproj" />
<ProjectReference
Include="..\..\tools\jcw-gen\jcw-gen.csproj"
ReferenceOutputAssembly="false"
/>
<ProjectReference
Include="..\..\tools\jnimarshalmethod-gen\Xamarin.Android.Tools.JniMarshalMethodGenerator.csproj"
ReferenceOutputAssembly="false"
/>
<ProjectReference
Include="..\..\tools\generator\generator.csproj"
ReferenceOutputAssembly="false"
/>
</ItemGroup>
<Import Project="Hello-NativeAOTFromAndroid.targets" />
</Project>

Просмотреть файл

@ -0,0 +1,221 @@
<Project>
<UsingTask AssemblyFile="$(MSBuildThisFileDirectory)..\..\bin\Build$(Configuration)\Java.Interop.BootstrapTasks.dll" TaskName="Java.Interop.BootstrapTasks.ParseAndroidResources" />
<PropertyGroup>
<GeneratorPath>$(UtilityOutputFullPath)generator.dll</GeneratorPath>
<_JcwOutputDir>app/src/main/java/my/</_JcwOutputDir>
<_GradleJniLibsDir>app/src/main/jniLibs/arm64-v8a</_GradleJniLibsDir>
<AndroidNdkDirectory Condition=" '$(AndroidNdkDirectory)' == '' ">$(ANDROID_NDK_HOME)</AndroidNdkDirectory>
<AndroidSdkDirectory Condition=" '$(AndroidSdkDirectory)' == '' ">$(ANDROID_HOME)</AndroidSdkDirectory>
</PropertyGroup>
<PropertyGroup>
<_NdkSysrootAbi Condition=" '$(RuntimeIdentifier)' == 'linux-bionic-arm64' ">aarch64-linux-android</_NdkSysrootAbi>
<_NdkClangPrefix Condition=" '$(RuntimeIdentifier)' == 'linux-bionic-arm64' ">aarch64-linux-android21-</_NdkClangPrefix>
<_NdkSysrootAbi Condition=" '$(RuntimeIdentifier)' == 'linux-bionic-x64' ">x86_64-linux-android</_NdkSysrootAbi>
<_NdkClangPrefix Condition=" '$(RuntimeIdentifier)' == 'linux-bionic-x64' ">x86_64-linux-android21-</_NdkClangPrefix>
<_NdkPrebuiltAbi Condition=" '$(NETCoreSdkRuntimeIdentifier)' == 'osx-x64' ">darwin-x86_64</_NdkPrebuiltAbi>
<_NdkSysrootLibDir>$(AndroidNdkDirectory)/toolchains/llvm/prebuilt/$(_NdkPrebuiltAbi)/sysroot/usr/lib/$(_NdkSysrootAbi)</_NdkSysrootLibDir>
<_NdkBinDir>$(AndroidNdkDirectory)/toolchains/llvm/prebuilt/$(_NdkPrebuiltAbi)/bin</_NdkBinDir>
</PropertyGroup>
<PropertyGroup Condition="$(RuntimeIdentifier.StartsWith('linux-bionic'))">
<CppCompilerAndLinker>$(_NdkBinDir)/$(_NdkClangPrefix)clang</CppCompilerAndLinker>
<ObjCopyName>$(_NdkBinDir)/llvm-objcopy</ObjCopyName>
</PropertyGroup>
<ItemGroup Condition="$(RuntimeIdentifier.StartsWith('linux-bionic'))">
<LinkerArg Include="-Wl,--undefined-version" />
</ItemGroup>
<Target Name="_ValidateEnvironment"
BeforeTargets="Build">
<Error
Condition=" '$(AndroidNdkDirectory)' == '' Or !Exists($(AndroidNdkDirectory)) "
Text="Set the %24(AndroidNdkDirectory) MSBuild property or the %24ANDROID_NDK_HOME environment variable to the path of the Android NDK."
/>
<Error
Condition=" !Exists($(_NdkSysrootLibDir))"
Text="NDK 'sysroot' dir `$(_NdkSysrootLibDir)` does not exist. You're on your own."
/>
<Error
Condition=" '$(AndroidSdkDirectory)' == '' Or !Exists($(AndroidSdkDirectory)) "
Text="Set the %24(AndroidSdkDirectory) MSBuild property or the %24ANDROID_HOME environment variable to the path of the Android SDK."
/>
</Target>
<ItemGroup>
<_GenerateAndroidBindingInputs Include="$(GeneratorPath)" />
<_GenerateAndroidBindingInputs Include="$(MSBuildThisFileFullPath)" />
<_GenerateAndroidBindingInputs Include="Transforms\**" />
<_GenerateAndroidBindingInputs Include="$(IntermediateOutputPath)mcw\api.xml" />
</ItemGroup>
<Target Name="_GenerateAndroidBinding"
BeforeTargets="CoreCompile"
Inputs="@(_GenerateAndroidBindingInputs)"
Outputs="$(IntermediateOutputPath)mcw\Hello-NativeAOTFromAndroid.projitems">
<MakeDir Directories="$(IntermediateOutputPath)mcw" />
<PropertyGroup>
<Generator>"$(GeneratorPath)"</Generator>
<_GenFlags>--public --global</_GenFlags>
<_Out>-o "$(IntermediateOutputPath)mcw"</_Out>
<_Codegen>--codegen-target=JavaInterop1</_Codegen>
<_Fixup>--fixup=Transforms/Metadata.xml</_Fixup>
<_Enums1>--preserve-enums --enumflags=Transforms/enumflags --enumfields=Transforms/map.csv --enummethods=Transforms/methodmap.csv</_Enums1>
<_Enums2>--enummetadata=$(IntermediateOutputPath)mcw/enummetadata</_Enums2>
<_Assembly>"--assembly=$(AssemblyName)"</_Assembly>
<_TypeMap>--type-map-report=$(IntermediateOutputPath)mcw/type-mapping.txt</_TypeMap>
<_Api>android.xml</_Api>
<_Dirs>--enumdir=$(IntermediateOutputPath)mcw</_Dirs>
<_FullIntermediateOutputPath>$([System.IO.Path]::GetFullPath('$(IntermediateOutputPath)'))</_FullIntermediateOutputPath>
<_LangFeatures>--lang-features=nullable-reference-types,default-interface-methods,nested-interface-types,interface-constants</_LangFeatures>
</PropertyGroup>
<ItemGroup>
<_RefAsmDir Include="@(ReferencePathWithRefAssemblies->'%(RootDir)%(Directory).'->Distinct())" />
<_Lib Include="@(_RefAsmDir->'-L &quot;%(Identity)&quot;')" />
<_JavaBaseRef Include="@(ReferencePathWithRefAssemblies)"
Condition=" '%(FileName)' == 'Java.Base' "
/>
<_Ref Include="@(_JavaBaseRef->'-r &quot;%(FullPath)&quot;')" />
</ItemGroup>
<ItemGroup>
<!-- I can't find a good way to trim the trailing `\`, so append with `.` so we can sanely quote for $(_Libpath) -->
</ItemGroup>
<Exec
Command="$(DotnetToolPath) $(Generator) $(_GenFlags) $(_ApiLevel) $(_Out) @(_Lib, ' ') @(_Ref, ' ') $(_Codegen) $(_Fixup) $(_Enums1) $(_Enums2) $(_Versions) $(_Annotations) $(_Assembly) $(_TypeMap) $(_LangFeatures) $(_Dirs) $(_Api) $(_WithJavadocXml)"
IgnoreStandardErrorWarningFormat="True"
/>
<ItemGroup>
<Compile Include="$(_FullIntermediateOutputPath)\mcw\**\*.cs" KeepDuplicates="False" />
</ItemGroup>
<XmlPeek
Namespaces="&lt;Namespace Prefix='msbuild' Uri='http://schemas.microsoft.com/developer/msbuild/2003' /&gt;"
XmlInputPath="$(IntermediateOutputPath)mcw\Hello-NativeAOTFromAndroid.projitems"
Query="/msbuild:Project/msbuild:PropertyGroup/msbuild:DefineConstants/text()" >
<Output TaskParameter="Result" PropertyName="_GeneratedDefineConstants" />
</XmlPeek>
<PropertyGroup>
<DefineConstants>$(DefineConstants);$([System.String]::Copy('$(_GeneratedDefineConstants)').Replace ('%24(DefineConstants);', ''))</DefineConstants>
</PropertyGroup>
</Target>
<Target Name="_CreateJavaCallableWrappers"
Condition=" '$(TargetPath)' != '' "
BeforeTargets="_BuildAppApk"
Inputs="$(TargetPath)"
Outputs="$(_JcwOutputDir).stamp">
<RemoveDir Directories="$(_JcwOutputDir)" />
<MakeDir Directories="$(_JcwOutputDir)" />
<ItemGroup>
<!-- I can't find a good way to trim the trailing `\`, so append with `.` so we can sanely quote for $(_Libpath) -->
<_JcwGenRefAsmDirs Include="@(ReferencePathWithRefAssemblies->'%(RootDir)%(Directory).'->Distinct())" />
</ItemGroup>
<PropertyGroup>
<_JcwGen>"$(UtilityOutputFullPath)/jcw-gen.dll"</_JcwGen>
<_Target>--codegen-target JavaInterop1</_Target>
<_Output>-o "$(_JcwOutputDir)"</_Output>
<_Libpath>@(_JcwGenRefAsmDirs->'-L "%(Identity)"', ' ')</_Libpath>
</PropertyGroup>
<Exec Command="$(DotnetToolPath) $(_JcwGen) &quot;$(TargetPath)&quot; $(_Target) $(_Output) $(_Libpath)" />
<Touch Files="$(_JcwOutputDir).stamp" AlwaysCreate="True" />
</Target>
<Target Name="_AddMarshalMethods"
Condition=" '$(TargetPath)' != '' "
Inputs="$(TargetPath)"
Outputs="$(IntermediateOutputPath).added-marshal-methods"
AfterTargets="_CreateJavaCallableWrappers">
<ItemGroup>
<!-- I can't find a good way to trim the trailing `\`, so append with `.` so we can sanely quote for $(_Libpath) -->
<_JnimmRefAsmDirs Include="@(RuntimePackAsset->'%(RootDir)%(Directory).'->Distinct())" />
</ItemGroup>
<PropertyGroup>
<_JnimarshalmethodGen>"$(UtilityOutputFullPath)/jnimarshalmethod-gen.dll"</_JnimarshalmethodGen>
<_Verbosity>-v -v --keeptemp</_Verbosity>
<_Libpath>-L "$(TargetDir)" @(_JnimmRefAsmDirs->'-L "%(Identity)"', ' ')</_Libpath>
<!-- <_Output>-o "$(IntermediateOutputPath)/jonp"</_Output> -->
</PropertyGroup>
<Exec Command="$(DotnetToolPath) $(_JnimarshalmethodGen) &quot;$(TargetPath)&quot; $(_Verbosity) $(_Libpath)" />
<!-- the IlcCompile target uses files from `$(IntermediateOutputPath)`, not `$(TargetPath)`, so… update both? -->
<Copy SourceFiles="$(TargetPath)" DestinationFolder="$(IntermediateOutputPath)" />
<Touch Files="$(IntermediateOutputPath).added-marshal-methods" AlwaysCreate="True" />
</Target>
<ItemGroup>
<_BuildAppApkInput Include="$(MSBuildThisFileFullPath)" />
<_BuildAppApkInput Include="app\src\main\java\**\*.java" />
<_BuildAppApkInput Include="app\src\main\AndroidManifest.xml" />
<_BuildAppApkInput Include="app\**\build.gradle" />
<_BuildAppApkInput Include="$(NativeBinary)" />
<_BuildAppApkInput Include="$(OutputPath)java-interop.jar" />
</ItemGroup>
<PropertyGroup>
<_AfterBuildDependsOnTargets>
_CreateJavaCallableWrappers;
_AddMarshalMethods;
</_AfterBuildDependsOnTargets>
</PropertyGroup>
<Target Name="_AfterBuild"
AfterTargets="Build"
DependsOnTargets="$(_AfterBuildDependsOnTargets)"
/>
<PropertyGroup>
<_GradleRtxtPath>app\build\intermediates\runtime_symbol_list\release\R.txt</_GradleRtxtPath>
</PropertyGroup>
<Target Name="_BuildRtxt"
BeforeTargets="CoreCompile"
Inputs="@(_BuildAppApkInput)"
Outputs="$(_GradleRtxtPath);$(IntermediateOutputPath)R.g.cs">
<Exec
Command="&quot;$(GradleWPath)&quot; $(GradleArgs) :app:processReleaseResources"
EnvironmentVariables="JAVA_HOME=$(JavaSdkDirectory);APP_HOME=$(GradleHome);ANDROID_HOME=$(AndroidSdkDirectory)"
WorkingDirectory="$(MSBuildThisFileDirectory)"
/>
<ParseAndroidResources
AndroidResourceFile="$(_GradleRtxtPath)"
OutputFile="$(IntermediateOutputPath)R.g.cs"
DeclaringNamespaceName="$(RootNamespace)"
DeclaringClassName="R"
/>
<ItemGroup>
<FileWrites Include="$(IntermediateOutputPath)R.g.cs" />
<Compile Include="$(IntermediateOutputPath)R.g.cs" />
</ItemGroup>
</Target>
<Target Name="_BuildAppApk"
AfterTargets="Publish"
Inputs="@(_BuildAppApkInput)"
Outputs="app/build/outputs/apk/release/app-release.apk">
<MakeDir Directories="$(_GradleJniLibsDir);app/lib" />
<ItemGroup>
<_GradleBuildSource Include="$(NativeBinary)" />
<_GradleBuildTarget Include="$(_GradleJniLibsDir)\lib$(AssemblyName)$(NativeBinaryExt)" />
<_GradleBuildSource Include="$(OutputPath)java-interop.jar" />
<_GradleBuildTarget Include="app\lib\java-interop.jar" />
</ItemGroup>
<Copy
SourceFiles="@(_GradleBuildSource)"
DestinationFiles="@(_GradleBuildTarget)"
/>
<Exec
Command="&quot;$(GradleWPath)&quot; $(GradleArgs) assembleRelease > gradle.log"
EnvironmentVariables="JAVA_HOME=$(JavaSdkDirectory);APP_HOME=$(GradleHome);ANDROID_HOME=$(AndroidSdkDirectory)"
WorkingDirectory="$(MSBuildThisFileDirectory)"
/>
<Copy
SourceFiles="app/build/outputs/apk/release/app-release.apk"
DestinationFiles="$(OutputPath)net.dot.jni.helloandroid-Signed.apk"
/>
</Target>
</Project>

Просмотреть файл

@ -0,0 +1,51 @@
using System.Runtime.InteropServices;
using Java.Interop;
namespace Java.Interop.Samples.NativeAotFromAndroid;
static class JavaInteropRuntime
{
static JniRuntime? runtime;
[UnmanagedCallersOnly (EntryPoint="JNI_OnLoad")]
static int JNI_OnLoad (IntPtr vm, IntPtr reserved)
{
try {
AndroidLog.Print (AndroidLogLevel.Info, "JavaInteropRuntime", "JNI_OnLoad()");
LogcatTextWriter.Init ();
return (int) JniVersion.v1_6;
}
catch (Exception e) {
AndroidLog.Print (AndroidLogLevel.Error, "JavaInteropRuntime", $"JNI_OnLoad() failed: {e}");
return 0;
}
}
[UnmanagedCallersOnly (EntryPoint="JNI_OnUnload")]
static void JNI_OnUnload (IntPtr vm, IntPtr reserved)
{
AndroidLog.Print(AndroidLogLevel.Info, "JavaInteropRuntime", "JNI_OnUnload");
runtime?.Dispose ();
}
// symbol name from `$(IntermediateOutputPath)obj/Release/osx-arm64/h-classes/net_dot_jni_hello_JavaInteropRuntime.h`
[UnmanagedCallersOnly (EntryPoint="Java_net_dot_jni_nativeaot_JavaInteropRuntime_init")]
static void init (IntPtr jnienv, IntPtr klass)
{
Console.WriteLine ($"C# init()");
try {
var options = new JreRuntimeOptions {
EnvironmentPointer = jnienv,
TypeManager = new NativeAotTypeManager (),
UseMarshalMemberBuilder = false,
JniGlobalReferenceLogWriter = new LogcatTextWriter (AndroidLogLevel.Debug, "NativeAot:GREF"),
JniLocalReferenceLogWriter = new LogcatTextWriter (AndroidLogLevel.Debug, "NativeAot:LREF"),
};
runtime = options.CreateJreVM ();
}
catch (Exception e) {
AndroidLog.Print (AndroidLogLevel.Error, "JavaInteropRuntime", $"JavaInteropRuntime.init: error: {e}");
}
}
}

Просмотреть файл

@ -0,0 +1,73 @@
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
namespace Java.Interop.Samples.NativeAotFromAndroid;
internal sealed class LogcatTextWriter : TextWriter {
public static void Init ()
{
// This method is a no-op, but it's necessary to ensure the static
// constructor is executed.
}
static LogcatTextWriter ()
{
Console.SetOut (new LogcatTextWriter (AndroidLogLevel.Info));
Console.SetError (new LogcatTextWriter (AndroidLogLevel.Error));
}
AndroidLogLevel Level;
string Tag;
internal LogcatTextWriter (AndroidLogLevel level, string tag = "NativeAotFromAndroid")
{
Level = level;
Tag = tag;
}
public override Encoding Encoding => Encoding.UTF8;
public override string NewLine => "\n";
public override void WriteLine (string? value)
{
if (value == null) {
AndroidLog.Print (Level, Tag, "");
return;
}
ReadOnlySpan<char> span = value;
while (!span.IsEmpty) {
if (span.IndexOf ('\n') is int n && n < 0) {
break;
}
var line = span.Slice (0, n);
AndroidLog.Print (Level, Tag, line.ToString ());
span = span.Slice (n + 1);
}
AndroidLog.Print (Level, Tag, span.ToString ());
}
}
static class AndroidLog {
[DllImport ("log", EntryPoint = "__android_log_print", CallingConvention = CallingConvention.Cdecl)]
private static extern void __android_log_print(AndroidLogLevel level, string? tag, string format, string args, IntPtr ptr);
internal static void Print(AndroidLogLevel level, string? tag, string message) =>
__android_log_print(level, tag, "%s", message, IntPtr.Zero);
}
internal enum AndroidLogLevel
{
Unknown = 0x00,
Default = 0x01,
Verbose = 0x02,
Debug = 0x03,
Info = 0x04,
Warn = 0x05,
Error = 0x06,
Fatal = 0x07,
Silent = 0x08
}

Просмотреть файл

@ -0,0 +1,39 @@
using Java.Interop;
namespace Java.Interop.Samples.NativeAotFromAndroid;
[JniTypeSignature ("my/MainActivity")]
public class MainActivity : Android.App.Activity {
public MainActivity ()
{
Console.WriteLine ("MainActivity..ctor()");
}
protected override void OnCreate (Android.OS.Bundle? savedInstanceState)
{
Console.WriteLine ($"MainActivity.OnCreate(): savedInstanceState? {savedInstanceState != null}");
base.OnCreate (savedInstanceState);
SetContentView (R.layout.activity_main);
PrintGrefInfo ();
}
static void PrintGrefInfo ()
{
var runtime = JniEnvironment.Runtime;
var peers = runtime.ValueManager.GetSurfacedPeers ();
Console.WriteLine ($"Created {runtime.ObjectReferenceManager.GlobalReferenceCount} GREFs; Surfaced {peers.Count} peers");
for (int i = 0; i < peers.Count; ++i) {
Console.WriteLine ($" SurfacedPeers[{i,3}] = {ToString (peers[i])}");
}
}
static string ToString (JniSurfacedPeerInfo peer)
{
if (!peer.SurfacedPeer.TryGetTarget (out var p)) {
return $"JniSurfacedPeerInfo(IdentityHashCode=0x{peer.JniIdentityHashCode:x})";
}
return $"JniSurfacedPeerInfo(PeerReference={p.PeerReference} IdentityHashCode=0x{peer.JniIdentityHashCode:x} Instance.Type={p.GetType ()})";
}
}

Просмотреть файл

@ -0,0 +1,61 @@
using System.Diagnostics.CodeAnalysis;
using Java.Interop;
namespace Java.Interop.Samples.NativeAotFromAndroid;
partial class NativeAotTypeManager : JniRuntime.JniTypeManager {
internal const DynamicallyAccessedMemberTypes Methods = DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods;
internal const DynamicallyAccessedMemberTypes MethodsAndPrivateNested = Methods | DynamicallyAccessedMemberTypes.NonPublicNestedTypes;
Dictionary<string, Type> typeMappings = new () {
["android/app/Activity"] = typeof (Android.App.Activity),
["android/content/Context"] = typeof (Android.Content.Context),
["android/content/ContextWrapper"] = typeof (Android.Content.ContextWrapper),
["android/os/BaseBundle"] = typeof (Android.OS.BaseBundle),
["android/os/Bundle"] = typeof (Android.OS.Bundle),
["android/view/ContextThemeWrapper"] = typeof (Android.View.ContextThemeWrapper),
["my/MainActivity"] = typeof (MainActivity),
};
public override void RegisterNativeMembers (
JniType nativeClass,
[DynamicallyAccessedMembers (MethodsAndPrivateNested)]
Type type,
ReadOnlySpan<char> methods)
{
Console.WriteLine ($"# jonp: RegisterNativeMembers: nativeClass={nativeClass} type=`{type}`");
base.RegisterNativeMembers (nativeClass, type, methods);
}
protected override IEnumerable<Type> GetTypesForSimpleReference (string jniSimpleReference)
{
Console.WriteLine ($"# jonp: GetTypesForSimpleReference: jniSimpleReference=`{jniSimpleReference}`");
if (typeMappings.TryGetValue (jniSimpleReference, out var target)) {
Console.WriteLine ($"# jonp: GetTypesForSimpleReference: jniSimpleReference=`{jniSimpleReference}` -> `{target}`");
yield return target;
}
foreach (var t in base.GetTypesForSimpleReference (jniSimpleReference)) {
Console.WriteLine ($"# jonp: GetTypesForSimpleReference: jniSimpleReference=`{jniSimpleReference}` -> `{t}`");
yield return t;
}
}
protected override IEnumerable<string> GetSimpleReferences (Type type)
{
return base.GetSimpleReferences (type)
.Concat (CreateSimpleReferencesEnumerator (type));
}
IEnumerable<string> CreateSimpleReferencesEnumerator (Type type)
{
if (typeMappings == null)
yield break;
foreach (var e in typeMappings) {
if (e.Value == type)
yield return e.Key;
}
}
}

Просмотреть файл

@ -0,0 +1,224 @@
# Hello From Android
[Hello-NativeAOTFromJNI](../Hello-NativeAOTFromJNI) demonstrated how
to use [NativeAOT][0] to create a native library which could be loaded
by a Java Virtual Machine (JVM).
Extend this idea for Android!
## Building
Building a native library with NativeAOT requires a Release configuration build.
For in-repo use, that means that xamarin/Java.Interop itself needs to be built in
Release configuration:
```sh
% dotnet build -c Release -t:Prepare
% dotnet build -c Release
```
Once Java.Interop itself is built, you can *publish* the sample:
```sh
% cd samples/Hello-NativeAOTFromAndroid
# set the ANDROID_NDK_HOME environment variable or set the AndroidNdkDirectory property
# set the ANDROID_HOME environment variable or set the AndroidSdkDirectory property
# values here are valid if you have a xamarin/xamarin-android build environment.
% dotnet publish -c Release -p:AndroidNdkDirectory=$HOME/android-toolchain/ndk \
-p:AndroidSdkDirectory=$HOME/android-toolchain/sdk
```
The resulting native library contains various import symbols:
```sh
% nm -D bin/Release/linux-bionic-arm64/native/Hello-NativeAOTFromAndroid.so | grep ' T '
0000000000240950 T JNI_OnLoad@@V1.0
0000000000240ab0 T JNI_OnUnload@@V1.0
0000000000240b30 T Java_net_dot_jni_nativeaot_JavaInteropRuntime_init@@V1.0
00000000002392e0 T __start___managedcode
00000000004394d0 T __start___unbox
00000000004394d0 T __stop___managedcode
000000000043a720 T __stop___unbox
```
The build system also produces a `net.dot.jni.helloandroid-Signed.apk`,
which can be installed and launched:
```sh
% adb install bin/Release/linux-bionic-arm64/net.dot.jni.helloandroid-Signed.apk
% adb shell am start net.dot.jni.helloandroid/my.MainActivity
# Only-java codepath for testing; doesn't use NativeAOT:
% adb shell am start net.dot.jni.helloandroid/net.dot.jni.nativeaot.JavaMainActivity
```
## Logging
By default this sample writes quite a bit to `adb logcat`, including:
* Initialization messages
```
D NativeAotRuntimeProvider: NativeAotRuntimeProvider()
D NativeAotRuntimeProvider: NativeAotRuntimeProvider.attachInfo(): calling JavaInteropRuntime.init()…
D JavaInteropRuntime: Loading libHello-NativeAOTFromAndroid.so…
I JavaInteropRuntime: JNI_OnLoad()
I NativeAotFromAndroid: C# init()
D NativeAotRuntimeProvider: NativeAotRuntimeProvider.onCreate()
```
* JNI Global Reference and Local Reference messages
```
D NativeAot:LREF: +l+ lrefc 1 handle 0x7eb64ae01d/L from thread ''(1)
D NativeAot:GREF: +g+ grefc 1 obj-handle 0x7eb64ae01d/L -> new-handle 0x2af2/G from thread ''(1)
```
* `MainActivity` messages
```
I NativeAotFromAndroid: MainActivity..ctor()
I NativeAotFromAndroid: MainActivity.OnCreate(): savedInstanceState? False
```
Additionally, the end of `MainActivity.OnCreate()` will print out how many
GREFs have been created, and information about the created "surfaced peers":
```
I NativeAotFromAndroid: Created 6 GREFs; Surfaced 1 peers
I NativeAotFromAndroid: SurfacedPeers[ 0] = JniSurfacedPeerInfo(PeerReference=0x2bc6/G IdentityHashCode=0x1d64f40 Instance.Type=Java.Interop.Samples.NativeAotFromAndroid.MainActivity)
```
The (very!) extensive logging around JNI Global and Local references mean that
this sample should *not* be used as-is for startup timing comparison.
That said, on my Pixel 6, we get:
```
I ActivityTaskManager: Displayed net.dot.jni.helloandroid/my.MainActivity for user 0: +282ms
```
## What does this mean for .NET for Android?
Short-term? Nothing. Long-term? *Maybe* something.
While .NET for Android uses Java.Interop, it uses a different *style* of Java.Interop.
.NET for Android *could* be updated to support NativeAot, but it would not be as simple
as this sample may suggest. Difficulties will include:
* [GC](#gc)
* [Marshal Methods](#marshal-methods)
* [Process Startup miscellany, including the important question "what is an Assembly?"](#miscellany)
### GC
.NET for Android relies on .NET's MonoVM, which provides a
[GC bridge](https://github.com/dotnet/runtime/blob/c5c7f0d3d11cc82eddf1747fbdcaec9cb850c3aa/src/native/public/mono/metadata/details/sgen-bridge-types.h),
which is used to support cross-VM object references. This allows an object
reference within a Java VM to keep an object instance within the .NET VM alive.
Neither CoreCLR nor NativeAot runtimes support such a GC bridge, and without
something like it, developers would need to take *significantly* more care in
object lifetimes and cleanup.
Until a cross-VM GC solution is found, .NET for Android must remain on MonoVM.
### Marshal Methods
"Marshal Methods" are methods that are:
* Invoked by the Java Virtual Machine when a `native` Java method is invoked.
* Responsible for parameter marshaling, invoking C# method overrides, and
marshaling the return type back to Java.
.NET for Android uses `generator --codegen-target=XAJavaInterop1` for binding
assemblies, which "bakes in" marshal methods. There is an implicit ABI for
marshal methods, and part of that ABI is that they don't catch exceptions:
```csharp
partial class Activity {
protected virtual unsafe void OnCreate (Android.OS.Bundle? savedInstanceState) => …
static Delegate? cb_onCreate_Landroid_os_Bundle_;
static Delegate GetOnCreate_Landroid_os_Bundle_Handler ()
{
if (cb_onCreate_Landroid_os_Bundle_ == null)
cb_onCreate_Landroid_os_Bundle_ = JNINativeWrapper.CreateDelegate (new _JniMarshal_PPL_V (n_OnCreate_Landroid_os_Bundle_));
return cb_onCreate_Landroid_os_Bundle_;
}
static void n_OnCreate_Landroid_os_Bundle_ (IntPtr jnienv, IntPtr native__this, IntPtr native_savedInstanceState)
{
// Note: no try/catch block! If `__this.OnCreate()` throws, Bad Things™ will happen.
var __this = global::Java.Lang.Object.GetObject<Android.App.Activity> (jnienv, native__this, JniHandleOwnership.DoNotTransfer)!;
var savedInstanceState = global::Java.Lang.Object.GetObject<Android.OS.Bundle> (native_savedInstanceState, JniHandleOwnership.DoNotTransfer);
__this.OnCreate (savedInstanceState);
}
}
```
`Activity.n_OnCreate_Landroid_os_Bundle_()` is the marshal method responsible for
invoking `Activity.OnCreate()`. It does not catch exceptions, and if an exception
*were* thrown from `Activity.OnCreate()`, the entire app could exit. Consequently,
every such marshal method is wrapped in `JNINativeWrapper.CreateDelegate()`, which
uses `DynamicMethod` to wrap the marshal method in a `try`/`catch` block, which
is responsible for notifying the debugger and exception marshaling.
As-is, none of this can work with NativeAot.
Updating .NET for Android to *not* use `DynamicMethod` has both known and unknown
issues (what new pattern do we use? What about compatibility with existing
binding assemblies?).
This sample uses `generator --codegen-target=JavaInterop1` for binding assemblies,
which *skips* the emission of marshal methods *entirely*. As Marshal Methods are
*required*, `jnimarshalmethod-gen` is invoked as a post-build step to insert
Marshal Methods into the assemblies, and these marshal methods appropriately
marshal exceptions.
## Miscellany
.NET for Android deals with assemblies: they can be side-loaded (for Fast Deployment),
packaged trimmed or untrimmed. Bidirectional mapping between JNI type names and
`System.Type` instances makes extensive use of MonoVM's embedding API.
None of the above exists in NativeAot: there are no separate assembly files,
"assembly identity" is a nebulous concept, and there is no equivalent to the MonoVm
embedding API.
Large portions of .NET for Android would need to be rewritten to support NativeAot,
and NativeAot would actively prevent features such as Fast Deployment, meaning *both*
MonoVM and NativeAot would need to be supported.
## Notes
As with `Hello-NativeAOTFromJNI`, the project needs to be built with
`$(PlatformTarget)`=AnyCPU, so that `jnimarshalmethod-gen` can be used
to generate JNI Marshal Methods as a post-build step.
This project contains a *tiny* `android.xml` API description for Android.
This is used to generate a binding, allowing (nominally) intuitive:
```csharp
[JniTypeSignature ("my/MainActivity")]
partial class MainActivity : Android.App.Activity {
protected override void OnCreate (Android.OS.Bundle? savedInstanceState) => …
}
```
This project follows what .NET for Android does to initialize things:
provide a custom [`ContentProvider`][1] which contains Java "bootstrap"
code to initialize the runtime.
### GC
As with [Hello-NativeAOTFromJNI](../Hello-NativeAOTFromJNI), NativeAOT does not
provide a GC bridge that we can rely on. Consequently, every "surfaced peer" will
*never be collected by default*.
This is a *sample*, not a product, and not even the *inkling* of a product.
For exploratory purposes only.
[0]: https://github.com/dotnet/samples/blob/main/core/nativeaot/NativeLibrary/README.md
[1]: https://developer.android.com/reference/android/content/ContentProvider

Просмотреть файл

@ -0,0 +1,2 @@
<metadata>
</metadata>

Просмотреть файл

Просмотреть файл

Просмотреть файл

Просмотреть файл

@ -0,0 +1,56 @@
<?xml version="1.0" encoding="utf-8"?>
<api platform="34">
<package name="android.content" jni-name="android/content">
<class abstract="true" deprecated="not deprecated" extends="java.lang.Object" extends-generic-aware="java.lang.Object" jni-extends="Ljava/lang/Object;" final="false" name="Context" static="false" visibility="public" jni-signature="Landroid/content/Context;">
<constructor deprecated="not deprecated" final="false" name="Context" jni-signature="()V" bridge="false" static="false" type="android.content.Context" synthetic="false" visibility="public" />
</class>
<class abstract="false" deprecated="not deprecated" extends="android.content.Context" extends-generic-aware="android.content.Context" jni-extends="Landroid/content/Context;" final="false" name="ContextWrapper" static="false" visibility="public" jni-signature="Landroid/content/ContextWrapper;">
<constructor deprecated="not deprecated" final="false" name="ContextWrapper" jni-signature="(Landroid/content/Context;)V" bridge="false" static="false" type="android.content.ContextWrapper" synthetic="false" visibility="public">
<parameter name="base" type="android.content.Context" jni-type="Landroid/content/Context;"></parameter>
</constructor>
</class>
</package>
<package name="android.view" jni-signature="android/view">
<class abstract="false" deprecated="not deprecated" extends="android.content.ContextWrapper" extends-generic-aware="android.content.ContextWrapper" jni-extends="Landroid/content/ContextWrapper;" final="false" name="ContextThemeWrapper" static="false" visibility="public" jni-signature="Landroid/view/ContextThemeWrapper;">
<constructor deprecated="not deprecated" final="false" name="ContextThemeWrapper" jni-signature="()V" bridge="false" static="false" type="android.view.ContextThemeWrapper" synthetic="false" visibility="public" />
<constructor deprecated="not deprecated" final="false" name="ContextThemeWrapper" jni-signature="(Landroid/content/Context;Landroid/content/res/Resources$Theme;)V" bridge="false" static="false" type="android.view.ContextThemeWrapper" synthetic="false" visibility="public" merge.SourceFile="..\..\bin\BuildDebug\api\api-23.xml.in">
<parameter name="base" type="android.content.Context" jni-type="Landroid/content/Context;"></parameter>
<parameter name="theme" type="android.content.res.Resources.Theme" jni-type="Landroid/content/res/Resources$Theme;"></parameter>
</constructor>
<constructor deprecated="not deprecated" final="false" name="ContextThemeWrapper" jni-signature="(Landroid/content/Context;I)V" bridge="false" static="false" type="android.view.ContextThemeWrapper" synthetic="false" visibility="public">
<parameter name="base" type="android.content.Context" jni-type="Landroid/content/Context;"></parameter>
<parameter name="themeResId" type="int" jni-type="I"></parameter>
</constructor>
<method abstract="false" deprecated="not deprecated" final="false" name="onCreate" jni-signature="(Landroid/os/Bundle;)V" bridge="false" native="false" return="void" jni-return="V" static="false" synchronized="false" synthetic="false" visibility="protected">
<parameter name="savedInstanceState" type="android.os.Bundle" jni-type="Landroid/os/Bundle;"></parameter>
</method>
</class>
</package>
<package name="android.app" jni-signature="android/app">
<class abstract="false" deprecated="not deprecated" extends="android.view.ContextThemeWrapper" extends-generic-aware="android.view.ContextThemeWrapper" jni-extends="Landroid/view/ContextThemeWrapper;" final="false" name="Activity" static="false" visibility="public" jni-signature="Landroid/app/Activity;">
<constructor deprecated="not deprecated" final="false" name="Activity" jni-signature="()V" bridge="false" static="false" type="android.app.Activity" synthetic="false" visibility="public" />
<method abstract="false" deprecated="not deprecated" final="false" name="setContentView" jni-signature="(I)V" bridge="false" native="false" return="void" jni-return="V" static="false" synchronized="false" synthetic="false" visibility="public">
<parameter name="layoutResID" type="int" jni-type="I"></parameter>
</method>
</class>
</package>
<package name="android.os" jni-name="android/os">
<class abstract="false" deprecated="not deprecated" extends="java.lang.Object" extends-generic-aware="java.lang.Object" jni-extends="Ljava/lang/Object;" final="false" name="BaseBundle" static="false" visibility="public" jni-signature="Landroid/os/BaseBundle;" merge.SourceFile="..\..\bin\BuildDebug\api\api-21.xml.in">
</class>
<class abstract="false" deprecated="not deprecated" extends="android.os.BaseBundle" extends-generic-aware="android.os.BaseBundle" jni-extends="Landroid/os/BaseBundle;" final="true" name="Bundle" static="false" visibility="public" jni-signature="Landroid/os/Bundle;">
<constructor deprecated="not deprecated" final="false" name="Bundle" jni-signature="()V" bridge="false" static="false" type="android.os.Bundle" synthetic="false" visibility="public" />
<constructor deprecated="not deprecated" final="false" name="Bundle" jni-signature="(Landroid/os/Bundle;)V" bridge="false" static="false" type="android.os.Bundle" synthetic="false" visibility="public">
<parameter name="b" type="android.os.Bundle" jni-type="Landroid/os/Bundle;"></parameter>
</constructor>
<constructor deprecated="not deprecated" final="false" name="Bundle" jni-signature="(Landroid/os/PersistableBundle;)V" bridge="false" static="false" type="android.os.Bundle" synthetic="false" visibility="public" merge.SourceFile="..\..\bin\BuildDebug\api\api-21.xml.in">
<parameter name="b" type="android.os.PersistableBundle" jni-type="Landroid/os/PersistableBundle;"></parameter>
</constructor>
<constructor deprecated="not deprecated" final="false" name="Bundle" jni-signature="(I)V" bridge="false" static="false" type="android.os.Bundle" synthetic="false" visibility="public">
<parameter name="capacity" type="int" jni-type="I"></parameter>
</constructor>
<constructor deprecated="not deprecated" final="false" name="Bundle" jni-signature="(Ljava/lang/ClassLoader;)V" bridge="false" static="false" type="android.os.Bundle" synthetic="false" visibility="public">
<parameter name="loader" type="java.lang.ClassLoader" jni-type="Ljava/lang/ClassLoader;"></parameter>
</constructor>
</class>
</package>
</api>

5
samples/Hello-NativeAOTFromAndroid/app/.gitignore поставляемый Normal file
Просмотреть файл

@ -0,0 +1,5 @@
/build
lib/java-interop.jar
src/main/java/my
src/main/jniLibs

Просмотреть файл

@ -0,0 +1,49 @@
plugins {
id 'com.android.application'
}
dependencies {
implementation files('lib/java-interop.jar')
}
android {
namespace 'net.dot.jni.helloandroid'
compileSdk 33
// Doing this to match NDK NativeAOT is using
ndkVersion "23.2.8568313"
defaultConfig {
applicationId "net.dot.jni.helloandroid"
minSdk 21
targetSdk 33
versionCode 1
versionName "1.0"
// NOTE: for now, arm64 only. Might eventually do 4 ABIs
ndk {
abiFilters 'arm64-v8a'
}
}
// Just use the built-in debug keystore
signingConfigs {
release {
storeFile file("${System.getProperty('user.home')}/.android/debug.keystore")
storePassword 'android'
keyAlias 'androiddebugkey'
keyPassword 'android'
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.release
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}

21
samples/Hello-NativeAOTFromAndroid/app/proguard-rules.pro поставляемый Normal file
Просмотреть файл

@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

Просмотреть файл

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
tools:targetApi="31">
<activity
android:name="net.dot.jni.nativeaot.JavaMainActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="true">
</activity>
<activity
android:name="my.MainActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="true"
android:label="Hello NativeAot!">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<provider
android:name="net.dot.jni.nativeaot.NativeAotRuntimeProvider"
android:exported="false"
android:initOrder="1999999999"
android:authorities="net.dot.jni.nativeaot.NativeAotRuntimeProvider.__init__"
/>
</application>
</manifest>

Просмотреть файл

@ -0,0 +1,15 @@
package net.dot.jni.nativeaot;
import android.util.Log;
public class JavaInteropRuntime {
static {
Log.d("JavaInteropRuntime", "Loading libHello-NativeAOTFromAndroid.so…");
System.loadLibrary("Hello-NativeAOTFromAndroid");
}
private JavaInteropRuntime() {
}
public static native void init();
}

Просмотреть файл

@ -0,0 +1,18 @@
package net.dot.jni.nativeaot;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import net.dot.jni.helloandroid.R;
public class JavaMainActivity extends Activity {
private static final String TAG = "NativeAot:JavaMainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
Log.i(TAG, "JavaMainActivity.onCreate()");
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}

Просмотреть файл

@ -0,0 +1,51 @@
package net.dot.jni.nativeaot;
import android.util.Log;
public class NativeAotRuntimeProvider
extends android.content.ContentProvider
{
private static final String TAG = "NativeAotRuntimeProvider";
public NativeAotRuntimeProvider() {
Log.d(TAG, "NativeAotRuntimeProvider()");
}
@Override
public boolean onCreate() {
Log.d(TAG, "NativeAotRuntimeProvider.onCreate()");
return true;
}
@Override
public void attachInfo(android.content.Context context, android.content.pm.ProviderInfo info) {
Log.d(TAG, "NativeAotRuntimeProvider.attachInfo(): calling JavaInteropRuntime.init()…");
JavaInteropRuntime.init();
super.attachInfo (context, info);
}
@Override
public android.database.Cursor query(android.net.Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
throw new RuntimeException ("This operation is not supported.");
}
@Override
public String getType(android.net.Uri uri) {
throw new RuntimeException ("This operation is not supported.");
}
@Override
public android.net.Uri insert(android.net.Uri uri, android.content.ContentValues initialValues) {
throw new RuntimeException ("This operation is not supported.");
}
@Override
public int delete(android.net.Uri uri, String where, String[] whereArgs) {
throw new RuntimeException ("This operation is not supported.");
}
@Override
public int update(android.net.Uri uri, android.content.ContentValues values, String where, String[] whereArgs) {
throw new RuntimeException ("This operation is not supported.");
}
}

Двоичный файл не отображается.

Просмотреть файл

@ -0,0 +1,30 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="85.84757"
android:endY="92.4963"
android:startX="42.9492"
android:startY="49.59793"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>

Просмотреть файл

@ -0,0 +1,170 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
</vector>

Просмотреть файл

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
android:gravity="center_horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:text="Hello World!"
/>
</LinearLayout>

Просмотреть файл

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

Просмотреть файл

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 1.4 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 2.8 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 982 B

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 1.7 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 1.9 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 3.8 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 2.8 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 5.8 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 3.8 KiB

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 7.6 KiB

Просмотреть файл

@ -0,0 +1,3 @@
<resources>
<string name="app_name">Hello NativeAOT from Android!</string>
</resources>

Просмотреть файл

@ -0,0 +1,5 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
// Need to use < 8.0 in order to use JDK-11
id 'com.android.application' version '7.4.0' apply false
}

Просмотреть файл

@ -0,0 +1,21 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app's APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Enables namespacing of each library's R class so that its R class includes only the
# resources declared in the library itself and none from the library's dependencies,
# thereby reducing the size of the R class for that library
android.nonTransitiveRClass=true

Просмотреть файл

@ -0,0 +1,17 @@
pluginManagement {
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
}
}
rootProject.name = "Hello-NativeAOTFromAndroid"
include ':app'

Просмотреть файл

@ -126,7 +126,7 @@ public class CecilImporter
foreach (var bt in type.GetBaseTypes (resolver)) {
ctorTypes.Add (bt);
var rattr = CecilExtensions.GetMethodRegistrationAttributes (bt).FirstOrDefault ();
var rattr = CecilExtensions.GetTypeRegistrationAttributes (bt).FirstOrDefault ();
if (rattr != null && rattr.DoNotGenerateAcw)
break;

Просмотреть файл

@ -45,7 +45,7 @@ static class CecilExtensions
while ((bmethod = method.GetBaseDefinition (cache)) != method) {
method = bmethod;
if (method.AnyCustomAttributes (typeof (RegisterAttribute))) {
if (HasMethodRegistrationAttributes (method)) {
return method;
}
}
@ -168,6 +168,7 @@ static class CecilExtensions
}
}
// Keep in sync w/ HasMethodRegistrationAttributes()
public static IEnumerable<RegisterAttribute> GetMethodRegistrationAttributes (Mono.Cecil.ICustomAttributeProvider p)
{
foreach (var a in CecilExtensions.GetAttributes<RegisterAttribute> (p, a => CecilExtensions.ToRegisterAttribute (a))) {
@ -189,6 +190,25 @@ static class CecilExtensions
}
}
static readonly string[] MethodRegistrationAttributes = new[]{
typeof (RegisterAttribute).FullName,
"Java.Interop.JniConstructorSignatureAttribute",
"Java.Interop.JniMethodSignatureAttribute",
};
// Keep in sync w/ GetMethodRegistrationAttributes()
public static bool HasMethodRegistrationAttributes (Mono.Cecil.ICustomAttributeProvider p)
{
foreach (CustomAttribute custom_attribute in p.CustomAttributes) {
var customAttrType = custom_attribute.Constructor.DeclaringType.FullName;
foreach (var t in MethodRegistrationAttributes) {
if (customAttrType == t)
return true;
}
}
return false;
}
public static IEnumerable<ExportAttribute> GetExportAttributes (IMemberDefinition p, IMetadataResolver cache)
{
return CecilExtensions.GetAttributes<ExportAttribute> (p, a => CecilExtensions.ToExportAttribute (a, p, cache))

Просмотреть файл

@ -330,7 +330,11 @@ namespace Java.Interop.Tools.JavaTypeSystem
attributes.FirstOrDefault (a => a.AttributeType.FullNameCorrected () == "System.ObsoleteAttribute");
static CustomAttribute? GetRegisterAttribute (Collection<CustomAttribute> attributes) =>
attributes.FirstOrDefault (a => a.AttributeType.FullNameCorrected () == "Android.Runtime.RegisterAttribute");
attributes.FirstOrDefault (a => {
var attrType = a.AttributeType.FullNameCorrected ();
return attrType == "Android.Runtime.RegisterAttribute" ||
attrType == "Java.Interop.JniTypeSignatureAttribute";
});
static string? GetRegisteredJavaTypeName (TypeDefinition type)
{

Просмотреть файл

@ -250,7 +250,11 @@ namespace MonoDroid.Generation
attribute?.ConstructorArguments.Any () == true ? (string) attribute.ConstructorArguments [0].Value : null;
static CustomAttribute GetRegisterAttribute (Collection<CustomAttribute> attributes) =>
attributes.FirstOrDefault (a => a.AttributeType.FullNameCorrected () == "Android.Runtime.RegisterAttribute");
attributes.FirstOrDefault (a => {
var attrType = a.AttributeType.FullNameCorrected ();
return attrType == "Android.Runtime.RegisterAttribute" ||
attrType == "Java.Interop.JniTypeSignatureAttribute";
});
static bool IsDefaultInterfaceMethod (GenBase declaringType, MethodDefinition method)
{