diff --git a/PluginManager/consumer-proguard-rules.pro b/PluginManager/consumer-proguard-rules.pro index 8113c5e..e7ba05d 100644 --- a/PluginManager/consumer-proguard-rules.pro +++ b/PluginManager/consumer-proguard-rules.pro @@ -38,6 +38,10 @@ -dontwarn android.app.** +-keep class com.reginald.pluginm.core.HostContext { +; +} + -keep class com.reginald.pluginm.core.HostInstrumentation { ; } diff --git a/PluginManager/src/main/java/com/reginald/pluginm/PluginConfigs.java b/PluginManager/src/main/java/com/reginald/pluginm/PluginConfigs.java index 53e2194..7346eec 100644 --- a/PluginManager/src/main/java/com/reginald/pluginm/PluginConfigs.java +++ b/PluginManager/src/main/java/com/reginald/pluginm/PluginConfigs.java @@ -37,6 +37,7 @@ public class PluginConfigs { private int mProcessType = PROCESS_TYPE_INDEPENDENT; private boolean mUseHostLoader = true; + private boolean mHostContextHook = true; private final Set mSignatures = new HashSet<>(); private boolean mSignatureCheckEnabled = false; @@ -47,6 +48,7 @@ public PluginConfigs() { public PluginConfigs(PluginConfigs pluginConfigs) { mProcessType = pluginConfigs.getProcessType(); mUseHostLoader = pluginConfigs.isUseHostLoader(); + mHostContextHook = pluginConfigs.isHostContextHook(); mSignatureCheckEnabled = pluginConfigs.isSignatureCheckEnabled(); mSignatures.addAll(pluginConfigs.getSignatures()); } @@ -57,8 +59,11 @@ public int getProcessType() { /** * 设置进程模式: - * {@see } - * @param processType + * {@see {@link #PROCESS_TYPE_INDEPENDENT}, + * {@link #PROCESS_TYPE_SINGLE}, + * {@link #PROCESS_TYPE_DUAL}, + * {@link #PROCESS_TYPE_COMPLETE}} + * @param processType 进程模式 * @return */ public PluginConfigs setProcessType(int processType) { @@ -70,15 +75,39 @@ public boolean isUseHostLoader() { return mUseHostLoader; } + /** + * 设置是否插件在无法查找到类时使用宿主的classloader继续查找 + * @param useHostLoader + * @return + */ public PluginConfigs setUseHostLoader(boolean useHostLoader) { mUseHostLoader = useHostLoader; return this; } + public boolean isHostContextHook() { + return mHostContextHook; + } + + /** + * 设置是否对宿主的context进行hook + * @param hook + * @return + */ + public PluginConfigs setHostContextHook(boolean hook) { + mHostContextHook = hook; + return this; + } + public boolean isSignatureCheckEnabled() { return mSignatureCheckEnabled; } + /** + * 设置是否进行插件签名校验 + * @param isEnabled + * @return + */ public PluginConfigs setSignatureCheckEnabled(boolean isEnabled) { mSignatureCheckEnabled = isEnabled; return this; @@ -88,6 +117,11 @@ public Set getSignatures() { return Collections.unmodifiableSet(mSignatures); } + /** + * 添加插件签名 + * @param signatures 插件签名 + * @return + */ public PluginConfigs addSignatures(Signature... signatures) { if (signatures != null && signatures.length > 0) { Collections.addAll(mSignatures, signatures); @@ -111,8 +145,8 @@ public String toString() { processType = "COMPLETE"; break; } - return String.format(" PluginConfig[ mProcessType = %s, mUseHostLoader = %b, " + - "mSignatureCheckEnabled = %b, mSignatures = %d ]", - processType, mUseHostLoader, mSignatureCheckEnabled, mSignatures.size()); + return String.format(" PluginConfig[ mProcessType = %s, mUseHostLoader = %b, mHostContextHook = %b" + + "mSignatureCheckEnabled = %b, mSignatures size = %d ]", + processType, mUseHostLoader, mHostContextHook, mSignatureCheckEnabled, mSignatures.size()); } } diff --git a/PluginManager/src/main/java/com/reginald/pluginm/core/HostContext.java b/PluginManager/src/main/java/com/reginald/pluginm/core/HostContext.java new file mode 100644 index 0000000..71d6198 --- /dev/null +++ b/PluginManager/src/main/java/com/reginald/pluginm/core/HostContext.java @@ -0,0 +1,124 @@ +package com.reginald.pluginm.core; + +import java.lang.reflect.Field; +import java.util.List; + +import com.android.common.ActivityThreadCompat; +import com.reginald.pluginm.PluginInfo; +import com.reginald.pluginm.reflect.FieldUtils; +import com.reginald.pluginm.reflect.MethodUtils; +import com.reginald.pluginm.utils.Logger; + +import android.app.Application; +import android.content.Context; +import android.content.ContextWrapper; +import android.content.pm.PackageManager; +import android.content.res.AssetManager; +import android.content.res.Resources; +import android.os.UserHandle; + +/** + * Created by lxy on 18-6-26. + *

+ * 替换ActivityThread中的mInitialApplication的 base context。 + * 这个替换不是必须的,主要目的为hook系统通过ActivityThread获取到Context来进行插件资源获取,如: + * 1. RemoteViews的创建过程 + * 2. Webview中加载插件中的assets资源。 + * 。。。。 + * + */ +public class HostContext extends ContextWrapper { + + private static final String TAG = "HostContext"; + + private Context mBase; + private PluginManager mPluginManager; + + public static boolean install(Context appContext) { + Logger.d(TAG, "install()"); + Object target = ActivityThreadCompat.currentActivityThread(); + if (target != null) { + Class ActivityThreadClass = target.getClass(); + + try { + Field initApplicationField = FieldUtils.getField(ActivityThreadClass, "mInitialApplication"); + Application initApplication = (Application) FieldUtils.readField(initApplicationField, target); + Logger.d(TAG, "install() initApplication = " + initApplication); + Context baseContext = initApplication.getBaseContext(); + HostContext hostContext = new HostContext(baseContext); + Logger.d(TAG, "install() hostContext = " + hostContext); + FieldUtils.writeField(ContextWrapper.class, "mBase", initApplication, hostContext); + boolean isSuc = hostContext == initApplication.getBaseContext(); + Logger.d(TAG, "install() success? %b", isSuc); + return isSuc; + } catch (Exception e) { + Logger.e(TAG, "install() error!", e); + } + } + return false; + } + + private HostContext(Context baseContext) { + super(baseContext); + mBase = baseContext; + mPluginManager = PluginManager.getInstance(); + } + + public Context createPackageContextAsUser(String packageName, int flags, UserHandle user) + throws PackageManager.NameNotFoundException { + Logger.d(TAG, "createPackageContextAsUser() packageName = " + packageName); + PluginInfo pluginInfo = mPluginManager.getLoadedPluginInfo(packageName); + if (pluginInfo != null) { + Logger.d(TAG, "createPackageContextAsUser() return plugin context for " + pluginInfo.packageName); + return pluginInfo.baseContext; + } + + try { + return (Context) MethodUtils.invokeMethod(mBase, "createPackageContextAsUser", packageName, flags, user); + } catch (Exception e) { + Logger.e(TAG, "createPackageContextAsUser()", e); + } + + return null; + } + + @Override + public Context createPackageContext(String packageName, int flags) throws PackageManager.NameNotFoundException { + PluginInfo pluginInfo = mPluginManager.getLoadedPluginInfo(packageName); + if (pluginInfo != null) { + Logger.d(TAG, "createPackageContext() return plugin context for " + pluginInfo.packageName); + return pluginInfo.baseContext; + } + + return super.createPackageContext(packageName, flags); + } + + @Override + public AssetManager getAssets() { + Logger.d(TAG, "getAssets() "); + List pluginInfos = mPluginManager.getLoadedPluginInfos(); + if (pluginInfos != null && !pluginInfos.isEmpty()) { + AssetManager combinedAssets = ResourcesManager.getCombinedAssetManager(mBase, pluginInfos); + if (combinedAssets != null) { + Logger.d(TAG, "getAssets() return combinedAssets = " + combinedAssets); + return combinedAssets; + } + } + + return super.getAssets(); + } + + @Override + public Resources getResources() { + List pluginInfos = mPluginManager.getLoadedPluginInfos(); + if (pluginInfos != null && !pluginInfos.isEmpty()) { + AssetManager combinedAssets = ResourcesManager.getCombinedAssetManager(mBase, pluginInfos); + if (combinedAssets != null) { + Logger.d(TAG, "getResources() return combinedResources "); + return ResourcesManager.createResources(mBase, combinedAssets); + } + } + + return super.getResources(); + } +} diff --git a/PluginManager/src/main/java/com/reginald/pluginm/core/HostInstrumentation.java b/PluginManager/src/main/java/com/reginald/pluginm/core/HostInstrumentation.java index 475fc8a..514f4b6 100644 --- a/PluginManager/src/main/java/com/reginald/pluginm/core/HostInstrumentation.java +++ b/PluginManager/src/main/java/com/reginald/pluginm/core/HostInstrumentation.java @@ -456,7 +456,6 @@ public void callActivityOnCreate(Activity activity, Bundle icicle) { } mBase.callActivityOnCreate(activity, icicle); - // ensure hook install ProcessHelper.post(new Runnable() { @Override diff --git a/PluginManager/src/main/java/com/reginald/pluginm/core/PluginDexClassLoader.java b/PluginManager/src/main/java/com/reginald/pluginm/core/PluginDexClassLoader.java index d1a35df..369d53a 100644 --- a/PluginManager/src/main/java/com/reginald/pluginm/core/PluginDexClassLoader.java +++ b/PluginManager/src/main/java/com/reginald/pluginm/core/PluginDexClassLoader.java @@ -27,41 +27,36 @@ protected Class loadClass(String className, boolean resolve) throws ClassNotF Logger.d(TAG, "loadClass() classname = " + className + " , resolve = " + resolve); } - ClassNotFoundException exception = null; - try { Class clazz = super.loadClass(className, resolve); if (LOADER_DEBUG) { Logger.d(TAG, "loadClass() plugin loader: classname = " + className + " ok!"); } return clazz; - } catch (ClassNotFoundException e) { + } catch (ClassNotFoundException pluginException) { if (LOADER_DEBUG) { Logger.e(TAG, "loadClass() plugin loader: classname = " + className + " fail!"); } - exception = e; - } - if (canUseHostLoader(className)) { - try { - Class clazz = mHost.loadClass(className); - if (LOADER_DEBUG) { - Logger.d(TAG, "loadClass() host loader: classname = " + className + " ok!"); - } - return clazz; - } catch (ClassNotFoundException e) { - if (LOADER_DEBUG) { - Logger.e(TAG, "loadClass() host loader: classname = " + className + " host load fail!"); + if (canUseHostLoader(className)) { + try { + Class clazz = mHost.loadClass(className); + if (LOADER_DEBUG) { + Logger.d(TAG, "loadClass() host loader: classname = " + className + " ok!"); + } + return clazz; + } catch (ClassNotFoundException hostException) { + if (LOADER_DEBUG) { + Logger.e(TAG, "loadClass() host loader: classname = " + className + " host load fail!"); + } + throw new ClassNotFoundException(String.format("plugin class {%s} is NOT found both in PLUGIN " + + "CLASSLOADER {%s} and HOST CLASSLOADER {%s}", className, this, mHost)); } - exception = e; + } else { + throw new ClassNotFoundException(String.format("plugin class {%s} is NOT found in PLUGIN CLASSLOADER " + + "{%s}", className, this)); } } - - if (exception != null) { - throw exception; - } - - throw new ClassNotFoundException(String.format("plugin class %s NOT found", className)); } private boolean canUseHostLoader(String className) { diff --git a/PluginManager/src/main/java/com/reginald/pluginm/core/PluginManager.java b/PluginManager/src/main/java/com/reginald/pluginm/core/PluginManager.java index 8c0546c..9d660c2 100644 --- a/PluginManager/src/main/java/com/reginald/pluginm/core/PluginManager.java +++ b/PluginManager/src/main/java/com/reginald/pluginm/core/PluginManager.java @@ -48,6 +48,7 @@ import android.content.pm.ProviderInfo; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; +import android.content.res.AssetManager; import android.content.res.Resources; import android.net.Uri; import android.os.Build; @@ -103,14 +104,25 @@ public static synchronized PluginManager getInstance() { * must be called in {@link Application#attachBaseContext(Context)} * @param app */ - public static void onAttachBaseContext(Application app) { + public static void onAttachBaseContext(final Application app) { ProcessHelper.init(app); boolean isPluginProcess = ProcessHelper.isPluginProcess(app); - Logger.d(TAG, String.format("onAttachBaseContext() process = %s(%d) isPluginProcess? %b", - ProcessHelper.sProcessName, ProcessHelper.sPid, isPluginProcess)); + boolean isHostContextHook = PluginM.getConfigs().isHostContextHook(); + Logger.d(TAG, "onAttachBaseContext() process = %s(%d), isHostContextHook? %b, isPluginProcess? %b", + ProcessHelper.sProcessName, ProcessHelper.sPid, isHostContextHook, isPluginProcess); // 只在插件进程初始化 if (isPluginProcess) { + if (isHostContextHook) { + ProcessHelper.post(new Runnable() { + @Override + public void run() { + boolean isSuc = HostContext.install(app); + Logger.d(TAG, "onAttachBaseContext() post: replace host base context, isSuc? " + isSuc); + } + }); + } + Instrumentation newInstrumentation = HostInstrumentation.install(app); Logger.d(TAG, "onAttachBaseContext() replace host instrumentation, instrumentation = " + newInstrumentation); @@ -304,7 +316,8 @@ public PluginInfo loadPlugin(String packageName, String pluginProcessName) { pluginInfo.packageManager = pluginPackageManager; // replace resources - Resources resources = ResourcesManager.getPluginResources(mContext, pluginInfo.apkPath); + AssetManager assetManager = ResourcesManager.createAssetManager(pluginInfo.apkPath); + Resources resources = ResourcesManager.createResources(mContext, assetManager); if (resources != null) { pluginInfo.resources = resources; } else { diff --git a/PluginManager/src/main/java/com/reginald/pluginm/core/ResourcesManager.java b/PluginManager/src/main/java/com/reginald/pluginm/core/ResourcesManager.java index b803f62..765955d 100644 --- a/PluginManager/src/main/java/com/reginald/pluginm/core/ResourcesManager.java +++ b/PluginManager/src/main/java/com/reginald/pluginm/core/ResourcesManager.java @@ -1,11 +1,15 @@ package com.reginald.pluginm.core; +import java.util.List; + +import com.reginald.pluginm.PluginInfo; +import com.reginald.pluginm.reflect.MethodUtils; +import com.reginald.pluginm.utils.Logger; + import android.content.Context; import android.content.res.AssetManager; import android.content.res.Resources; -import com.reginald.pluginm.utils.Logger; - /** * Created by lxy on 16-6-2. */ @@ -13,22 +17,42 @@ public class ResourcesManager { private static final String TAG = "ResourcesManager"; - public static Resources getPluginResources(Context hostContext, String apkPath) { + public static Resources createResources(Context hostContext, AssetManager assetManager) { try { - AssetManager assetManager = createAssetManager(apkPath); if (assetManager != null) { return new Resources(assetManager, hostContext.getResources().getDisplayMetrics(), hostContext.getResources().getConfiguration()); } } catch (Exception e) { - Logger.e(TAG, "getPluginResources() error!", e); + Logger.e(TAG, "createResources() error!", e); + } + return null; + } + + public static AssetManager getCombinedAssetManager(Context hostContext, List pluginInfos) { + try { + + /* 5.0以上这种方式会有问题 + AssetManager assetManager = AssetManager.class.newInstance(); + AssetManager.class.getDeclaredMethod("addAssetPath", String.class).invoke(assetManager, + hostContext.getApplicationInfo().sourceDir); + */ + // TODO 在插件进程中,宿主资源与插件资源混合,如果没有对插件包进行资源id隔离,有产生资源冲突的风险。 + AssetManager assetManager = hostContext.getAssets(); + for (PluginInfo pluginInfo : pluginInfos) { + MethodUtils.invokeMethod(assetManager, "addAssetPath", pluginInfo.apkPath); + } + return assetManager; + } catch (Exception e) { + Logger.e(TAG, "getCombinedAssetManager() error!", e); } + return null; } - private static AssetManager createAssetManager(String apkPath) { + public static AssetManager createAssetManager(String apkPath) { try { AssetManager assetManager = AssetManager.class.newInstance(); - AssetManager.class.getDeclaredMethod("addAssetPath", String.class).invoke(assetManager, apkPath); + MethodUtils.invokeMethod(assetManager, "addAssetPath", apkPath); return assetManager; } catch (Exception e) { Logger.e(TAG, "createAssetManager() error!", e); diff --git a/testhost/src/main/AndroidManifest.xml b/testhost/src/main/AndroidManifest.xml index 80a4eab..b4dda69 100644 --- a/testhost/src/main/AndroidManifest.xml +++ b/testhost/src/main/AndroidManifest.xml @@ -145,7 +145,7 @@ - +