diff --git a/core/src/main/aidl/org/lsposed/lspd/models/ModuleConfig.aidl b/core/src/main/aidl/org/lsposed/lspd/models/ModuleConfig.aidl
index 0d663e09..cce31280 100644
--- a/core/src/main/aidl/org/lsposed/lspd/models/ModuleConfig.aidl
+++ b/core/src/main/aidl/org/lsposed/lspd/models/ModuleConfig.aidl
@@ -1,4 +1,5 @@
package org.lsposed.lspd.models;
parcelable ModuleConfig {
+ SharedMemory[] preLoadedDexes;
}
diff --git a/core/src/main/java/de/robv/android/xposed/XposedInit.java b/core/src/main/java/de/robv/android/xposed/XposedInit.java
index d32e7fba..ba5b1001 100644
--- a/core/src/main/java/de/robv/android/xposed/XposedInit.java
+++ b/core/src/main/java/de/robv/android/xposed/XposedInit.java
@@ -41,6 +41,7 @@ import android.content.res.XResources;
import android.os.Build;
import android.os.IBinder;
import android.os.Process;
+import android.os.SharedMemory;
import android.util.ArraySet;
import android.util.Log;
@@ -224,11 +225,12 @@ public final class XposedInit {
moduleList.forEach(module -> {
var apk = module.apk;
var name = module.name;
+ var dexes = module.config.preLoadedDexes;
if (loadedModules.contains(apk)) {
newLoadedApk.add(apk);
} else {
loadedModules.add(apk); // temporarily add it for XSharedPreference
- boolean loadSuccess = loadModule(name, apk);
+ boolean loadSuccess = loadModule(name, apk, dexes);
if (loadSuccess) {
newLoadedApk.add(apk);
}
@@ -361,7 +363,7 @@ public final class XposedInit {
* in assets/xposed_init.
*/
@SuppressLint("PrivateApi")
- private static boolean loadModule(String name, String apk) {
+ private static boolean loadModule(String name, String apk, SharedMemory[] dexes) {
Log.i(TAG, "Loading module " + name + " from " + apk);
if (!new File(apk).exists()) {
@@ -375,7 +377,7 @@ public final class XposedInit {
librarySearchPath.append(apk).append("!/lib/").append(abi).append(File.pathSeparator);
}
ClassLoader initLoader = XposedInit.class.getClassLoader();
- ClassLoader mcl = LspModuleClassLoader.loadApk(new File(apk), librarySearchPath.toString(), initLoader);
+ ClassLoader mcl = LspModuleClassLoader.loadApk(new File(apk), dexes, librarySearchPath.toString(), initLoader);
try {
if (mcl.loadClass(XposedBridge.class.getName()).getClassLoader() != initLoader) {
diff --git a/core/src/main/java/org/lsposed/lspd/service/ConfigManager.java b/core/src/main/java/org/lsposed/lspd/service/ConfigManager.java
index aa3d13dc..a5f0a09b 100644
--- a/core/src/main/java/org/lsposed/lspd/service/ConfigManager.java
+++ b/core/src/main/java/org/lsposed/lspd/service/ConfigManager.java
@@ -36,6 +36,7 @@ import android.os.SharedMemory;
import android.os.SystemClock;
import android.system.ErrnoException;
import android.system.Os;
+import android.system.OsConstants;
import android.util.Log;
import android.util.Pair;
@@ -46,6 +47,7 @@ import org.apache.commons.lang3.SerializationUtils;
import org.lsposed.lspd.BuildConfig;
import org.lsposed.lspd.models.Application;
import org.lsposed.lspd.models.Module;
+import org.lsposed.lspd.models.ModuleConfig;
import java.io.File;
import java.io.FileNotFoundException;
@@ -53,6 +55,7 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Serializable;
+import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.file.Files;
@@ -141,8 +144,6 @@ public class ConfigManager {
private final Handler cacheHandler;
- private final Map moduleDexes = new ConcurrentHashMap<>();
-
private long lastModuleCacheTime = 0;
private long requestModuleCacheTime = 0;
@@ -199,8 +200,13 @@ public class ConfigManager {
private final Map> cachedScope = new ConcurrentHashMap<>();
+ // apkPath, dexes
+ private final Map cachedDexes = new ConcurrentHashMap<>();
+
+ // appId, packageName
private final Map cachedModule = new ConcurrentHashMap<>();
+ // packageName, userId, group, key, value
private final Map, Map>> cachedConfig = new ConcurrentHashMap<>();
private void updateCaches(boolean sync) {
@@ -251,9 +257,12 @@ public class ConfigManager {
int pkgNameIdx = cursor.getColumnIndex("module_pkg_name");
while (cursor.moveToNext()) {
var module = new Module();
+ var config = new ModuleConfig();
+ var path = cursor.getString(apkPathIdx);
+ config.preLoadedDexes = getModuleDexes(path);
module.name = cursor.getString(pkgNameIdx);
- module.apk = cursor.getString(apkPathIdx);
- module.config = null;
+ module.apk = path;
+ module.config = config;
modules.add(module);
}
}
@@ -433,6 +442,7 @@ public class ConfigManager {
}
int pkgNameIdx = cursor.getColumnIndex("module_pkg_name");
int userIdIdx = cursor.getColumnIndex("user_id");
+ // packageName, userId, packageInfo
Map> modules = new HashMap<>();
Set obsoleteModules = new HashSet<>();
Set obsoleteScopes = new HashSet<>();
@@ -466,6 +476,7 @@ public class ConfigManager {
for (var obsoleteScope : obsoleteScopes) {
removeModuleScopeWithoutCache(obsoleteScope);
}
+ cleanModuleDexes();
}
Log.d(TAG, "cached modules");
for (int uid : cachedModule.keySet()) {
@@ -506,9 +517,11 @@ public class ConfigManager {
}
for (ProcessScope processScope : processesScope) {
var module = new Module();
+ var config = new ModuleConfig();
+ config.preLoadedDexes = getModuleDexes(apk_path);
module.name = module_pkg;
module.apk = apk_path;
- module.config = null;
+ module.config = config;
cachedScope.computeIfAbsent(processScope, ignored -> new LinkedList<>()).add(module);
if (module_pkg.equals(app.packageName)) {
var appId = processScope.uid % PER_USER_RANGE;
@@ -534,6 +547,45 @@ public class ConfigManager {
});
}
+ private SharedMemory[] loadModuleDexes(String path) {
+ var sharedMemories = new ArrayList();
+ try (var apkFile = new ZipFile(path)) {
+ int secondary = 2;
+ for (var dexFile = apkFile.getEntry("classes.dex"); dexFile != null;
+ dexFile = apkFile.getEntry("classes" + secondary + ".dex"), secondary++) {
+ try (var in = apkFile.getInputStream(dexFile)) {
+ var memory = SharedMemory.create(null, in.available());
+ var byteBuffer = memory.mapReadWrite();
+ Channels.newChannel(in).read(byteBuffer);
+ SharedMemory.unmap(byteBuffer);
+ memory.setProtect(OsConstants.PROT_READ);
+ sharedMemories.add(memory);
+ } catch (IOException | ErrnoException e) {
+ Log.w(TAG, "Can not load " + dexFile + " in " + path, e);
+ }
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "Can not open " + path, e);
+ }
+ return sharedMemories.toArray(new SharedMemory[0]);
+ }
+
+ private SharedMemory[] getModuleDexes(String path) {
+ return cachedDexes.computeIfAbsent(path, this::loadModuleDexes);
+ }
+
+ private void cleanModuleDexes() {
+ cachedDexes.entrySet().removeIf(entry -> {
+ var path = entry.getKey();
+ var dexes = entry.getValue();
+ if (!new File(path).exists()) {
+ Arrays.stream(dexes).parallel().forEach(SharedMemory::close);
+ return true;
+ }
+ return false;
+ });
+ }
+
// This is called when a new process created, use the cached result
public List getModulesForProcess(String processName, int uid) {
return isManager(uid) ? Collections.emptyList() : cachedScope.getOrDefault(new ProcessScope(processName, uid), Collections.emptyList());
diff --git a/core/src/main/java/org/lsposed/lspd/util/LspModuleClassLoader.java b/core/src/main/java/org/lsposed/lspd/util/LspModuleClassLoader.java
index c1550dc7..1a961b03 100644
--- a/core/src/main/java/org/lsposed/lspd/util/LspModuleClassLoader.java
+++ b/core/src/main/java/org/lsposed/lspd/util/LspModuleClassLoader.java
@@ -3,6 +3,7 @@ package org.lsposed.lspd.util;
import static de.robv.android.xposed.XposedBridge.TAG;
import android.os.Build;
+import android.os.SharedMemory;
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
@@ -15,15 +16,14 @@ import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.nio.ByteBuffer;
-import java.nio.channels.Channels;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
+import java.util.Objects;
import java.util.jar.JarFile;
import java.util.zip.ZipEntry;
-import java.util.zip.ZipFile;
import hidden.ByteBufferDexClassLoader;
@@ -177,35 +177,27 @@ public final class LspModuleClassLoader extends ByteBufferDexClassLoader {
}
public static LspModuleClassLoader loadApk(File apk,
+ SharedMemory[] dexes,
String librarySearchPath,
ClassLoader parent) {
- var byteBuffers = new ArrayList();
- try (var apkFile = new ZipFile(apk)) {
- int secondary = 2;
- for (var dexFile = apkFile.getEntry("classes.dex"); dexFile != null;
- dexFile = apkFile.getEntry("classes" + secondary + ".dex"), secondary++) {
- try (var in = apkFile.getInputStream(dexFile)) {
- var byteBuffer = ByteBuffer.allocate(in.available());
- byteBuffer.mark();
- Channels.newChannel(in).read(byteBuffer);
- byteBuffer.reset();
- byteBuffers.add(byteBuffer);
- } catch (IOException e) {
- Log.w(TAG, "Can not read " + dexFile + " in " + apk, e);
- }
+ var dexBuffers = Arrays.stream(dexes).parallel().map(dex -> {
+ try {
+ return dex.mapReadOnly();
+ } catch (ErrnoException e) {
+ Log.w(TAG, "Can not map " + dex, e);
+ return null;
}
- } catch (IOException e) {
- Log.e(TAG, "Can not open " + apk, e);
- }
- var dexBuffers = new ByteBuffer[byteBuffers.size()];
+ }).filter(Objects::nonNull).toArray(ByteBuffer[]::new);
+ LspModuleClassLoader cl;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
- return new LspModuleClassLoader(byteBuffers.toArray(dexBuffers),
- librarySearchPath, parent, apk.getAbsolutePath());
- } else {
- var cl = new LspModuleClassLoader(byteBuffers.toArray(dexBuffers),
+ cl = new LspModuleClassLoader(dexBuffers, librarySearchPath,
parent, apk.getAbsolutePath());
+ } else {
+ cl = new LspModuleClassLoader(dexBuffers, parent, apk.getAbsolutePath());
cl.initNativeLibraryDirs(librarySearchPath);
- return cl;
}
+ Arrays.stream(dexBuffers).parallel().forEach(SharedMemory::unmap);
+ Arrays.stream(dexes).parallel().forEach(SharedMemory::close);
+ return cl;
}
}