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.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.content.pm.ApplicationInfo;
|
import android.content.pm.ApplicationInfo;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
import android.os.Environment;
|
import android.os.Environment;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.os.ParcelFileDescriptor;
|
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
|
import android.os.ParcelFileDescriptor;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import org.json.JSONArray;
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
import org.lsposed.lspatch.util.ModuleLoader;
|
import org.lsposed.lspatch.util.ModuleLoader;
|
||||||
import org.lsposed.lspd.models.Module;
|
import org.lsposed.lspd.models.Module;
|
||||||
|
|
@ -23,6 +25,7 @@ import java.util.List;
|
||||||
public class NeoLocalApplicationService extends ILSPApplicationService.Stub {
|
public class NeoLocalApplicationService extends ILSPApplicationService.Stub {
|
||||||
private static final String TAG = "NPatch";
|
private static final String TAG = "NPatch";
|
||||||
private final List<Module> cachedModule;
|
private final List<Module> cachedModule;
|
||||||
|
|
||||||
public NeoLocalApplicationService(Context context) {
|
public NeoLocalApplicationService(Context context) {
|
||||||
cachedModule = Collections.synchronizedList(new ArrayList<>());
|
cachedModule = Collections.synchronizedList(new ArrayList<>());
|
||||||
loadModulesFromSharedPreferences(context);
|
loadModulesFromSharedPreferences(context);
|
||||||
|
|
@ -31,33 +34,51 @@ public class NeoLocalApplicationService extends ILSPApplicationService.Stub {
|
||||||
private void loadModulesFromSharedPreferences(Context context) {
|
private void loadModulesFromSharedPreferences(Context context) {
|
||||||
var shared = context.getSharedPreferences("npatch", Context.MODE_PRIVATE);
|
var shared = context.getSharedPreferences("npatch", Context.MODE_PRIVATE);
|
||||||
try {
|
try {
|
||||||
String modulesJsonString = shared.getString("modules", "[]");
|
var modulesJsonString = shared.getString("modules", "[]");
|
||||||
Log.i(TAG, "using local application service with modules:" + modulesJsonString);
|
Log.i(TAG, "Loading modules from local SharedPreferences...");
|
||||||
|
|
||||||
if (modulesJsonString.equals("{}")) {
|
if (modulesJsonString.equals("{}")) {
|
||||||
modulesJsonString = "[]";
|
modulesJsonString = "[]";
|
||||||
}
|
}
|
||||||
|
|
||||||
var mArr = new JSONArray(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++) {
|
for (int i = 0; i < mArr.length(); i++) {
|
||||||
var mObj = mArr.getJSONObject(i);
|
var mObj = mArr.getJSONObject(i);
|
||||||
var m = new Module();
|
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.");
|
Log.w(TAG, "Module:" + m.packageName + " path not available, attempting reset.");
|
||||||
try {
|
try {
|
||||||
ApplicationInfo info = context.getPackageManager().getApplicationInfo(m.packageName, 0);
|
var info = context.getPackageManager().getApplicationInfo(m.packageName, 0);
|
||||||
m.apkPath = info.sourceDir;
|
m.apkPath = info.sourceDir;
|
||||||
Log.i(TAG, "Module:" + m.packageName + " path reset to " + m.apkPath);
|
Log.i(TAG, "Module:" + m.packageName + " path reset to " + m.apkPath);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.e(TAG, "Failed to get ApplicationInfo for module: " + m.packageName, e);
|
Log.e(TAG, "Failed to get ApplicationInfo for module: " + m.packageName, e);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
m.apkPath = apkPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (m.apkPath != null) {
|
||||||
m.file = ModuleLoader.loadModule(m.apkPath);
|
m.file = ModuleLoader.loadModule(m.apkPath);
|
||||||
cachedModule.add(m);
|
cachedModule.add(m);
|
||||||
|
} else {
|
||||||
|
Log.w(TAG, "Could not load module " + m.packageName + ": final path is null.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
Log.e(TAG, "Error loading modules from SharedPreferences.", e);
|
Log.e(TAG, "Error loading modules from SharedPreferences.", e);
|
||||||
|
|
@ -76,7 +97,7 @@ public class NeoLocalApplicationService extends ILSPApplicationService.Stub {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getPrefsPath(String packageName) throws RemoteException {
|
public String getPrefsPath(String packageName) throws RemoteException {
|
||||||
return new File(Environment.getDataDirectory(), "data/" + packageName + "/shared_prefs/").getAbsolutePath();
|
return "/data/data/" + packageName + "/shared_prefs/";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ 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.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
@ -28,55 +29,69 @@ 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 {
|
public class RemoteApplicationService implements ILSPApplicationService, IBinder.DeathRecipient, Closeable {
|
||||||
|
|
||||||
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 volatile ILSPApplicationService service;
|
private volatile ILSPApplicationService mService;
|
||||||
|
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();
|
||||||
|
|
||||||
var 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", context.getPackageName());
|
.putExtra("packageName", mContext.getPackageName());
|
||||||
// TODO: Authentication
|
|
||||||
var latch = new CountDownLatch(1);
|
var latch = new CountDownLatch(1);
|
||||||
var conn = new ServiceConnection() {
|
mConnection = 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");
|
||||||
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();
|
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");
|
||||||
service = null;
|
mService = null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Log.i(TAG, "Request manager binder");
|
Log.i(TAG, "Request manager binder");
|
||||||
|
mHandlerThread = null; // Initialize member
|
||||||
try {
|
try {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
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 {
|
} else {
|
||||||
var handlerThread = new HandlerThread("RemoteApplicationService");
|
// 為 <29 創建一個臨時的 HandlerThread
|
||||||
handlerThread.start();
|
mHandlerThread = new HandlerThread("RemoteApplicationService");
|
||||||
var handler = new Handler(handlerThread.getLooper());
|
mHandlerThread.start();
|
||||||
|
var handler = new Handler(mHandlerThread.getLooper());
|
||||||
var contextImplClass = context.getClass();
|
var contextImplClass = context.getClass();
|
||||||
var getUserMethod = contextImplClass.getMethod("getUser");
|
var getUserMethod = contextImplClass.getMethod("getUser");
|
||||||
var bindServiceAsUserMethod = contextImplClass.getDeclaredMethod(
|
var bindServiceAsUserMethod = contextImplClass.getDeclaredMethod(
|
||||||
"bindServiceAsUser", Intent.class, ServiceConnection.class, int.class, Handler.class, UserHandle.class);
|
"bindServiceAsUser", Intent.class, ServiceConnection.class, int.class, Handler.class, UserHandle.class);
|
||||||
var userHandle = (UserHandle) getUserMethod.invoke(context);
|
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);
|
boolean success = latch.await(3, TimeUnit.SECONDS);
|
||||||
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
// Attempt to unbind the service before throwing a timeout for cleanup
|
// Attempt to unbind the service before throwing a timeout for cleanup
|
||||||
try {
|
try {
|
||||||
context.unbindService(conn);
|
mContext.unbindService(mConnection);
|
||||||
} catch (IllegalArgumentException | IllegalStateException ignored) {
|
} catch (IllegalArgumentException | IllegalStateException ignored) {
|
||||||
// Ignored
|
// Ignored
|
||||||
}
|
}
|
||||||
|
|
@ -87,9 +102,18 @@ public class RemoteApplicationService implements ILSPApplicationService {
|
||||||
var r = new RemoteException("Failed to get manager binder");
|
var r = new RemoteException("Failed to get manager binder");
|
||||||
r.initCause(e);
|
r.initCause(e);
|
||||||
throw r;
|
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
|
@Override
|
||||||
public boolean isLogMuted() throws RemoteException {
|
public boolean isLogMuted() throws RemoteException {
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -97,30 +121,51 @@ public class RemoteApplicationService implements ILSPApplicationService {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Module> getLegacyModulesList() throws RemoteException {
|
public List<Module> getLegacyModulesList() throws RemoteException {
|
||||||
return service == null ? new ArrayList<>() : service.getLegacyModulesList();
|
return getServiceOrThrow().getLegacyModulesList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Module> getModulesList() throws RemoteException {
|
public List<Module> getModulesList() throws RemoteException {
|
||||||
return service == null ? new ArrayList<>() : service.getModulesList();
|
return getServiceOrThrow().getModulesList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getPrefsPath(String packageName) throws RemoteException {
|
public String getPrefsPath(String packageName) throws RemoteException {
|
||||||
if (service != null) {
|
return getServiceOrThrow().getPrefsPath(packageName);
|
||||||
return service.getPrefsPath(packageName);
|
|
||||||
}
|
|
||||||
Log.e(TAG, "Manager service null, 無法取得遠端首選項路徑.");
|
|
||||||
throw new RemoteException("Manager service is unavailable for getPrefsPath.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IBinder asBinder() {
|
public IBinder asBinder() {
|
||||||
return service == null ? null : service.asBinder();
|
return mService == null ? null : mService.asBinder();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ParcelFileDescriptor requestInjectedManagerBinder(List<IBinder> binder) {
|
public ParcelFileDescriptor requestInjectedManagerBinder(List<IBinder> binder) throws RemoteException {
|
||||||
|
// return getServiceOrThrow().requestInjectedManagerBinder(binder);
|
||||||
return null;
|
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