-
Notifications
You must be signed in to change notification settings - Fork 5.1k
[mono] Add Android app sample using mono runtime #3944
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
f34100d
Add Android sample
128f79a
Add Activity to Android sample
f2aa69b
Update Android sample to use activity
8c481d2
Add native file for interop
dc626bb
Build native interop lib into app
fcad35a
Enable java interop from csharp
40807bb
Add csharp interop from java button click
0997464
Another csharp java interop example
ee5d1a7
Add initial Android sample readme
a83d10d
Move global.json to affect all mono samples
f0aed19
Reference dotnet6 NuGet feed
fb5492f
Generalize for other architectures
65adcea
Update target framework to target net6.0
2b1db08
Trim assemblies and Default configuration to Release
998d46e
Add Android publish lane
4f710ed
Bump dotnet sdk version and csproj pathing and version
36432c0
Clarify parameter name
664557b
Update .NET sdk version in README
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
using System; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using System.Runtime.InteropServices; | ||
|
||
public static class AndroidSampleApp | ||
{ | ||
[DllImport("native-lib")] | ||
private extern static int myNum(); | ||
|
||
[DllImport("native-lib")] | ||
private extern static void androidRegisterCounterIncrement(Func<string> del); | ||
|
||
[DllImport("native-lib")] | ||
private extern static void androidRegisterNameGreet(Func<string,string> del); | ||
|
||
private static int counter = 1; | ||
|
||
// Called by native code, see native-lib.c | ||
private static string IncrementCounter() | ||
{ | ||
return $"Clicked {counter++} times!"; | ||
} | ||
|
||
private static string GreetName(string name) | ||
{ | ||
return $"Hello {name}!\nRunning on mono runtime\nUsing C#"; | ||
} | ||
|
||
public static int Main(string[] args) | ||
{ | ||
androidRegisterCounterIncrement(IncrementCounter); | ||
androidRegisterNameGreet(GreetName); | ||
Console.WriteLine("Hello, Android!"); // logcat | ||
return myNum(); | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
<Project Sdk="Microsoft.NET.Sdk" DefaultTargets="BuildApp"> | ||
<PropertyGroup> | ||
<TargetFramework>net6.0</TargetFramework> | ||
<TargetArchitecture>x64</TargetArchitecture> | ||
<RuntimeIdentifier>android-$(TargetArchitecture)</RuntimeIdentifier> | ||
<OutputType>Exe</OutputType> | ||
<PublishTrimmed>True</PublishTrimmed> | ||
<TrimMode>link</TrimMode> | ||
<Configuration>Release</Configuration> | ||
<OutputPath>$(MSBuildThisFileDirectory)bin/$(Configuration)/$(TargetFramework)/$(RuntimeIdentifier)/publish</OutputPath> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="Microsoft.NET.Runtime.Android.Sample.Mono" Version="$(BundledNETCoreAppPackageVersion)" GeneratePathProperty="true" /> | ||
</ItemGroup> | ||
|
||
<Target Name="BuildNativeLib" BeforeTargets="Publish"> | ||
<PropertyGroup> | ||
<abi Condition="'$(TargetArchitecture)' == x64">x86_64</abi> | ||
<abi Condition="'$(TargetArchitecture)' == x86">x86</abi> | ||
<abi Condition="'$(TargetArchitecture)' == arm">armeabi-v7a</abi> | ||
<abi Condition="'$(TargetArchitecture)' == arm64">arm64-v8a</abi> | ||
</PropertyGroup> | ||
<Exec Command="cmake -DCMAKE_TOOLCHAIN_FILE=$(ANDROID_NDK_ROOT)/build/cmake/android.toolchain.cmake -DANDROID_ABI=$(abi) -DANDROID_STL=none -DANDROID_NATIVE_API_LEVEL=21 -B monodroid -DCMAKE_BUILD_TYPE=MinSizeRel -DCMAKE_C_FLAGS=%22-s -Wno-unused-command-line-argument%22" /> | ||
<Exec Command="cmake --build monodroid --config MinSizeRel" /> | ||
|
||
<ItemGroup> | ||
<NativeLibFiles Include="$(MSBuildThisFileDirectory)monodroid/libnative-lib.so" /> | ||
</ItemGroup> | ||
|
||
<Copy SourceFiles="@(NativeLibFiles)" DestinationFolder="$(OutputPath)" /> | ||
|
||
<RemoveDir Directories="$(MSBuildThisFileDirectory)monodroid" /> | ||
</Target> | ||
|
||
<UsingTask TaskName="AndroidAppBuilderTask" | ||
AssemblyFile="$(PkgMicrosoft_NET_Runtime_Android_Sample_Mono)\tools\net6.0\AndroidAppBuilder.dll" /> | ||
|
||
<Target Name="BuildApp" AfterTargets="Publish"> | ||
<PropertyGroup> | ||
<ApkDir>$(OutputPath)apk\</ApkDir> | ||
<AdbTool>$(ANDROID_SDK_ROOT)\platform-tools\adb</AdbTool> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<AndroidRuntimePack Include="@(ResolvedRuntimePack)" Exclude="Microsoft.AspNetCore.App.Runtime.linux-$(TargetArchitecture)" /> | ||
</ItemGroup> | ||
|
||
<RemoveDir Directories="$(ApkDir)" /> | ||
|
||
<AndroidAppBuilderTask | ||
RuntimeIdentifier="$(RuntimeIdentifier)" | ||
SourceDir="$(OutputPath)" | ||
MonoRuntimeHeaders="%(AndroidRuntimePack.PackageDirectory)\runtimes\$(RuntimeIdentifier)\native\include\mono-2.0" | ||
MainLibraryFileName="$(AssemblyName).dll" | ||
ProjectName="HelloAndroid" | ||
OutputDir="$(ApkDir)" | ||
StripDebugSymbols="True" | ||
KeyStorePath="$(HOME)/.android/" | ||
NativeMainSource="$(MSBuildThisFileDirectory)MainActivity.java"> | ||
<Output TaskParameter="ApkBundlePath" PropertyName="ApkBundlePath" /> | ||
<Output TaskParameter="ApkPackageId" PropertyName="ApkPackageId" /> | ||
</AndroidAppBuilderTask> | ||
</Target> | ||
|
||
<Target Name="LaunchApp" AfterTargets="BuildApp" Condition="'$(RunningOnCI)' == ''"> | ||
<Exec Command="$(AdbTool) kill-server" /> | ||
<Exec Command="$(AdbTool) start-server" /> | ||
<Exec Command="$(AdbTool) logcat -c" ContinueOnError="WarnAndContinue" /> | ||
<Exec Command="$(AdbTool) install $(ApkDir)/bin/HelloAndroid.apk" /> | ||
<Exec Command="$(AdbTool) shell am start -n net.dot.HelloAndroid/net.dot.MainActivity" /> | ||
</Target> | ||
</Project> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
cmake_minimum_required(VERSION 3.10) | ||
|
||
project(buildnativelib) | ||
|
||
add_library( | ||
native-lib | ||
SHARED | ||
native-lib.c) | ||
|
||
target_link_libraries( | ||
native-lib | ||
log) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
package net.dot; | ||
|
||
import android.app.Activity; | ||
import android.os.Bundle; | ||
import android.os.Handler; | ||
import android.os.Looper; | ||
import android.view.View; | ||
import android.widget.Button; | ||
import android.widget.EditText; | ||
import android.widget.RelativeLayout; | ||
import android.widget.TextView; | ||
import android.graphics.Color; | ||
|
||
public class MainActivity extends Activity | ||
{ | ||
static | ||
{ | ||
System.loadLibrary("native-lib"); | ||
} | ||
|
||
// Called from JNI | ||
public static int getNum() | ||
{ | ||
return 42; | ||
} | ||
|
||
@Override | ||
protected void onCreate(Bundle savedInstanceState) | ||
{ | ||
super.onCreate(savedInstanceState); | ||
|
||
RelativeLayout rootLayout = new RelativeLayout(this); | ||
|
||
final TextView welcomeMsg = new TextView(this); | ||
welcomeMsg.setId(1); | ||
welcomeMsg.setTextSize(20); | ||
RelativeLayout.LayoutParams welcomeMsgParams = | ||
new RelativeLayout.LayoutParams( | ||
RelativeLayout.LayoutParams.WRAP_CONTENT, | ||
RelativeLayout.LayoutParams.WRAP_CONTENT); | ||
welcomeMsgParams.addRule(RelativeLayout.CENTER_HORIZONTAL); | ||
welcomeMsgParams.addRule(RelativeLayout.ALIGN_TOP); | ||
rootLayout.addView(welcomeMsg, welcomeMsgParams); | ||
|
||
final TextView clickCounter = new TextView(this); | ||
clickCounter.setId(2); | ||
clickCounter.setTextSize(20); | ||
RelativeLayout.LayoutParams clickCounterParams = | ||
new RelativeLayout.LayoutParams( | ||
RelativeLayout.LayoutParams.WRAP_CONTENT, | ||
RelativeLayout.LayoutParams.WRAP_CONTENT); | ||
clickCounterParams.addRule(RelativeLayout.CENTER_HORIZONTAL); | ||
clickCounterParams.addRule(RelativeLayout.CENTER_VERTICAL); | ||
rootLayout.addView(clickCounter, clickCounterParams); | ||
|
||
Button button = new Button(this); | ||
button.setId(3); | ||
button.setText("Click me"); | ||
button.setOnClickListener(new View.OnClickListener() { | ||
@Override | ||
public void onClick(View v) { | ||
clickCounter.setText(incrementCounter()); | ||
} | ||
}); | ||
RelativeLayout.LayoutParams buttonParams = | ||
new RelativeLayout.LayoutParams( | ||
RelativeLayout.LayoutParams.WRAP_CONTENT, | ||
RelativeLayout.LayoutParams.WRAP_CONTENT); | ||
buttonParams.addRule(RelativeLayout.BELOW, clickCounter.getId()); | ||
buttonParams.addRule(RelativeLayout.CENTER_HORIZONTAL); | ||
rootLayout.addView(button, buttonParams); | ||
|
||
EditText textField = new EditText(this); | ||
textField.setId(4); | ||
textField.setTextSize(20); | ||
RelativeLayout.LayoutParams textFieldParams = | ||
new RelativeLayout.LayoutParams( | ||
RelativeLayout.LayoutParams.WRAP_CONTENT, | ||
RelativeLayout.LayoutParams.WRAP_CONTENT); | ||
textFieldParams.addRule(RelativeLayout.CENTER_HORIZONTAL); | ||
|
||
Button enterName = new Button(this); | ||
enterName.setId(5); | ||
enterName.setText("Enter"); | ||
enterName.setOnClickListener(new View.OnClickListener() { | ||
@Override | ||
public void onClick(View v) { | ||
welcomeMsg.setText(greetName(textField.getText().toString())); | ||
} | ||
}); | ||
RelativeLayout.LayoutParams enterNameParams = | ||
new RelativeLayout.LayoutParams( | ||
RelativeLayout.LayoutParams.WRAP_CONTENT, | ||
RelativeLayout.LayoutParams.WRAP_CONTENT); | ||
enterNameParams.addRule(RelativeLayout.CENTER_HORIZONTAL); | ||
enterNameParams.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM); | ||
rootLayout.addView(enterName, enterNameParams); | ||
|
||
textFieldParams.addRule(RelativeLayout.ABOVE, enterName.getId()); | ||
rootLayout.addView(textField, textFieldParams); | ||
|
||
setContentView(rootLayout); | ||
|
||
final String entryPointLibName = "AndroidSampleApp.dll"; | ||
welcomeMsg.setText("Running " + entryPointLibName + "..."); | ||
clickCounter.setText("Hello World!"); | ||
final Activity ctx = this; | ||
new Handler(Looper.getMainLooper()).postDelayed(new Runnable() { | ||
@Override | ||
public void run() { | ||
int retcode = MonoRunner.initialize(entryPointLibName, ctx); | ||
welcomeMsg.setText("Hello Android " + retcode + "!\nRunning on mono runtime\nUsing C#"); | ||
} | ||
}, 1000); | ||
} | ||
|
||
public native String incrementCounter(); | ||
|
||
public native String greetName(String text); | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
<?xml version="1.0" encoding="utf-8"?> | ||
<configuration> | ||
<packageSources> | ||
<add key="dotnet6" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet6/nuget/v3/index.json" /> | ||
</packageSources> | ||
</configuration> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
--- | ||
languages: | ||
- csharp | ||
products: | ||
- dotnet-core | ||
page_type: sample | ||
name: "Android Sample: Simple greeting and counter (C#)" | ||
description: "An Android application that contains an example of embedding the mono runtime to invoke unmanaged code with C#." | ||
urlFragment: "mono-android-csharp" | ||
--- | ||
|
||
# Android Sample: Simple greeting and counter (C#) | ||
|
||
In this sample, the mono runtime is used to invoke C unmanaged code (native-lib.c) from the C# managed side (AndroidSampleApp.cs) and vice versa. Then through the Java Native Interface (JNI), the java code (MainActivity.java) can call and be called by the C unamanged code. With the sample running, you can enter your name and click the corresponding button to modify the greeting message as well as clicking a button to increment a counter. | ||
|
||
[!NOTE] | ||
The purpose of this sample is to demonstrate the concept of building an Android application on top of the mono runtime. The mono runtime headers should be supplied through the build process. | ||
If you wish to use your own activity file, set `NativeMainSource` in `AndroidSampleApp.csproj` to point at your activity file, and the class is currently restricted to be called `MainActivity`. | ||
When running an activity in a non-UI thread, `FindClass` in `native-lib.c` will not work. | ||
|
||
## Sample Prerequisites | ||
|
||
- ANDROID_NDK & ANDROID_SDK (<https://github.com/dotnet/runtime/blob/master/docs/workflow/testing/libraries/testing-android.md> right under `Testing Libraries on Android`). | ||
- Android simulator API 21 or greater. | ||
- Dotnet sdk 6.0.100-alpha.1.20567.4 (Installation instructions in parent directory). | ||
|
||
## Building the sample | ||
|
||
The source code includes an MSBuild project file for C# (a _.csproj_ file) that targets .NET 6.0. After downloading the _.zip_ file, be sure to have the Android simulator open. Mdify the `TargetArchitecture` property in `AndroidSampleApp.csproj` to your simulator's architecture (x64, x86, arm, arm64). To run the sample, open the command line, navigate to the downloaded folder, and run `dotnet publish`. | ||
|
||
[!NOTE] | ||
To view the application's logs, use `<YOUR_ANDROID_SDK_ROOT>\platform-tools\adb logcat -d` after publishing. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
#include <jni.h> | ||
|
||
char *(*incrementHandlerPtr)(void); | ||
char *(*greetHandlerPtr)(const char *); | ||
|
||
JNIEXPORT jstring JNICALL | ||
Java_net_dot_MainActivity_incrementCounter( | ||
JNIEnv* env, | ||
jobject thisObject) { | ||
char *s = "Hello"; | ||
if (incrementHandlerPtr) | ||
s = incrementHandlerPtr(); | ||
return (*env) -> NewStringUTF(env, s); | ||
} | ||
|
||
JNIEXPORT jstring JNICALL | ||
Java_net_dot_MainActivity_greetName( | ||
JNIEnv* env, | ||
jobject thisObject, | ||
jstring string) { | ||
char *s = "Hello Android!\nRunning on mono runtime\nUsing C#"; | ||
const char *name = (*env)->GetStringUTFChars(env, string, NULL); | ||
if (greetHandlerPtr) | ||
s = greetHandlerPtr(name); | ||
return (*env) -> NewStringUTF(env, s); | ||
} | ||
|
||
static JavaVM *gJvm; | ||
|
||
JNIEXPORT jint JNICALL | ||
JNI_OnLoad(JavaVM *vm, void *reserved) | ||
{ | ||
(void)reserved; | ||
gJvm = vm; | ||
return JNI_VERSION_1_6; | ||
} | ||
|
||
static JNIEnv* GetJniEnv() | ||
{ | ||
JNIEnv *env; | ||
(*gJvm)->GetEnv(gJvm, (void**)&env, JNI_VERSION_1_6); | ||
if (env) | ||
return env; | ||
(*gJvm)->AttachCurrentThread(gJvm, &env, NULL); | ||
return env; | ||
} | ||
|
||
void | ||
androidRegisterCounterIncrement (void* ptr) | ||
{ | ||
incrementHandlerPtr = ptr; | ||
} | ||
|
||
void | ||
androidRegisterNameGreet (void* ptr) | ||
{ | ||
greetHandlerPtr = ptr; | ||
} | ||
|
||
int myNum() { | ||
JNIEnv* jniEnv = GetJniEnv(); | ||
|
||
jclass mainActivity = (*jniEnv)->FindClass (jniEnv, "net/dot/MainActivity"); | ||
jmethodID getNum = (*jniEnv)->GetStaticMethodID(jniEnv, mainActivity, "getNum", "()I"); | ||
jint val = (*jniEnv)->CallStaticIntMethod(jniEnv, mainActivity, getNum); | ||
return val; | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.