Skip to content

Commit

Permalink
add HostContext hook
Browse files Browse the repository at this point in the history
  • Loading branch information
ReginaldLiu committed Jun 27, 2018
1 parent 161c2cf commit 6b12016
Show file tree
Hide file tree
Showing 11 changed files with 266 additions and 44 deletions.
4 changes: 4 additions & 0 deletions PluginManager/consumer-proguard-rules.pro
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@
-dontwarn android.app.**


-keep class com.reginald.pluginm.core.HostContext {
<methods>;
}

-keep class com.reginald.pluginm.core.HostInstrumentation {
<methods>;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ public class PluginConfigs {

private int mProcessType = PROCESS_TYPE_INDEPENDENT;
private boolean mUseHostLoader = true;
private boolean mHostContextHook = true;
private final Set<Signature> mSignatures = new HashSet<>();
private boolean mSignatureCheckEnabled = false;

Expand All @@ -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());
}
Expand All @@ -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) {
Expand All @@ -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;
Expand All @@ -88,6 +117,11 @@ public Set<Signature> getSignatures() {
return Collections.unmodifiableSet(mSignatures);
}

/**
* 添加插件签名
* @param signatures 插件签名
* @return
*/
public PluginConfigs addSignatures(Signature... signatures) {
if (signatures != null && signatures.length > 0) {
Collections.addAll(mSignatures, signatures);
Expand All @@ -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());
}
}
124 changes: 124 additions & 0 deletions PluginManager/src/main/java/com/reginald/pluginm/core/HostContext.java
Original file line number Diff line number Diff line change
@@ -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.
* <p>
* 替换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<PluginInfo> 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<PluginInfo> 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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -456,7 +456,6 @@ public void callActivityOnCreate(Activity activity, Bundle icicle) {
}

mBase.callActivityOnCreate(activity, icicle);

// ensure hook install
ProcessHelper.post(new Runnable() {
@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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 {
Expand Down
Loading

0 comments on commit 6b12016

Please sign in to comment.