{"id":17950894,"url":"https://github.com/jonathanpeppers/android-nativeaot","last_synced_at":"2025-08-18T11:50:03.262Z","repository":{"id":219786062,"uuid":"732127673","full_name":"jonathanpeppers/Android-NativeAOT","owner":"jonathanpeppers","description":"A .NET 8, NativeAOT example on Android","archived":false,"fork":false,"pushed_at":"2024-12-05T17:13:32.000Z","size":25895,"stargazers_count":89,"open_issues_count":1,"forks_count":9,"subscribers_count":5,"default_branch":"main","last_synced_at":"2025-05-19T20:11:25.353Z","etag":null,"topics":["android","csharp","dotnet","nativeaot"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jonathanpeppers.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-12-15T17:59:27.000Z","updated_at":"2025-04-26T17:14:05.000Z","dependencies_parsed_at":"2024-01-29T20:18:43.386Z","dependency_job_id":"2b79bc2d-716f-4d71-972b-21d84ca15689","html_url":"https://github.com/jonathanpeppers/Android-NativeAOT","commit_stats":null,"previous_names":["jonathanpeppers/android-nativeaot"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/jonathanpeppers/Android-NativeAOT","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jonathanpeppers%2FAndroid-NativeAOT","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jonathanpeppers%2FAndroid-NativeAOT/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jonathanpeppers%2FAndroid-NativeAOT/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jonathanpeppers%2FAndroid-NativeAOT/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jonathanpeppers","download_url":"https://codeload.github.com/jonathanpeppers/Android-NativeAOT/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jonathanpeppers%2FAndroid-NativeAOT/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":270988471,"owners_count":24680672,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-18T02:00:08.743Z","response_time":89,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["android","csharp","dotnet","nativeaot"],"created_at":"2024-10-29T09:40:59.959Z","updated_at":"2025-08-18T11:50:03.199Z","avatar_url":"https://github.com/jonathanpeppers.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Android-NativeAOT\n\nA .NET 8, NativeAOT example on Android.\n\n## Getting started\n\nConfigure your environment: the following environment variables are required:\n\n  * `ANDROID_NDK_HOME`: The path to an Android NDK installation.\n  * `ANDROID_HOME`: The path to an Android SDK installation.\n\nBuild the project:\n\n```console\ndotnet build\ndotnet publish DotNet/libdotnet.csproj\n(cd Native \u0026\u0026 ./gradlew assembleRelease)\n```\n\nInstall the app:\n\n```console\n$ANDROID_HOME/platform-tools/adb install Native/app/build/outputs/apk/release/app-release.apk\n```\n\nRun the app:\n\n```console\n$ANDROID_HOME/platform-tools/adb shell am start com.jonathanpeppers.nativeaot/android.app.NativeActivity\n```\n\n## Example using SkiaSharp\n\nThis sample has a C++ Android Studio project:\n\n* Uses [Native Activity](https://developer.android.com/ndk/samples/sample_na)\n* No Java/Kotlin code\n* Configures OpenGL\n* Calls into C# / managed code\n* Managed code uses SkiaSharp for rendering a random Skia shader\n* Tap input randomly changes the shader\n\nSome screenshots of the Skia content:\n\n\u003cimg width=\"256\" src=\"docs/screenshot1.gif\" /\u003e\n\u003cimg width=\"256\" src=\"docs/screenshot2.gif\" /\u003e\n\u003cimg width=\"256\" src=\"docs/screenshot3.gif\" /\u003e\n\n(Note these look completely smooth on a Pixel 5, I just tried to snap quick gifs with Vysor)\n\nThe C# side is a:\n\n* .NET 8 class library\n* Built with RID `linux-bionic-arm64`\n* Uses the SkiaSharp NuGet package, as one would.\n  * Used a nightly build of SkiaSharp, as I wanted a feature from Skia 3.0\n\n## App Size\n\nThe release `.apk` file of the SkiaSharp sample is ~4.26 MB\n\nA breakdown of the files inside:\n\n```\n\u003e 7z l app-release.apk\n   Date      Time    Attr         Size   Compressed  Name\n------------------- ----- ------------ ------------  ------------------------\n1981-01-01 01:01:02 .....           56           52  META-INF\\com\\android\\build\\gradle\\app-metadata.properties\n1981-01-01 01:01:02 .....         1524          753  classes.dex\n1981-01-01 01:01:02 .....      8525024      3733033  lib\\arm64-v8a\\libSkiaSharp.so\n1981-01-01 01:01:02 .....      1070792       473191  lib\\arm64-v8a\\libdotnet.so\n1981-01-01 01:01:02 .....        19504         6869  lib\\arm64-v8a\\libnativeaot.so\n1981-01-01 01:01:02 .....         2376          867  AndroidManifest.xml\n1981-01-01 01:01:02 .....         7778         7778  res\\-6.webp\n1981-01-01 01:01:02 .....          548          239  res\\0K.xml\n1981-01-01 01:01:02 .....         5696          987  res\\0w.xml\n1981-01-01 01:01:02 .....          788          347  res\\9s.xml\n1981-01-01 01:01:02 .....          548          239  res\\BW.xml\n1981-01-01 01:01:02 .....         1404         1404  res\\MO.webp\n1981-01-01 01:01:02 .....         1572          703  res\\PF.xml\n1981-01-01 01:01:02 .....         2884         2884  res\\Sn.webp\n1981-01-01 01:01:02 .....          982          982  res\\d2.webp\n1981-01-01 01:01:02 .....         2898         2898  res\\fq.webp\n1981-01-01 01:01:02 .....         5914         5914  res\\j_.webp\n1981-01-01 01:01:02 .....         1900         1900  res\\qs.webp\n1981-01-01 01:01:02 .....         3844         3844  res\\sK.webp\n1981-01-01 01:01:02 .....         3918         3918  res\\u5.webp\n1981-01-01 01:01:02 .....         1772         1772  res\\yw.webp\n1981-01-01 01:01:02 .....         2036         2036  resources.arsc\n1981-01-01 01:01:02 .....         2085         1122  META-INF\\CERT.SF\n1981-01-01 01:01:02 .....         1167         1021  META-INF\\CERT.RSA\n1981-01-01 01:01:02 .....         2011         1046  META-INF\\MANIFEST.MF\n------------------- ----- ------------ ------------  ------------------------\n1981-01-01 01:01:02            9669021      4255799  25 files\n```\n\n`libdotnet.so` is ~1.07 MB, and `libSkiaSharp.so` is ~8.5MB!\n\nIf we reduce this to a \"Hello World\" example:\n\n* `hello.apk` is ~430 KB!\n* `libdotnet.so` (uncompressed) is ~821 KB!\n\n## Startup Time\n\nThe average of 10 runs on a Pixel 5 of the SkiaSharp sample:\n\n```log\nAverage(ms): 121\nStd Err(ms): 3.29983164553722\nStd Dev(ms): 10.434983894999\n```\n\nAverage of 10 runs on a Pixel 5 of the \"Hello World\" example:\n\n```log\nAverage(ms): 120.9\nStd Err(ms): 2.97937353594118\nStd Dev(ms): 9.42160637400367\n```\n\nThey might be effectively the same.\n\nFor comparison (as of .NET 8), a `dotnet new android` app is about ~180ms on a\nPixel 5, and `dotnet new maui` is about ~560ms.\n\nSource: https://github.com/jonathanpeppers/maui-profiling\n\n## \"Hello World\" Example\n\nSee the [HelloWorld](https://github.com/jonathanpeppers/Android-NativeAOT/tree/HelloWorld) branch.\n\nI had this managed code:\n\n```csharp\n[UnmanagedCallersOnly(EntryPoint = \"ManagedAdd\")]\npublic static int ManagedAdd(int x, int y) =\u003e x + y;\n```\n\nI created a C++ Android project using NativeActivity, and I called the managed\ncode from C++:\n\n```c++\n// in dotnet.h\nextern \"C\" int ManagedAdd(int x, int y);\n\n// in native-lib.cpp\nint result = ManagedAdd(1, 2);\n__android_log_print (ANDROID_LOG_INFO, TAG, \"ManagedAdd(1, 2) returned: %i\", result);\n```\n\nResults in the message:\n\n```log\n01-31 11:42:44.545 28239 28259 I NATIVE  : Entering android_main\n01-31 11:42:44.550 28239 28259 I NATIVE  : ManagedAdd(1, 2) returned: 3\n```\n\nSee [DotNet/README.md](DotNet/README.md) on how to build `libdotnet.so`.\n\n## Notes\n\n`Console.WriteLine()` doesn't work because it basically just writes to Unix stdout. stdout does not appear in `adb logcat` output, as you have to call `__android_log_print` instead.\n\nThis was an interesting example, to start a thread that processes stdout and calls the appropriate Android API:\n\n* https://codelab.wordpress.com/2014/11/03/how-to-use-standard-output-streams-for-logging-in-android-apps/\n\nInstead, we can p/invoke into:\n\n```csharp\n[DllImport(\"log\", EntryPoint = \"__android_log_print\", CallingConvention = CallingConvention.Cdecl)]\npublic static extern int LogPrint(LogPriority priority, string tag, string format);\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjonathanpeppers%2Fandroid-nativeaot","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjonathanpeppers%2Fandroid-nativeaot","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjonathanpeppers%2Fandroid-nativeaot/lists"}