refactor: improve module loading and remote service
優化本地模組加載流程,修復模組路徑丟失時自動恢復,並加強日誌提示。重構 RemoteApplicationService,實現 Binder 死亡監聽與資源釋放,提升遠端服務連接穩定性與錯誤處理能力。
This commit is contained in:
parent
c30f588b50
commit
0883ea5577
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue