diff --git a/Documentation/guides/extract-native-libraries.md b/Documentation/guides/extract-native-libraries.md
new file mode 100644
index 00000000000..b5e7de01b7f
--- /dev/null
+++ b/Documentation/guides/extract-native-libraries.md
@@ -0,0 +1,49 @@
+# Installation of Native Libraries
+
+An Android `.apk` file can contain native libraries for one or more
+architectures. Historically, all native libraries for the target
+device are extracted at `.apk` installation time. This would result
+in *two* copies of native libraries on the target device: a set of
+native libraries which are stored compressed within the `.apk`, and
+a separate set of native libraries on the Android target filesystem.
+
+Starting with Android v6.0 (API-23), Android added an ability for
+native libraries to be stored *uncompressed* within the `.apk` along
+with the ability to load those native libraries *from* the `.apk`
+without requiring a separate filesystem copy of the native libraries.
+
+Android Things *requires* this new mechanism; `.apk` files installed
+on Android Things will no longer have *any* native libraries extracted.
+
+As a result, the `.apk` will be *larger*, because the native
+libraries are stored uncompressed within the `.apk`, but the
+install size will be *smaller*, because there isn't a second "copy"
+of the native libraries (one compressed in the `.apk`, one outside
+of the `.apk`).
+
+On Android versions older than Android v6.0, the native libraries
+will continue to be extracted during `.apk` installation.
+
+In order to indicate to Android v6.0 and later that native libraries
+do not need to be extracted, the
+[`//application/@android:extractNativeLibs`][extractNativeLibs]
+attribute within `AndroidManifest.xml` must be set to `false.`
+
+[extractNativeLibs]: https://developer.android.com/guide/topics/manifest/application-element#extractNativeLibs
+
+## OSS Implementation Details
+
+When `AndroidManifest.xml` contains an XML attribute matching
+`//application[@android:extractNativeLibs='false']`, the
+Xamarin.Android build system will do the following:
+
+ 1. The `$(AndroidStoreUncompressedFileExtensions)` MSBuild property
+ will be automatically updated to contain the `.so` file
+ extension, causing native libraries to be stored uncompressed
+ within the `.apk`.
+
+ 2. The `__XA_DSO_IN_APK` environment variable will be set within the
+ created `.apk` file with the value of `1`, indicating to
+ the app that native libraries should be loaded from the `.apk`
+ itself instead of from the filesystem.
+
diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs
index 8ab08c7d297..fd5bcb37901 100644
--- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs
@@ -2,9 +2,9 @@
using System;
using System.Collections.Generic;
-using System.Diagnostics;
using System.IO;
using System.Linq;
+using System.Xml.Linq;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using MonoDroid.Utils;
@@ -68,6 +68,11 @@ public class GenerateJavaStubs : Task
public string ApplicationJavaClass { get; set; }
+ ///
+ /// If specified, we need to cache the value of EmbeddedDSOsEnabled=True for incremental builds
+ ///
+ public string CacheFile { get; set; }
+
public override bool Execute ()
{
try {
@@ -223,6 +228,22 @@ void Run (DirectoryAssemblyResolver res)
MonoAndroidHelper.CopyIfStreamChanged (stream, MergedAndroidManifestOutput);
}
+ // Create the CacheFile if needed
+ if (!string.IsNullOrEmpty (CacheFile)) {
+ bool extractNativeLibraries = manifest.ExtractNativeLibraries ();
+ if (!extractNativeLibraries) {
+ //We need to write the value to a file, if _GenerateJavaStubs is skipped on incremental builds
+ var document = new XDocument (
+ new XDeclaration ("1.0", "UTF-8", null),
+ new XElement ("Properties", new XElement (nameof (ReadJavaStubsCache.EmbeddedDSOsEnabled), "True"))
+ );
+ document.SaveIfChanged (CacheFile);
+ } else {
+ //Delete the file otherwise, since we only need to specify when EmbeddedDSOsEnabled=True
+ File.Delete (CacheFile);
+ }
+ }
+
// Create additional runtime provider java sources.
string providerTemplateFile = UseSharedRuntime ? "MonoRuntimeProvider.Shared.java" : "MonoRuntimeProvider.Bundled.java";
string providerTemplate = GetResource (providerTemplateFile);
diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/ReadJavaStubsCache.cs b/src/Xamarin.Android.Build.Tasks/Tasks/ReadJavaStubsCache.cs
new file mode 100644
index 00000000000..e5b1e120db6
--- /dev/null
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/ReadJavaStubsCache.cs
@@ -0,0 +1,28 @@
+using Microsoft.Build.Framework;
+using Microsoft.Build.Utilities;
+using System.IO;
+using System.Xml.Linq;
+
+namespace Xamarin.Android.Tasks
+{
+ public class ReadJavaStubsCache : Task
+ {
+ [Required]
+ public string CacheFile { get; set; }
+
+ [Output]
+ public bool EmbeddedDSOsEnabled { get; set; }
+
+ public override bool Execute ()
+ {
+ if (File.Exists (CacheFile)) {
+ var doc = XDocument.Load (CacheFile);
+ string text = doc.Element ("Properties")?.Element (nameof (EmbeddedDSOsEnabled))?.Value;
+ if (bool.TryParse (text, out bool value))
+ EmbeddedDSOsEnabled = value;
+ }
+
+ return !Log.HasLoggedErrors;
+ }
+ }
+}
diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/PackagingTest.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/PackagingTest.cs
index 873884083f0..80ace65b602 100644
--- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/PackagingTest.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/PackagingTest.cs
@@ -195,6 +195,53 @@ public void CheckIncludedNativeLibraries ([Values (true, false)] bool compressNa
}
}
+ [Test]
+ public void EmbeddedDSOs ()
+ {
+ var proj = new XamarinAndroidApplicationProject ();
+ proj.AndroidManifest = $@"
+
+
+
+
+";
+
+ using (var b = CreateApkBuilder (Path.Combine ("temp", TestName))) {
+ Assert.IsTrue (b.Build (proj), "first build should have succeeded");
+
+ var apk = Path.Combine (Root, b.ProjectDirectory,
+ proj.IntermediateOutputPath, "android", "bin", "UnnamedProject.UnnamedProject.apk");
+ AssertEmbeddedDSOs (apk);
+
+ //Delete the apk & build again
+ File.Delete (apk);
+ Assert.IsTrue (b.Build (proj), "second build should have succeeded");
+ AssertEmbeddedDSOs (apk);
+ }
+ }
+
+ void AssertEmbeddedDSOs (string apk)
+ {
+ FileAssert.Exists (apk);
+
+ using (var zip = ZipHelper.OpenZip (apk)) {
+ foreach (var entry in zip) {
+ if (entry.FullName.EndsWith (".so")) {
+ Assert.AreEqual (entry.Size, entry.CompressedSize, $"`{entry.FullName}` should be uncompressed!");
+ } else if (entry.FullName == "environment") {
+ using (var stream = new MemoryStream ()) {
+ entry.Extract (stream);
+ stream.Position = 0;
+ using (var reader = new StreamReader (stream)) {
+ string environment = reader.ReadToEnd ();
+ StringAssert.Contains ("__XA_DSO_IN_APK=1", environment, "`__XA_DSO_IN_APK=1` should be set via @(AndroidEnvironment)");
+ }
+ }
+ }
+ }
+ }
+ }
+
[Test]
public void ExplicitPackageNamingPolicy ()
{
diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ManifestDocument.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ManifestDocument.cs
index aab7e8720d4..8524dccb6ed 100644
--- a/src/Xamarin.Android.Build.Tasks/Utilities/ManifestDocument.cs
+++ b/src/Xamarin.Android.Build.Tasks/Utilities/ManifestDocument.cs
@@ -644,6 +644,20 @@ bool IsMainLauncher (XElement intentFilter)
intentFilter.Elements (entry.Key).Any (e => ((string) e.Attribute (attName) == entry.Value)));
}
+ ///
+ /// Returns the value of //application/@android:extractNativeLibs.
+ ///
+ public bool ExtractNativeLibraries ()
+ {
+ string text = app?.Attribute (androidNs + "extractNativeLibs")?.Value;
+ if (bool.TryParse (text, out bool value)) {
+ return value;
+ }
+
+ // If android:extractNativeLibs is omitted, returns true.
+ return true;
+ }
+
XElement ActivityFromTypeDefinition (TypeDefinition type, string name, int targetSdkVersion)
{
if (name.StartsWith ("_"))
diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj
index f16e8a78db9..3218846fe5b 100644
--- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj
+++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj
@@ -122,6 +122,7 @@
+
diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets
index bf4afabcdc2..89a4183e6bb 100755
--- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets
+++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets
@@ -91,6 +91,7 @@ Copyright (C) 2011-2012 Xamarin. All rights reserved.
+
@@ -2285,7 +2286,7 @@ because xbuild doesn't support framework reference assemblies.
+ Outputs="$(_AndroidStampDirectory)_GenerateJavaStubs.stamp">
+ AcwMapFile="$(_AcwMapFile)"
+ CacheFile="$(IntermediateOutputPath)javastubs.cache">
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ .so;$(AndroidStoreUncompressedFileExtensions)
+
-
+
+
-
+
+ <_GetAddOnPlatformLibrariesDependsOn>
+ _GenerateJavaStubs;
+ _ReadJavaStubsCache;
+ _SetupEmbeddedDSOs;
+
+
+
+
-
diff --git a/tests/CodeGen-Binding/Xamarin.Android.McwGen-Tests/Jars/.gitignore b/tests/CodeGen-Binding/Xamarin.Android.McwGen-Tests/Jars/.gitignore
new file mode 100644
index 00000000000..d392f0e82c4
--- /dev/null
+++ b/tests/CodeGen-Binding/Xamarin.Android.McwGen-Tests/Jars/.gitignore
@@ -0,0 +1 @@
+*.jar
diff --git a/tests/EmbeddedDSOs/EmbeddedDSO/EmbeddedDSO.csproj b/tests/EmbeddedDSOs/EmbeddedDSO/EmbeddedDSO.csproj
index 81d42105437..6c456feb415 100644
--- a/tests/EmbeddedDSOs/EmbeddedDSO/EmbeddedDSO.csproj
+++ b/tests/EmbeddedDSOs/EmbeddedDSO/EmbeddedDSO.csproj
@@ -18,11 +18,6 @@
true
false
armeabi-v7a;x86
-
-
- .so
diff --git a/tests/EmbeddedDSOs/EmbeddedDSO/Environment.txt b/tests/EmbeddedDSOs/EmbeddedDSO/Environment.txt
index 966b2ab27fb..ba117b60829 100644
--- a/tests/EmbeddedDSOs/EmbeddedDSO/Environment.txt
+++ b/tests/EmbeddedDSOs/EmbeddedDSO/Environment.txt
@@ -2,4 +2,3 @@ debug.mono.debug=1
MONO_LOG_LEVEL=debug
MONO_LOG_MASK=asm
MONO_XDEBUG=1
-__XA_DSO_IN_APK=1