refactor: improve module loading and remote service

優化本地模組加載流程,修復模組路徑丟失時自動恢復,並加強日誌提示。重構 RemoteApplicationService,實現 Binder 死亡監聽與資源釋放,提升遠端服務連接穩定性與錯誤處理能力。
This commit is contained in:
NkBe 2025-10-29 22:30:25 +08:00
parent c30f588b50
commit 0883ea5577
No known key found for this signature in database
GPG Key ID: 525137026FF031DF
2 changed files with 99 additions and 33 deletions

View File

@ -3,13 +3,15 @@ package org.lsposed.lspatch.service;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.Environment;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.ParcelFileDescriptor;
import android.util.Log;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.lsposed.lspatch.util.ModuleLoader;
import org.lsposed.lspd.models.Module;
@ -23,6 +25,7 @@ import java.util.List;
public class NeoLocalApplicationService extends ILSPApplicationService.Stub {
private static final String TAG = "NPatch";
private final List<Module> cachedModule;
public NeoLocalApplicationService(Context context) {
cachedModule = Collections.synchronizedList(new ArrayList<>());
loadModulesFromSharedPreferences(context);
@ -31,33 +34,51 @@ public class NeoLocalApplicationService extends ILSPApplicationService.Stub {
private void loadModulesFromSharedPreferences(Context context) {
var shared = context.getSharedPreferences("npatch", Context.MODE_PRIVATE);
try {
String modulesJsonString = shared.getString("modules", "[]");
Log.i(TAG, "using local application service with modules:" + modulesJsonString);
var modulesJsonString = shared.getString("modules", "[]");
Log.i(TAG, "Loading modules from local SharedPreferences...");
if (modulesJsonString.equals("{}")) {
modulesJsonString = "[]";
}
var mArr = new JSONArray(modulesJsonString);
if (mArr.length() > 0) {
Log.i(TAG, "Found " + mArr.length() + " modules.");
}
for (int i = 0; i < mArr.length(); i++) {
var mObj = mArr.getJSONObject(i);
var m = new Module();
m.apkPath = mObj.getString("path");
m.packageName = mObj.getString("packageName");
if (m.apkPath == null || !new File(m.apkPath).exists()) {
m.packageName = mObj.optString("packageName", null);
var apkPath = mObj.optString("path", null);
if (m.packageName == null) {
Log.w(TAG, "Module at index " + i + " has no package name, skipping.");
continue;
}
// 如果路徑為 null 或文件不存在嘗試從 PackageManager 恢復
if (apkPath == null || !new File(apkPath).exists()) {
Log.w(TAG, "Module:" + m.packageName + " path not available, attempting reset.");
try {
ApplicationInfo info = context.getPackageManager().getApplicationInfo(m.packageName, 0);
var info = context.getPackageManager().getApplicationInfo(m.packageName, 0);
m.apkPath = info.sourceDir;
Log.i(TAG, "Module:" + m.packageName + " path reset to " + m.apkPath);
} catch (Exception e) {
Log.e(TAG, "Failed to get ApplicationInfo for module: " + m.packageName, e);
continue;
}
} else {
m.apkPath = apkPath;
}
if (m.apkPath != null) {
m.file = ModuleLoader.loadModule(m.apkPath);
cachedModule.add(m);
} else {
Log.w(TAG, "Could not load module " + m.packageName + ": final path is null.");
}
m.file = ModuleLoader.loadModule(m.apkPath);
cachedModule.add(m);
}
} catch (Throwable e) {
Log.e(TAG, "Error loading modules from SharedPreferences.", e);
@ -76,7 +97,7 @@ public class NeoLocalApplicationService extends ILSPApplicationService.Stub {
@Override
public String getPrefsPath(String packageName) throws RemoteException {
return new File(Environment.getDataDirectory(), "data/" + packageName + "/shared_prefs/").getAbsolutePath();
return "/data/data/" + packageName + "/shared_prefs/";
}
@Override

View File

@ -19,6 +19,7 @@ import org.lsposed.lspatch.share.Constants;
import org.lsposed.lspd.models.Module;
import org.lsposed.lspd.service.ILSPApplicationService;
import java.io.Closeable;
import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
@ -28,55 +29,69 @@ import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
public class RemoteApplicationService implements ILSPApplicationService {
public class RemoteApplicationService implements ILSPApplicationService, IBinder.DeathRecipient, Closeable {
private static final String TAG = "NPatch";
private static final String MODULE_SERVICE = "org.lsposed.lspatch.manager.ModuleService";
private volatile ILSPApplicationService service;
private volatile ILSPApplicationService mService;
private final Context mContext;
private final ServiceConnection mConnection;
private HandlerThread mHandlerThread;
@SuppressLint("DiscouragedPrivateApi")
public RemoteApplicationService(Context context) throws RemoteException {
this.mContext = context.getApplicationContext();
var intent = new Intent()
.setComponent(new ComponentName(Constants.MANAGER_PACKAGE_NAME, MODULE_SERVICE))
.putExtra("packageName", context.getPackageName());
// TODO: Authentication
.putExtra("packageName", mContext.getPackageName());
var latch = new CountDownLatch(1);
var conn = new ServiceConnection() {
mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder binder) {
Log.i(TAG, "Manager binder received");
service = ILSPApplicationService.Stub.asInterface(binder);
mService = ILSPApplicationService.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();
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.e(TAG, "Manager service died");
service = null;
mService = null;
}
};
Log.i(TAG, "Request manager binder");
mHandlerThread = null; // Initialize member
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
context.bindService(intent, Context.BIND_AUTO_CREATE, Executors.newSingleThreadExecutor(), conn);
mContext.bindService(intent, Context.BIND_AUTO_CREATE, Executors.newSingleThreadExecutor(), mConnection);
} else {
var handlerThread = new HandlerThread("RemoteApplicationService");
handlerThread.start();
var handler = new Handler(handlerThread.getLooper());
// 29 創建一個臨時的 HandlerThread
mHandlerThread = new HandlerThread("RemoteApplicationService");
mHandlerThread.start();
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, conn, Context.BIND_AUTO_CREATE, handler, userHandle);
bindServiceAsUserMethod.invoke(context, intent, mConnection, Context.BIND_AUTO_CREATE, handler, userHandle);
}
boolean success = latch.await(3, TimeUnit.SECONDS);
if (!success) {
// Attempt to unbind the service before throwing a timeout for cleanup
try {
context.unbindService(conn);
mContext.unbindService(mConnection);
} catch (IllegalArgumentException | IllegalStateException ignored) {
// Ignored
}
@ -87,9 +102,18 @@ public class RemoteApplicationService implements ILSPApplicationService {
var r = new RemoteException("Failed to get manager binder");
r.initCause(e);
throw r;
} finally {
}
}
private ILSPApplicationService getServiceOrThrow() throws RemoteException {
ILSPApplicationService service = mService;
if (service == null) {
throw new RemoteException("Manager service is not connected or has died.");
}
return service;
}
@Override
public boolean isLogMuted() throws RemoteException {
return false;
@ -97,30 +121,51 @@ public class RemoteApplicationService implements ILSPApplicationService {
@Override
public List<Module> getLegacyModulesList() throws RemoteException {
return service == null ? new ArrayList<>() : service.getLegacyModulesList();
return getServiceOrThrow().getLegacyModulesList();
}
@Override
public List<Module> getModulesList() throws RemoteException {
return service == null ? new ArrayList<>() : service.getModulesList();
return getServiceOrThrow().getModulesList();
}
@Override
public String getPrefsPath(String packageName) throws RemoteException {
if (service != null) {
return service.getPrefsPath(packageName);
}
Log.e(TAG, "Manager service null, 無法取得遠端首選項路徑.");
throw new RemoteException("Manager service is unavailable for getPrefsPath.");
return getServiceOrThrow().getPrefsPath(packageName);
}
@Override
public IBinder asBinder() {
return service == null ? null : service.asBinder();
return mService == null ? null : mService.asBinder();
}
@Override
public ParcelFileDescriptor requestInjectedManagerBinder(List<IBinder> binder) {
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;
}
}