[Hello-NativeAOTFromAndroid] Add NativeAOT+Android sample (#1218)
Context:2197579478
Commit21975794
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.
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 "%(Identity)"')" />
|
||||
<_JavaBaseRef Include="@(ReferencePathWithRefAssemblies)"
|
||||
Condition=" '%(FileName)' == 'Java.Base' "
|
||||
/>
|
||||
<_Ref Include="@(_JavaBaseRef->'-r "%(FullPath)"')" />
|
||||
</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="<Namespace Prefix='msbuild' Uri='http://schemas.microsoft.com/developer/msbuild/2003' />"
|
||||
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) "$(TargetPath)" $(_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) "$(TargetPath)" $(_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=""$(GradleWPath)" $(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=""$(GradleWPath)" $(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>
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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.");
|
||||
}
|
||||
}
|
Двоичные данные
samples/Hello-NativeAOTFromAndroid/app/src/main/libs/arm64-v8a/libdotnet.so.dbg
Normal file
|
@ -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>
|
Двоичные данные
samples/Hello-NativeAOTFromAndroid/app/src/main/res/mipmap-hdpi/ic_launcher.webp
Normal file
После Ширина: | Высота: | Размер: 1.4 KiB |
Двоичные данные
samples/Hello-NativeAOTFromAndroid/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
Normal file
После Ширина: | Высота: | Размер: 2.8 KiB |
Двоичные данные
samples/Hello-NativeAOTFromAndroid/app/src/main/res/mipmap-mdpi/ic_launcher.webp
Normal file
После Ширина: | Высота: | Размер: 982 B |
Двоичные данные
samples/Hello-NativeAOTFromAndroid/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
Normal file
После Ширина: | Высота: | Размер: 1.7 KiB |
Двоичные данные
samples/Hello-NativeAOTFromAndroid/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
Normal file
После Ширина: | Высота: | Размер: 1.9 KiB |
Двоичные данные
samples/Hello-NativeAOTFromAndroid/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
Normal file
После Ширина: | Высота: | Размер: 3.8 KiB |
Двоичные данные
samples/Hello-NativeAOTFromAndroid/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
Normal file
После Ширина: | Высота: | Размер: 2.8 KiB |
Двоичные данные
samples/Hello-NativeAOTFromAndroid/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
Normal file
После Ширина: | Высота: | Размер: 5.8 KiB |
Двоичные данные
samples/Hello-NativeAOTFromAndroid/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
Normal file
После Ширина: | Высота: | Размер: 3.8 KiB |
Двоичные данные
samples/Hello-NativeAOTFromAndroid/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
Normal file
После Ширина: | Высота: | Размер: 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)
|
||||
{
|
||||
|
|