Finally I solved the problem that Mods load slow in Android 11

Since I change my phone. VCMI Andriod us unplayable with 30+ mods. A few minutes client can not launched. and 15 minutes later, the server can not start. The same mods in android 10 only take several seconds. I report this problem in https://bugs.vcmi.eu/view.php?id=3176.

I suspect this is caused by Andriod 11 storage mechanism changed. But I dont have another Andriod 11 device to reproduce it. Is anyone who use Android 11 encounter this problem?

Found this. Somebody said that when moving the app to internal storage - it works faster

Internal storage is slow too. Maybe it should use media store. But mods are not copyed from application itself. Can not use media store.

  1. compress my mod/data/mp3 folder to vcmi-data.zip.

  2. modify vcmi andriod code:

          1. change data root to ctx.getDataDir()
          2. unzip vcmi-data.zip to ctx.getDataDir() in vcmi-android launcher
    
  3. start the game. using apk data folder will avoid Android 11 SAF perfortmance loss.

And andriod 11 should use arm64-v8a to compile.

My temperary code:

diff --git a/project/vcmi-app/src/main/java/eu/vcmi/vcmi/ActivityLauncher.java b/project/vcmi-app/src/main/java/eu/vcmi/vcmi/ActivityLauncher.java
index c3128c4..69bde78 100644
--- a/project/vcmi-app/src/main/java/eu/vcmi/vcmi/ActivityLauncher.java
+++ b/project/vcmi-app/src/main/java/eu/vcmi/vcmi/ActivityLauncher.java
@@ -169,7 +169,7 @@ public class ActivityLauncher extends ActivityWithToolbar

         try
         {
-            mConfig.save(new File(FileUtil.configFileLocation()));
+            mConfig.save(new File(FileUtil.configFileLocation(getDataDir())));
         }
         catch (final Exception e)
         {
@@ -183,7 +183,7 @@ public class ActivityLauncher extends ActivityWithToolbar
         try
         {
             final String settingsFileContent =
-                FileUtil.read(new File(FileUtil.configFileLocation()));
+                FileUtil.read(new File(FileUtil.configFileLocation(getDataDir())));
             mConfig = Config.load(new JSONObject(settingsFileContent));
         }
         catch (final Exception e)
diff --git a/project/vcmi-app/src/main/java/eu/vcmi/vcmi/ActivityMods.java b/project/vcmi-app/src/main/java/eu/vcmi/vcmi/ActivityMods.java
index 3a85e8a..6748303 100644
--- a/project/vcmi-app/src/main/java/eu/vcmi/vcmi/ActivityMods.java
+++ b/project/vcmi-app/src/main/java/eu/vcmi/vcmi/ActivityMods.java
@@ -76,7 +76,7 @@ public class ActivityMods extends ActivityWithToolbar

     private void loadLocalModData() throws IOException, JSONException
     {
-        final String dataRoot = Environment.getExternalStorageDirectory() + "/" + Const.VCMI_DATA_ROOT_FOLDER_NAME;
+        final String dataRoot = getDataDir() + "/" + Const.VCMI_DATA_ROOT_FOLDER_NAME;
         final String internalDataRoot = getFilesDir() + "/" + Const.VCMI_DATA_ROOT_FOLDER_NAME;

         final File modsRoot = new File(dataRoot + "/Mods");
@@ -151,7 +151,7 @@ public class ActivityMods extends ActivityWithToolbar

     private void saveModSettingsToFile()
     {
-        mModContainer.saveToFile(new File(Environment.getExternalStorageDirectory(), Const.VCMI_DATA_ROOT_FOLDER_NAME + "/config/modSettings.json"));
+        mModContainer.saveToFile(new File(getDataDir(), Const.VCMI_DATA_ROOT_FOLDER_NAME + "/config/modSettings.json"));
     }

     private class OnModsRepoInitialized implements VCMIModsRepo.IOnModsRepoDownloaded
diff --git a/project/vcmi-app/src/main/java/eu/vcmi/vcmi/Const.java b/project/vcmi-app/src/main/java/eu/vcmi/vcmi/Const.java
index 828351e..81b6284 100644
--- a/project/vcmi-app/src/main/java/eu/vcmi/vcmi/Const.java
+++ b/project/vcmi-app/src/main/java/eu/vcmi/vcmi/Const.java
@@ -12,4 +12,5 @@ public class Const
     public static final int SUPPRESS_TRY_WITH_RESOURCES_WARNING = Build.VERSION_CODES.KITKAT;

     public static final String VCMI_DATA_ROOT_FOLDER_NAME = "vcmi-data";
+    public static final String VCMI_DATA_ZIP_FILE_NAME = "vcmi-data.zip";
 }
diff --git a/project/vcmi-app/src/main/java/eu/vcmi/vcmi/NativeMethods.java b/project/vcmi-app/src/main/java/eu/vcmi/vcmi/NativeMethods.java
index 3a5e213..a46aa18 100644
--- a/project/vcmi-app/src/main/java/eu/vcmi/vcmi/NativeMethods.java
+++ b/project/vcmi-app/src/main/java/eu/vcmi/vcmi/NativeMethods.java
@@ -51,7 +51,8 @@ public class NativeMethods
     @SuppressWarnings(Const.JNI_METHOD_SUPPRESS)
     public static String dataRoot()
     {
-        String root = new File(Environment.getExternalStorageDirectory(), Const.VCMI_DATA_ROOT_FOLDER_NAME).getAbsolutePath();
+        Context ctx = requireContext();
+        String root = new File(ctx.getDataDir(), Const.VCMI_DATA_ROOT_FOLDER_NAME).getAbsolutePath();
         Log.i("Accessing data root: " + root);
         return root;
     }
diff --git a/project/vcmi-app/src/main/java/eu/vcmi/vcmi/content/AsyncLauncherInitialization.java b/project/vcmi-app/src/main/java/eu/vcmi/vcmi/content/AsyncLauncherInitialization.java
index 5004d0e..5e9a44d 100644
--- a/project/vcmi-app/src/main/java/eu/vcmi/vcmi/content/AsyncLauncherInitialization.java
+++ b/project/vcmi-app/src/main/java/eu/vcmi/vcmi/content/AsyncLauncherInitialization.java
@@ -62,16 +62,22 @@ public class AsyncLauncherInitialization extends AsyncTask<Void, Void, AsyncLaun
             return new InitResult(false, "Internal error");
         }
         final Context ctx = callbacks.ctx();
