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).

I included your ideas in the latest Android build. It is available only on github for now. Soon will find a way to publish it to our downloads.

1 Like