feat: restore IntegrApplicationService and refactor RemoteService

LSPApplication 現在會根據情況選擇使用 IntegrApplicationService 或 NeoLocalApplicationService(是否 useManager 和安裝包内是否有集成模組安裝包)。
重構 RemoteApplicationService,簡化連接流程,移除死亡監聽(NeoLocal用不到了)。
This commit is contained in:
NkBe 2025-11-19 20:38:03 +08:00
parent 9588a87814
commit 83817fba3c
No known key found for this signature in database
GPG Key ID: 525137026FF031DF
3 changed files with 212 additions and 126 deletions

View File

@ -20,6 +20,7 @@ import org.json.JSONArray;
import org.json.JSONObject; import org.json.JSONObject;
import org.lsposed.lspatch.loader.util.FileUtils; import org.lsposed.lspatch.loader.util.FileUtils;
import org.lsposed.lspatch.loader.util.XLog; import org.lsposed.lspatch.loader.util.XLog;
import org.lsposed.lspatch.service.IntegrApplicationService;
import org.lsposed.lspatch.service.NeoLocalApplicationService; import org.lsposed.lspatch.service.NeoLocalApplicationService;
import org.lsposed.lspatch.service.RemoteApplicationService; import org.lsposed.lspatch.service.RemoteApplicationService;
import org.lsposed.lspatch.share.PatchConfig; import org.lsposed.lspatch.share.PatchConfig;
@ -71,6 +72,15 @@ public class LSPApplication {
return (android.os.Process.myUid() % PER_USER_RANGE) >= FIRST_APP_ZYGOTE_ISOLATED_UID; return (android.os.Process.myUid() % PER_USER_RANGE) >= FIRST_APP_ZYGOTE_ISOLATED_UID;
} }
private static boolean hasEmbeddedModules(Context context) {
try {
String[] list = context.getAssets().list("lspatch/modules");
return list != null && list.length > 0;
} catch (IOException e) {
return false;
}
}
public static void onLoad() throws RemoteException, IOException { public static void onLoad() throws RemoteException, IOException {
if (isIsolated()) { if (isIsolated()) {
XLog.d(TAG, "Skip isolated process"); XLog.d(TAG, "Skip isolated process");
@ -84,7 +94,7 @@ public class LSPApplication {
} }
Log.d(TAG, "Initialize service client"); Log.d(TAG, "Initialize service client");
ILSPApplicationService service; ILSPApplicationService service = null;
if (config.useManager) { if (config.useManager) {
try { try {
@ -100,14 +110,24 @@ public class LSPApplication {
SharedPreferences shared = context.getSharedPreferences("npatch", Context.MODE_PRIVATE); SharedPreferences shared = context.getSharedPreferences("npatch", Context.MODE_PRIVATE);
shared.edit().putString("modules", moduleArr.toString()).apply(); shared.edit().putString("modules", moduleArr.toString()).apply();
Log.i(TAG, "Success update module scope from Manager"); Log.i(TAG, "Success update module scope from Manager");
}catch (Exception e){
Log.e(TAG, "Failed to connect to manager, fallback to fixed local service (NLAS)"); } catch (Throwable e) {
service = new NeoLocalApplicationService(context); Log.w(TAG, "Failed to connect to manager: " + e.getMessage());
service = null;
} }
}
if (service == null) {
if (hasEmbeddedModules(context)) {
Log.i(TAG, "Using Integrated Service (Embedded Modules Found)");
service = new IntegrApplicationService(context);
} else { } else {
Log.i(TAG, "Manager is disabled, using remote service (NLAS)"); Log.i(TAG, "Using NeoLocal Service (Cached Config)");
service = new NeoLocalApplicationService(context); service = new NeoLocalApplicationService(context);
} }
}
Startup.initXposed(false, ActivityThread.currentProcessName(), context.getApplicationInfo().dataDir, service); Startup.initXposed(false, ActivityThread.currentProcessName(), context.getApplicationInfo().dataDir, service);
Startup.bootstrapXposed(); Startup.bootstrapXposed();

View File

@ -0,0 +1,104 @@
package org.lsposed.lspatch.service;
import android.content.Context;
import android.os.Environment;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.util.Log;
import org.lsposed.lspatch.loader.util.FileUtils;
import org.lsposed.lspatch.share.Constants;
import org.lsposed.lspatch.util.ModuleLoader;
import org.lsposed.lspd.models.Module;
import org.lsposed.lspd.service.ILSPApplicationService;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
public class IntegrApplicationService extends ILSPApplicationService.Stub {
private static final String TAG = "NPatch";
private final List<Module> modules = new ArrayList<>();
public IntegrApplicationService(Context context) {
try {
String[] assetsList = context.getAssets().list("lspatch/modules");
if (assetsList == null || assetsList.length == 0) {
return;
}
for (var name : assetsList) {
if (name == null || name.length() <= 4) continue;
String packageName = name.substring(0, name.length() - 4);
String modulePath = context.getCacheDir() + "/npatch/" + packageName + "/";
String cacheApkPath;
try (ZipFile sourceFile = new ZipFile(context.getPackageResourcePath())) {
ZipEntry entry = sourceFile.getEntry(Constants.EMBEDDED_MODULES_ASSET_PATH + name);
if (entry == null) {
Log.w(TAG, "Skipping module (entry not found in APK): " + name);
continue;
}
cacheApkPath = modulePath + entry.getCrc() + ".apk";
}
if (!Files.exists(Paths.get(cacheApkPath))) {
Log.i(TAG, "Extracting embedded module: " + packageName);
FileUtils.deleteFolderIfExists(Paths.get(modulePath));
Files.createDirectories(Paths.get(modulePath));
try (var is = context.getAssets().open("lspatch/modules/" + name)) {
Files.copy(is, Paths.get(cacheApkPath));
}
}
var module = new Module();
module.apkPath = cacheApkPath;
module.packageName = packageName;
module.file = ModuleLoader.loadModule(cacheApkPath);
modules.add(module);
}
} catch (IOException e) {
Log.e(TAG, "Error when initializing IntegrApplicationServiceClient", e);
}
}
@Override
public List<Module> getLegacyModulesList() throws RemoteException {
return modules;
}
@Override
public List<Module> getModulesList() throws RemoteException {
return new ArrayList<>();
}
@Override
public String getPrefsPath(String packageName) throws RemoteException {
return "/data/data/" + packageName + "/shared_prefs/";
}
@Override
public ParcelFileDescriptor requestInjectedManagerBinder(List<IBinder> binder) throws RemoteException {
return null;
}
@Override
public IBinder asBinder() {
return this;
}
@Override
public boolean isLogMuted() throws RemoteException {
return false;
}
}

View File

@ -19,9 +19,9 @@ import org.lsposed.lspatch.share.Constants;
import org.lsposed.lspd.models.Module; import org.lsposed.lspd.models.Module;
import org.lsposed.lspd.service.ILSPApplicationService; import org.lsposed.lspd.service.ILSPApplicationService;
import java.io.Closeable;
import java.io.File; import java.io.File;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
@ -29,143 +29,105 @@ import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
public class RemoteApplicationService implements ILSPApplicationService, IBinder.DeathRecipient, Closeable { public class RemoteApplicationService implements ILSPApplicationService {
private static final String TAG = "NPatch"; private static final String TAG = "NPatch";
private static final String MODULE_SERVICE = "org.lsposed.lspatch.manager.ModuleService"; private static final String MODULE_SERVICE = "org.lsposed.lspatch.manager.ModuleService";
private static final int CONNECTION_TIMEOUT_SEC = 1;
private volatile ILSPApplicationService mService; private volatile ILSPApplicationService service;
private final Context mContext;
private final ServiceConnection mConnection;
private HandlerThread mHandlerThread;
@SuppressLint("DiscouragedPrivateApi") @SuppressLint("DiscouragedPrivateApi")
public RemoteApplicationService(Context context) throws RemoteException { public RemoteApplicationService(Context context) throws RemoteException {
this.mContext = context.getApplicationContext(); try {
Intent intent = new Intent()
var intent = new Intent()
.setComponent(new ComponentName(Constants.MANAGER_PACKAGE_NAME, MODULE_SERVICE)) .setComponent(new ComponentName(Constants.MANAGER_PACKAGE_NAME, MODULE_SERVICE))
.putExtra("packageName", mContext.getPackageName()); .putExtra("packageName", context.getPackageName());
var latch = new CountDownLatch(1);
mConnection = new ServiceConnection() { CountDownLatch latch = new CountDownLatch(1);
ServiceConnection conn = new ServiceConnection() {
@Override @Override
public void onServiceConnected(ComponentName name, IBinder binder) { public void onServiceConnected(ComponentName name, IBinder binder) {
Log.i(TAG, "Manager binder received"); Log.i(TAG, "Manager binder received");
mService = ILSPApplicationService.Stub.asInterface(binder); service = Stub.asInterface(binder);
try {
// 註冊 Binder 死亡通知
binder.linkToDeath(RemoteApplicationService.this, 0);
} catch (RemoteException e) {
Log.e(TAG, "Failed to link to death", e);
mService = null;
}
latch.countDown(); latch.countDown();
} }
@Override @Override
public void onServiceDisconnected(ComponentName name) { public void onServiceDisconnected(ComponentName name) {
Log.e(TAG, "Manager service died"); Log.e(TAG, "Manager service died");
mService = null; service = null;
} }
}; };
Log.i(TAG, "Request manager binder"); Log.i(TAG, "Requesting manager binder...");
mHandlerThread = null; // Initialize member
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
mContext.bindService(intent, Context.BIND_AUTO_CREATE, Executors.newSingleThreadExecutor(), mConnection); context.bindService(intent, Context.BIND_AUTO_CREATE, Executors.newSingleThreadExecutor(), conn);
} else { } else {
// 29 創建一個臨時的 HandlerThread HandlerThread handlerThread = new HandlerThread("RemoteApplicationService");
mHandlerThread = new HandlerThread("RemoteApplicationService"); handlerThread.start();
mHandlerThread.start(); Handler handler = new Handler(handlerThread.getLooper());
var handler = new Handler(mHandlerThread.getLooper());
var contextImplClass = context.getClass();
var getUserMethod = contextImplClass.getMethod("getUser");
var bindServiceAsUserMethod = contextImplClass.getDeclaredMethod(
"bindServiceAsUser", Intent.class, ServiceConnection.class, int.class, Handler.class, UserHandle.class);
var userHandle = (UserHandle) getUserMethod.invoke(context);
bindServiceAsUserMethod.invoke(context, intent, mConnection, Context.BIND_AUTO_CREATE, handler, userHandle);
}
boolean success = latch.await(3, TimeUnit.SECONDS);
if (!success) { Class<?> contextImplClass = context.getClass();
// Attempt to unbind the service before throwing a timeout for cleanup Method getUserMethod = contextImplClass.getMethod("getUser");
try { UserHandle userHandle = (UserHandle) getUserMethod.invoke(context);
mContext.unbindService(mConnection);
} catch (IllegalArgumentException | IllegalStateException ignored) { Method bindServiceAsUserMethod = contextImplClass.getDeclaredMethod(
// Ignored "bindServiceAsUser",
Intent.class,
ServiceConnection.class,
int.class,
Handler.class,
UserHandle.class
);
bindServiceAsUserMethod.invoke(context, intent, conn, Context.BIND_AUTO_CREATE, handler, userHandle);
} }
boolean success = latch.await(CONNECTION_TIMEOUT_SEC, TimeUnit.SECONDS);
if (!success) {
throw new TimeoutException("Bind service timeout"); throw new TimeoutException("Bind service timeout");
} }
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException | } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException |
InterruptedException | TimeoutException e) { InterruptedException | TimeoutException e) {
var r = new RemoteException("Failed to get manager binder");
r.initCause(e); RemoteException remoteException = new RemoteException("Failed to get manager binder");
throw r; remoteException.initCause(e);
} finally { throw remoteException;
} }
} }
private ILSPApplicationService getServiceOrThrow() throws RemoteException { @Override
ILSPApplicationService service = mService; public List<Module> getLegacyModulesList() throws RemoteException {
if (service == null) { return service == null ? new ArrayList<>() : service.getLegacyModulesList();
throw new RemoteException("Manager service is not connected or has died.");
} }
return service;
@Override
public List<Module> getModulesList() throws RemoteException {
return service == null ? new ArrayList<>() : service.getModulesList();
}
@Override
public String getPrefsPath(String packageName) {
return new File(Environment.getDataDirectory(), "data/" + packageName + "/shared_prefs/")
.getAbsolutePath();
}
@Override
public IBinder asBinder() {
return service == null ? null : service.asBinder();
}
@Override
public ParcelFileDescriptor requestInjectedManagerBinder(List<IBinder> binder) {
return null;
} }
@Override @Override
public boolean isLogMuted() throws RemoteException { public boolean isLogMuted() throws RemoteException {
return false; return false;
} }
@Override
public List<Module> getLegacyModulesList() throws RemoteException {
return getServiceOrThrow().getLegacyModulesList();
}
@Override
public List<Module> getModulesList() throws RemoteException {
return getServiceOrThrow().getModulesList();
}
@Override
public String getPrefsPath(String packageName) throws RemoteException {
return getServiceOrThrow().getPrefsPath(packageName);
}
@Override
public IBinder asBinder() {
return mService == null ? null : mService.asBinder();
}
@Override
public ParcelFileDescriptor requestInjectedManagerBinder(List<IBinder> binder) throws RemoteException {
// return getServiceOrThrow().requestInjectedManagerBinder(binder);
return null;
}
@Override
public void binderDied() {
Log.e(TAG, "Manager service binder has died.");
if (mService != null) {
mService.asBinder().unlinkToDeath(this, 0);
}
mService = null;
}
@Override
public void close() {
if (mService != null) {
mService.asBinder().unlinkToDeath(this, 0);
}
try {
// 解綁服務
mContext.unbindService(mConnection);
} catch (IllegalArgumentException e) {
Log.w(TAG, "Service was not registered or already unbound: " + e.getMessage());
}
mService = null;
}
} }