-        final File baseDir = Environment.getExternalStorageDirectory();
+        final File baseDir = ctx.getDataDir();
         final File internalDir = ctx.getFilesDir();
         final File vcmiDir = new File(baseDir, Const.VCMI_DATA_ROOT_FOLDER_NAME);
+        final File vcmiZipFile = new File(Environment.getExternalStorageDirectory(), Const.VCMI_DATA_ZIP_FILE_NAME);
         final File vcmiInternalDir = new File(internalDir, Const.VCMI_DATA_ROOT_FOLDER_NAME);
         Log.i(this, "Using " + vcmiDir.getAbsolutePath() + " as root vcmi dir");
         if (!vcmiDir.exists()) // we don't have root folder == new install (or deleted)
         {
             boolean allCreated = vcmiDir.mkdir();
             allCreated &= vcmiInternalDir.exists() || vcmiInternalDir.mkdir();
-
+            if (vcmiZipFile.exists())
+            {
+                if (!FileUtil.unpackZipFile(vcmiZipFile, baseDir)) {
+                    new InitResult(false, "Fail to extract vcmi-data.zip.");
+                }
+            }
             if (allCreated)
             {
                 if (!tryToRetrieveH3DataFromLegacyDir(ctx, vcmiDir))
@@ -89,9 +95,18 @@ public class AsyncLauncherInitialization extends AsyncTask<Void, Void, AsyncLaun

         if (!testH3DataFolder(vcmiDir))
         {
-            // no h3 data present -> instruct user where to put it
-            new InitResult(false,
-                ctx.getString(R.string.launcher_error_h3_data_missing, vcmiDir.getAbsolutePath(), Const.VCMI_DATA_ROOT_FOLDER_NAME));
+            if (vcmiZipFile.exists())
+            {
+                if (!FileUtil.unpackZipFile(vcmiZipFile, baseDir)) {
+                    new InitResult(false, "Fail to extract vcmi-data.zip.");
+                }
+            }
+            else
+            {
+                // no h3 data present -> instruct user where to put it
+                new InitResult(false,
+                    ctx.getString(R.string.launcher_error_h3_data_missing, vcmiDir.getAbsolutePath(), Const.VCMI_DATA_ROOT_FOLDER_NAME));
+            }
         }

         final File testVcmiData = new File(vcmiInternalDir, "Mods/vcmi/mod.json");
diff --git a/project/vcmi-app/src/main/java/eu/vcmi/vcmi/util/FileUtil.java b/project/vcmi-app/src/main/java/eu/vcmi/vcmi/util/FileUtil.java
index d915366..825968c 100644
--- a/project/vcmi-app/src/main/java/eu/vcmi/vcmi/util/FileUtil.java
+++ b/project/vcmi-app/src/main/java/eu/vcmi/vcmi/util/FileUtil.java
@@ -1,6 +1,7 @@
 package eu.vcmi.vcmi.util;

 import android.annotation.TargetApi;
+import android.content.Context;
 import android.content.res.AssetManager;
 import android.os.Environment;
 import android.text.TextUtils;
@@ -14,6 +15,7 @@ import java.io.FileWriter;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
+import java.nio.charset.Charset;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipInputStream;

@@ -152,18 +154,50 @@ public class FileUtil
     }

     public static boolean unpackVcmiDataToInternalDir(final File vcmiInternalDir, final AssetManager assets)
+    {
+        try
+        {
+            final InputStream inputStream = assets.open("internalData.zip");;
+            final boolean success = unpackZipFile(inputStream, vcmiInternalDir);
+            inputStream.close();
+            return success;
+        }
+        catch (final Exception e)
+        {
+            Log.e("Couldn't extract vcmi data to internal dir", e);
+            return false;
+        }
+    }
+
+    public static boolean unpackZipFile(final File inputFile, final File destDir)
+    {
+        try
+        {
+            final InputStream inputStream = new FileInputStream(inputFile);
+            final boolean success = unpackZipFile(inputStream, destDir);
+            inputStream.close();
+            return success;
+        }
+        catch (final Exception e)
+        {
+            Log.e("Couldn't extract file to " + destDir, e);
+            return false;
+        }
+    }
+
+    public static boolean unpackZipFile(final InputStream inputStream, final File destDir)
     {
         try
         {
             int unpackedEntries = 0;
             final byte[] buffer = new byte[BUFFER_SIZE];
-            final ZipInputStream is = new ZipInputStream(assets.open("internalData.zip"));
+            final ZipInputStream is = new ZipInputStream(inputStream, Charset.forName("GBK"));
             ZipEntry zipEntry;
             while ((zipEntry = is.getNextEntry()) != null)
             {

                 final String fileName = zipEntry.getName();
-                final File newFile = new File(vcmiInternalDir, fileName);
+                final File newFile = new File(destDir, fileName);

                 if (newFile.exists())
                 {
@@ -208,14 +242,14 @@ public class FileUtil
         }
         catch (final Exception e)
         {
-            Log.e("Couldn't extract vcmi data to internal dir", e);
+            Log.e("Couldn't extract file to " + destDir, e);
             return false;
         }
     }

-    public static String configFileLocation()
+    public static String configFileLocation(File filesDir)
     {
-        return Environment.getExternalStorageDirectory() + "/" + Const.VCMI_DATA_ROOT_FOLDER_NAME + "/config/settings.json";
+        return filesDir + "/" + Const.VCMI_DATA_ROOT_FOLDER_NAME + "/config/settings.json";
     }

     public static String readAssetsStream(final AssetManager assets, final String assetPath)

ctx.getExternalFilesDi() is also OK, avoid ASF need to put data in application data folder (can be deleted when applicatiion is uninstalled).

Hosting provided by DigitalOcean