(1)AssetManager
android
資源插件化實現方式:bash
#LoadedPlugin
public LoadedPlugin(PluginManager pluginManager, Context context, File apk) throws Exception {
......
this.mResources = createResources(context, getPackageName(), apk);
......
}
protected Resources createResources(Context context, String packageName, File apk) throws Exception {
if (Constants.COMBINE_RESOURCES) {
//插件資源合併到宿主中,插件可訪問宿主資源
return ResourcesManager.createResources(context, packageName, apk);
} else {
//插件建立獨立的Resources,不與宿主關聯
Resources hostResources = context.getResources();
AssetManager assetManager = createAssetManager(context, apk);
return new Resources(assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration());
}
}
複製代碼
主要經過反射建立新的AssetManager對象,經過addAssetPath加載插件資源。適用於資源獨立的狀況,沒法調用宿主資源cookie
protected AssetManager createAssetManager(Context context, File apk) throws Exception {
//經過反射建立新的AssetManager對象,經過addAssetPath加載插件資源
AssetManager am = AssetManager.class.newInstance();
Reflector.with(am).method("addAssetPath", String.class).call(apk.getAbsolutePath());
return am;
}
複製代碼
先獲取到宿主資源的AssetManager,再經過反射調用AssetManager的addAssetPath添加插件資源,返回新的Resourcesapp
#ResourcesManager
public static synchronized Resources createResources(Context hostContext, String packageName, File apk) throws Exception {
//根據版本建立Resources對象
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
//(1)
return createResourcesForN(hostContext, packageName, apk);
}
//(2)
Resources resources = ResourcesManager.createResourcesSimple(hostContext, apk.getAbsolutePath());
ResourcesManager.hookResources(hostContext, resources);
return resources;
}
//建立Resource對象
private static Resources createResourcesSimple(Context hostContext, String apk) throws Exception {
//宿主Resources對象
Resources hostResources = hostContext.getResources();
Resources newResources = null;
AssetManager assetManager;
Reflector reflector = Reflector.on(AssetManager.class).method("addAssetPath", String.class);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
//經過反射建立AssetManager
assetManager = AssetManager.class.newInstance();
reflector.bind(assetManager);
final int cookie1 = reflector.call(hostContext.getApplicationInfo().sourceDir);;
if (cookie1 == 0) {
throw new RuntimeException("createResources failed, can't addAssetPath for " + hostContext.getApplicationInfo().sourceDir);
}
} else {
//獲取到宿主的AssetManager
assetManager = hostResources.getAssets();
reflector.bind(assetManager);
}
final int cookie2 = reflector.call(apk);
if (cookie2 == 0) {
throw new RuntimeException("createResources failed, can't addAssetPath for " + apk);
}
List<LoadedPlugin> pluginList = PluginManager.getInstance(hostContext).getAllLoadedPlugins();
for (LoadedPlugin plugin : pluginList) {
final int cookie3 = reflector.call(plugin.getLocation());
if (cookie3 == 0) {
throw new RuntimeException("createResources failed, can't addAssetPath for " + plugin.getLocation());
}
}
//經過不一樣的手機品牌建立Resources對象
if (isMiUi(hostResources)) {
newResources = MiUiResourcesCompat.createResources(hostResources, assetManager);
} else if (isVivo(hostResources)) {
newResources = VivoResourcesCompat.createResources(hostContext, hostResources, assetManager);
} else if (isNubia(hostResources)) {
newResources = NubiaResourcesCompat.createResources(hostResources, assetManager);
} else if (isNotRawResources(hostResources)) {
newResources = AdaptationResourcesCompat.createResources(hostResources, assetManager);
} else {
// is raw android resources
newResources = new Resources(assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration());
}
// lastly, sync all LoadedPlugin to newResources
for (LoadedPlugin plugin : pluginList) {
plugin.updateResources(newResources);
}
return newResources;
}
複製代碼
Hook住了ContextImpl的mResources和LoadedApk的mResourceside
public static void hookResources(Context base, Resources resources) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
return;
}
try {
Reflector reflector = Reflector.with(base);
//hook mResources
reflector.field("mResources").set(resources);
Object loadedApk = reflector.field("mPackageInfo").get();
//hook mResources
Reflector.with(loadedApk).field("mResources").set(resources);
Object activityThread = ActivityThread.currentActivityThread();
Object resManager;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
resManager = android.app.ResourcesManager.getInstance();
} else {
resManager = Reflector.with(activityThread).field("mResourcesManager").get();
}
Map<Object, WeakReference<Resources>> map = Reflector.with(resManager).field("mActiveResources").get();
Object key = map.keySet().iterator().next();
map.put(key, new WeakReference<>(resources));
} catch (Exception e) {
Log.w(TAG, e);
}
}
複製代碼
#VAInstrumentation
@Override
public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
try {
cl.loadClass(className);
Log.i(TAG, String.format("newActivity[%s]", className));
} catch (ClassNotFoundException e) {
......
// 經過反射將Resources賦值給Activity的mResources
Reflector.QuietReflector.with(activity).field("mResources").set(plugin.getResources());
return newActivity(activity);
}
return newActivity(mBase.newActivity(cl, className, intent));
}
複製代碼
so的插件化,有兩種方案:基於System.Load和基於System.LoadLibrary。函數
#LoadedPlugin
protected ClassLoader createClassLoader(Context context, File apk, File libsDir, ClassLoader parent) throws Exception {
File dexOutputDir = getDir(context, Constants.OPTIMIZE_DIR);
String dexOutputPath = dexOutputDir.getAbsolutePath();
DexClassLoader loader = new DexClassLoader(apk.getAbsolutePath(), dexOutputPath, libsDir.getAbsolutePath(), parent);
if (Constants.COMBINE_CLASSLOADER) {
DexUtil.insertDex(loader, parent, libsDir);
}
return loader;
}
複製代碼
建立了一個DexClassLoader,解析出每一個插件apk中的so文件,解壓到某個位置,把這些路徑用逗號鏈接起來成爲一個字符串,放到DexClassLoader的構造函數的第3個參數中。這樣插件中的so,就和宿主App中jniLib目錄下的so同樣,經過System.loadLibrary方法來加載。ui
#DexUtil
public static void insertDex(DexClassLoader dexClassLoader, ClassLoader baseClassLoader, File nativeLibsDir) throws Exception {
Object baseDexElements = getDexElements(getPathList(baseClassLoader));
Object newDexElements = getDexElements(getPathList(dexClassLoader));
//將宿主和插件的DexElements合併獲得allDexElements
Object allDexElements = combineArray(baseDexElements, newDexElements);
Object pathList = getPathList(baseClassLoader);
//經過反射將dexElements替換爲allDexElements
Reflector.with(pathList).field("dexElements").set(allDexElements);
insertNativeLibrary(dexClassLoader, baseClassLoader, nativeLibsDir);
}
複製代碼
so插件化核心代碼this
private static synchronized void insertNativeLibrary(DexClassLoader dexClassLoader, ClassLoader baseClassLoader, File nativeLibsDir) throws Exception {
if (sHasInsertedNativeLibrary) {
return;
}
sHasInsertedNativeLibrary = true;
Context context = ActivityThread.currentApplication();
//獲取宿主的PathList
Object basePathList = getPathList(baseClassLoader);
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP_MR1) {
Reflector reflector = Reflector.with(basePathList);
List<File> nativeLibraryDirectories = reflector.field("nativeLibraryDirectories").get();
nativeLibraryDirectories.add(nativeLibsDir);
//獲取到宿主的so集合
Object baseNativeLibraryPathElements = reflector.field("nativeLibraryPathElements").get();
final int baseArrayLength = Array.getLength(baseNativeLibraryPathElements);
Object newPathList = getPathList(dexClassLoader);
//獲取到插件的so集合
Object newNativeLibraryPathElements = reflector.get(newPathList);
Class<?> elementClass = newNativeLibraryPathElements.getClass().getComponentType();
Object allNativeLibraryPathElements = Array.newInstance(elementClass, baseArrayLength + 1);
//將原來宿主的so集合拷貝到新集合中
System.arraycopy(baseNativeLibraryPathElements, 0, allNativeLibraryPathElements, 0, baseArrayLength);
Field soPathField;
if (Build.VERSION.SDK_INT >= 26) {
soPathField = elementClass.getDeclaredField("path");
} else {
soPathField = elementClass.getDeclaredField("dir");
}
soPathField.setAccessible(true);
//將插件的so集合拷貝到新集合中
final int newArrayLength = Array.getLength(newNativeLibraryPathElements);
for (int i = 0; i < newArrayLength; i++) {
Object element = Array.get(newNativeLibraryPathElements, i);
String dir = ((File)soPathField.get(element)).getAbsolutePath();
if (dir.contains(Constants.NATIVE_DIR)) {
Array.set(allNativeLibraryPathElements, baseArrayLength, element);
break;
}
}
//將宿主和插件so的合集替換上去
reflector.set(allNativeLibraryPathElements);
} else {
Reflector reflector = Reflector.with(basePathList).field("nativeLibraryDirectories");
File[] nativeLibraryDirectories = reflector.get();
final int N = nativeLibraryDirectories.length;
File[] newNativeLibraryDirectories = new File[N + 1];
System.arraycopy(nativeLibraryDirectories, 0, newNativeLibraryDirectories, 0, N);
newNativeLibraryDirectories[N] = nativeLibsDir;
reflector.set(newNativeLibraryDirectories);
}
}
複製代碼
獲取宿主so集合,獲取插件so集合,兩者合併後經過反射替換原so集合,插件so文件就能正常被加載了spa
VirtualApk初始化時經過ActivityManagerProxy Hook了IActivityManager。啓動服務時,經過ActivityManagerProxy攔截到了startService的操做插件
public class ActivityManagerProxy implements InvocationHandler {
protected Object startService(Object proxy, Method method, Object[] args) throws Throwable {
IApplicationThread appThread = (IApplicationThread) args[0];
//跳轉的intent
Intent target = (Intent) args[1];
//檢查Service信息
ResolveInfo resolveInfo = this.mPluginManager.resolveService(target, 0);
if (null == resolveInfo || null == resolveInfo.serviceInfo) {
// is host service
return method.invoke(this.mActivityManager, args);
}
return startDelegateServiceForTarget(target, resolveInfo.serviceInfo, null, RemoteService.EXTRA_COMMAND_START_SERVICE);
}
protected ComponentName startDelegateServiceForTarget(Intent target, ServiceInfo serviceInfo, Bundle extras, int command) {
Intent wrapperIntent = wrapperTargetIntent(target, serviceInfo, extras, command);
return mPluginManager.getHostContext().startService(wrapperIntent);
}
protected Intent wrapperTargetIntent(Intent target, ServiceInfo serviceInfo, Bundle extras, int command) {
// 將目標Service的相關信息存儲起來
target.setComponent(new ComponentName(serviceInfo.packageName, serviceInfo.name));
String pluginLocation = mPluginManager.getLoadedPlugin(target.getComponent()).getLocation();
// 根據processName判斷是否爲遠程服務
boolean local = PluginUtil.isLocalService(serviceInfo);
// 判斷交給LocalService仍是RemoteService進行處理
Class<? extends Service> delegate = local ? LocalService.class : RemoteService.class;
// 參數傳遞
Intent intent = new Intent();
intent.setClass(mPluginManager.getHostContext(), delegate);
intent.putExtra(RemoteService.EXTRA_TARGET, target);
intent.putExtra(RemoteService.EXTRA_COMMAND, command);
intent.putExtra(RemoteService.EXTRA_PLUGIN_LOCATION, pluginLocation);
if (extras != null) {
intent.putExtras(extras);
}
return intent;
}
}
複製代碼
public class LocalService extends Service {
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
......
switch (command) {
case EXTRA_COMMAND_START_SERVICE: {
//獲取ActivityThread
ActivityThread mainThread = ActivityThread.currentActivityThread();
IApplicationThread appThread = mainThread.getApplicationThread();
Service service;
if (this.mPluginManager.getComponentsHandler().isServiceAvailable(component)) {
//獲取Service
service = this.mPluginManager.getComponentsHandler().getService(component);
} else {
try {
//經過DexClassLoader加載Service
service = (Service) plugin.getClassLoader().loadClass(component.getClassName()).newInstance();
Application app = plugin.getApplication();
IBinder token = appThread.asBinder();
Method attach = service.getClass().getMethod("attach", Context.class, ActivityThread.class, String.class, IBinder.class, Application.class, Object.class);
IActivityManager am = mPluginManager.getActivityManager();
//經過attach方法綁定Context
attach.invoke(service, plugin.getPluginContext(), mainThread, component.getClassName(), token, app, am);
//調用Service的onCreate方法
service.onCreate();
this.mPluginManager.getComponentsHandler().rememberService(component, service);
} catch (Throwable t) {
return START_STICKY;
}
}
//調用service的onStartCommand方法
service.onStartCommand(target, 0, this.mPluginManager.getComponentsHandler().getServiceCounter(service).getAndIncrement());
break;
}
......
}
}
}
複製代碼
public class RemoteService extends LocalService {
private static final String TAG = Constants.TAG_PREFIX + "RemoteService";
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent == null) {
return super.onStartCommand(intent, flags, startId);
}
//獲取目標service的intent
Intent target = intent.getParcelableExtra(EXTRA_TARGET);
if (target != null) {
//獲取插件路徑
String pluginLocation = intent.getStringExtra(EXTRA_PLUGIN_LOCATION);
ComponentName component = target.getComponent();
LoadedPlugin plugin = PluginManager.getInstance(this).getLoadedPlugin(component);
if (plugin == null && pluginLocation != null) {
try {
//加載apk插件文件
PluginManager.getInstance(this).loadPlugin(new File(pluginLocation));
} catch (Exception e) {
Log.w(TAG, e);
}
}
}
return super.onStartCommand(intent, flags, startId);
}
}
複製代碼
啓動遠程服務多了一步加載其餘插件的Service的操做
參考資料: