此文章基于Android 插件化原理(一)和Android 插件化原理(二)

众所周知,在Android中,四大组件是需要在AndroidManifest.xml文件中注册之后才能调用的,但是插件APP可能并没有安装,只是放在终端的某个存储路径上,故系统会找不到插件里面用到的四大组件;那么如果宿主APP需要调用插件APP的四大组件呢,比如,宿主APP要启动插件APP中的某个Activity,需要怎么操作呢?

本篇文章只讲述两个问题:

  1. 宿主如何启动插件中的Activity;
  2. 插件APP不安装的情况下,插件APP中的资源并没有解压到系统data目录,需要怎么去调用插件中的资源(String,drawable等);

用到的技术:

  1. Hook Activity;
  2. Java动态代理;
  3. Java 反射;
  4. 熟悉AMS启动Activity相关流程,可参考https://blog.csdn.net/lj19851227/article/details/82562115;
  5. 熟悉AssetManager加载app资源相关流程;

不熟悉相关技术的,可以先行做个了解,明白基础的用法即可。

宿主APP中启动插件APP中的Activity

要从宿主中启动插件的Activity,但是插件并没有安装,如果直接启动,Android系统会抛出没有在AndroidManifest.xml中注册该Activity的错误,要解决这个问题,这里就需要用到Hook技术,绕过AMS对Activity的检查。

  • HOOK简介

在Java中,正常情况下,对象A调用对象B,对象B处理完数据之后,会将结果返回给A,如下图:

加入HOOK技术后,就会变成如下形式:

HOOK可以是方法,也可以是对象,它像一个钩子一样挂在对象B上,当A调用B的时候,会先经过HOOK,那么HOOK就可以做一些小动作,起到“欺上瞒下”的作用。对象B就是我们常说的HOOK点,为了保证HOOK的稳定性,HOOK点一般选择容易找到,并且不易变化的对象,如静态变量和单例。

那么按照HOOK的思路,首先我们在宿主APP中创建一个代理ProxyActivity继承自Activity,并且在宿主的清单文件(AndroidManifest.xml)中注册,当启动插件Activity的时候,在AMS检测之前,先找到一个HOOK点,将目标Activity替换为ProxyActivity(也就是假装启动ProxyActivity),等检测完之后,再找一个HOOK点,将目标Activity替换回来,这样就成功绕过了系统的检测。

要寻找到合适的HOOK点,需要熟悉AMS启动Activity的流程:

通过上图,我们可以确定HOOK的大概位置:

  1. 在进入AMS之前,找HOOK点,将目标Activity(插件中的Activity)替换为ProxyActivity;
  2. 在从AMS出来之后,找HOOK点,将ProxyActivity替换为目标Activity;

启动Activity的过程,在这里不做详述,这里只说明具体的HOOK点(注意此HOOK点不是唯一的,可以根据自己对Android源码的理解,自己选择认为合适的HOOK点),这里也可以参照滴滴的VirtualAPK中的PluginManager中的操作,选择HOOK点:

//android/app/Instrumentation.java
public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, String target,Intent intent, int requestCode, Bundle options) {IApplicationThread whoThread = (IApplicationThread) contextThread;if (mActivityMonitors != null) {synchronized (mSync) {final int N = mActivityMonitors.size();for (int i=0; i<N; i++) {final ActivityMonitor am = mActivityMonitors.get(i);ActivityResult result = null;if (am.ignoreMatchingSpecificIntents()) {result = am.onStartActivity(intent);}if (result != null) {am.mHits++;return result;} else if (am.match(who, null, intent)) {am.mHits++;if (am.isBlocking()) {return requestCode >= 0 ? am.getResult() : null;}break;}}}}try {intent.migrateExtraStreamToClipData();intent.prepareToLeaveProcess(who);// 这儿就是我们的 Hook 点,替换传入 startActivity 方法中的 intent 参数int result = ActivityManager.getService().startActivity(whoThread, who.getBasePackageName(), intent,intent.resolveTypeIfNeeded(who.getContentResolver()),token, target, requestCode, 0, null, options);checkStartActivityResult(result, intent);} catch (RemoteException e) {throw new RuntimeException("Failure from system", e);}return null;
}

找到了HOOK点,具体怎么操作呢?

这儿就需要用到前面提到的动态代理,需要替换的就是ActivityManager.getService()这个对象;简单的说,就是在此处代码执行之前,将ActivityManager.getService()这个对象替换我们自己代码中提供的代理对象,这样在执行startActivity方法的时候,其实执行的就是在我们自己在代码中做过一些小动作的startActivity方法,把startActivity方法中的第三个参数intent,原本用于启动目标Activity的Intent替换为启动ProxyActivity的Intent。

这儿这么做的目的就是因为ProxyActivity是在宿主APP中的,并且是在AndroidManifest.xml中声明的,进入AMS之后,就不会抛出Activity没有声明的错误。

接着上面的思路,要替换ActivityManager.getService()这个对象,先看下它返回的是啥?

//android/app/ActivityManager.java
public static IActivityManager getService() {return IActivityManagerSingleton.get();
}private static final Singleton<IActivityManager> IActivityManagerSingleton =new Singleton<IActivityManager>() {@Overrideprotected IActivityManager create() {final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);final IActivityManager am = IActivityManager.Stub.asInterface(b);return am;}};

ActivityManager.getService()返回的是一个IActivityManager对象,并且可以看出这儿的IActivityManager是从Singleton的get()方法中获取的,看一下Singleton的get方法:

//android/util/Singleton.java
package android.util;/*** Singleton helper class for lazily initialization.** Modeled after frameworks/base/include/utils/Singleton.h** @hide*/
public abstract class Singleton<T> {private T mInstance;protected abstract T create();//可以看到get方法其实返回的就是mInstancepublic final T get() {synchronized (this) {if (mInstance == null) {mInstance = create();}return mInstance;}}
}

屡清楚上面这个流程之后,需要做以下两点去执行HOOK的动作:

  1. 通过动态代理替换掉startActivity方法中的intent参数;
  2. 通过反射替换到调用startActivity方法的IActivityManager对象;

Talk is easy,show me the code:

public static final String TARGET_INTENT = "target_intent";
public static void hookAMS(){try {Class<?> iActivityManagerClass = Class.forName("android.app.IActivityManager");Class<?> activityManagerCls = Class.forName("android.app.ActivityManager");Field singletonField = activityManagerCls.getDeclaredField("IActivityManagerSingleton");if(!singletonField.isAccessible()){singletonField.setAccessible(true);}//IActivityManagerSingleton在ActivityManager中是静态的,可通过反射直接获取Object singleObj = singletonField.get(null);//Singletone中的get()方法返回值就是IActivityManager类型的对象Class<?> singleClz = Class.forName("android.util.Singleton");Field instanceField = singleClz.getDeclaredField("mInstance");if(!instanceField.isAccessible())instanceField.setAccessible(true);final Object mInstance = instanceField.get(singleObj);//也可通过Singleton类的get()获取,get方法返回的是mInstance,mInstance就是IActivityManager对象//Method getMethod = singleClz.getDeclaredMethod("get");//final Object mInstance = getMethod.invoke(singleObj);Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),new Class<?>[]{iActivityManagerClass}, new InvocationHandler() {Intent targetIntent = null;@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {if(method.getName().equals("startActivity")){int index = 0;for(int i=0;i<args.length;i++){//if(args[i].getClass().getName().contains("intent")){if(args[i] instanceof Intent){index = i;break;}}//目标intent,也就是启动插件Activity的IntenttargetIntent = (Intent) args[index];Intent proxyIntent = new Intent();//保存目标intent,绕过AMS之后,再次hook的时候需要用到proxyIntent.putExtra(TARGET_INTENT,targetIntent);Log.d("Rayman", "invoke: extra intent = "+targetIntent);//此处Intnet应该是用于启动代理ActivityproxyIntent.setClassName("com.enjoy.myplugin","com.enjoy.myplugin.ProxyActivity");ComponentName cpn =  targetIntent.getComponent();if(cpn != null){String clsName = cpn.getClassName();//此处做判断,因为可能会启动很多个不同的Activity,需要对不同的Activity做区分,有可能是启动宿主本身的Activity,就不需要执行替换的动作if(!TextUtils.isEmpty(clsName) && clsName.equals("com.enjoy.plugin.MainActivity")){//替换目标intentargs[index] = proxyIntent;}}//到此处,启动插件Activity的intent就被我们替换了}//此处要反射调用IActivityManager的startActivity方法,需要一个IActivityManager对象return method.invoke(mInstance,args);}});//利用反射,替换调用startActivity的IActivityManager对象instanceField.set(singleObj,proxy);} catch (Exception e) {e.printStackTrace();}
}

那么到这儿,进入AMS之前的HOOK就结束了,接下来要做的就是从AMS出来之后,再次HOOK,把启动目标Activity的intent再替换回来,用以启动目标Activity。

根据Activity启动流程,参考前面的流程图可知,在从AMS出来之后,进入ActivityThread,会调用Hander的handleMessage,我们看相关源码:(这儿查找源码目的就是再找出一个启动Activity之前的Intent,将之前已经被替换的Intent再替换回来)

//android/app/ActivityThread.java
public void handleMessage(Message msg) {if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));switch (msg.what) {...case EXECUTE_TRANSACTION:final ClientTransaction transaction = (ClientTransaction) msg.obj;mTransactionExecutor.execute(transaction);if (isSystem()) {// Client transactions inside system process are recycled on the client side// instead of ClientLifecycleManager to avoid being cleared before this                 // message is handled.transaction.recycle();}// TODO(lifecycler): Recycle locally scheduled transactions.break;...}
}

EXECUTE_TRANSACTION的值是159。这里的ClientTransaction,也就是msg.obj,来看一下TransactionExecutor的execute方法。

//android/app/servertransaction/TransactionExecutor.java
public void execute(ClientTransaction transaction) {......executeCallbacks(transaction);......
}
/** Cycle through all states requested by callbacks and execute them at proper times. */
@VisibleForTesting
public void executeCallbacks(ClientTransaction transaction) {//获取transaction的Callbacksfinal List<ClientTransactionItem> callbacks = transaction.getCallbacks();......final int size = callbacks.size();for (int i = 0; i < size; ++i) {final ClientTransactionItem item = callbacks.get(i);if (DEBUG_RESOLVER) Slog.d(TAG, tId(transaction) + "Resolving callback: " + item);final int postExecutionState = item.getPostExecutionState();final int closestPreExecutionState = mHelper.getClosestPreExecutionState(r,item.getPostExecutionState());if (closestPreExecutionState != UNDEFINED) {cycleToPath(r, closestPreExecutionState, transaction);}//此处会调用ClientTransactionItem的executeitem.execute(mTransactionHandler, token, mPendingActions);item.postExecute(mTransactionHandler, token, mPendingActions);......   }
}

上面ClientTransaction的getCallbacks()方法如下:

//android/app/servertransaction/ClientTransaction.java/** A list of individual callbacks to a client. */
@UnsupportedAppUsage
private List<ClientTransactionItem> mActivityCallbacks;/*** Add a message to the end of the sequence of callbacks.* @param activityCallback A single message that can contain a lifecycle request/callback.*/
public void addCallback(ClientTransactionItem activityCallback) {if (mActivityCallbacks == null) {mActivityCallbacks = new ArrayList<>();}mActivityCallbacks.add(activityCallback);
}/** Get the list of callbacks. */
@Nullable
@UnsupportedAppUsage
List<ClientTransactionItem> getCallbacks() {return mActivityCallbacks;
}

getCallbacks()方法返回的是mActivityCallbacks,而mActivityCallbacks是通过addCallback方法赋值的,addCallback方法的掉用是在下面这ActivityStackSupervisor.java中的realStartActivityLocked方法中的:

//android/server/ActivityStackSupervisor.java
boolean realStartActivityLocked(ActivityRecord r, WindowProcessController proc,boolean andResume, boolean checkConfig) throws RemoteException {......// Create activity launch transaction.final ClientTransaction clientTransaction = ClientTransaction.obtain(proc.getThread(), r.appToken);final DisplayContent dc = r.getDisplay().mDisplayContent;//此处调用addCallbacks,LauncherActivityItem是ClientTransactionItem的子类clientTransaction.addCallback(LaunchActivityItem.obtain(new Intent(r.intent),System.identityHashCode(r), r.info,// TODO: Have this take the merged configuration instead of separate global// and override configs.mergedConfiguration.getGlobalConfiguration(),mergedConfiguration.getOverrideConfiguration(), r.compat,r.launchedFromPackage, task.voiceInteractor, proc.getReportedProcState(),r.icicle, r.persistentState, results, newIntents,dc.isNextTransitionForward(), proc.createProfilerInfoIfNeeded(),r.assistToken));// Set desired final state.final ActivityLifecycleItem lifecycleItem;if (andResume) {lifecycleItem = ResumeActivityItem.obtain(dc.isNextTransitionForward());} else {lifecycleItem = PauseActivityItem.obtain();}clientTransaction.setLifecycleStateRequest(lifecycleItem);// Schedule transaction.mService.getLifecycleManager().scheduleTransaction(clientTransaction);......}
看下LaunchActivityItem源码:
//android/app/servertransaction/LaunchActivityItem.java
public class LaunchActivityItem extends ClientTransactionItem {//这里我们发现LaunchActivityItem中正好有个Intent@UnsupportedAppUsageprivate Intent mIntent;private int mIdent;@UnsupportedAppUsageprivate ActivityInfo mInfo;private Configuration mCurConfig;private Configuration mOverrideConfig;......
}
这里LaunchActivityItem,中就有一个Intent可用于HOOK,这个流程scheduleTransaction方法最终会调用到上面提到的ActivityThread中handleMessage方法中,msg.obj对象就是ClientTransaction,ClientTransaction中的mActivityCallbacks中可获取LaunchActivityItem,再从LaunchActivityItem中获取需要的Intent。

找到Intent之后,接下来就是通过反射将此处的Intent替换掉,替换Intent,需要操作一下前面提到的ActivityThread中的Handler,也就是ActivityThread类的成员mH,那么看下Handler的源码:

//android/os/Handler.java
/*** Handle system messages here.*/
public void dispatchMessage(@NonNull Message msg) {if (msg.callback != null) {handleCallback(msg);} else {if (mCallback != null) {if (mCallback.handleMessage(msg)) {return;}}handleMessage(msg);}
}
 

当 mCallback != null 时,首先会执行 mCallback.handleMessage(msg),再执行 handleMessage(msg),所以我
们可以将 mCallback 作为 Hook 点,创建它。代码如下:

public static void hookHandler(){try {final Class<?> activtyThreadCls = Class.forName("android.app.ActivityThread");Field activitThreadField = activtyThreadCls.getDeclaredField("sCurrentActivityThread");activitThreadField.setAccessible(true);final Object activityTheadObj = activitThreadField.get(null);Field mHField = activtyThreadCls.getDeclaredField("mH");if(!mHField.isAccessible()){mHField.setAccessible(true);}Object mHObj = mHField.get(activityTheadObj);Class<?> handlerCls = Class.forName("android.os.Handler");Field mCallbackField = handlerCls.getDeclaredField("mCallback");mCallbackField.setAccessible(true);mCallbackField.set(mHObj,new Handler.Callback() {@Overridepublic boolean handleMessage(@NonNull Message msg) {switch(msg.what){case 159:try {//Class<?> transExecutorCls = Class.forName("android.app.servertransaction.TransactionExecutor");Object clientTransObj = msg.obj;/*Method getActivityTokenMethod = clientTransObj.getClass().getDeclaredMethod("getActivityToken");getActivityTokenMethod.setAccessible(true);Object tokenObj = getActivityTokenMethod.invoke(clientTransObj);此方法会报错,(greylist-max-o, reflection, denied),在Android Q上已经禁止反射调用了Method getActivityClientMethod = activtyThreadCls.getDeclaredMethod("getActivityClient", IBinder.class);getActivityClientMethod.setAccessible(true);Object activityClientRecordObj = getActivityClientMethod.invoke(activityTheadObj,tokenObj);Field intentField = activityClientRecordObj.getClass().getDeclaredField("intent");intentField.setAccessible(true);Intent proxyIntent = (Intent)intentField.get(activityClientRecordObj);*/Field mActivityCallbacksField = clientTransObj.getClass().getDeclaredField("mActivityCallbacks");mActivityCallbacksField.setAccessible(true);List<Object> mActivityCallbacksObj = (List<Object>) mActivityCallbacksField.get(clientTransObj);Object launchActivityItem = null;for(Object obj:mActivityCallbacksObj){Log.d("Rayman", "name: "+obj.getClass().getName());if(obj.getClass().getName().contains("android.app.servertransaction.LaunchActivityItem")){launchActivityItem = obj;break;}}if(launchActivityItem == null) return false;Log.d("Rayman", "launchActivityItem = "+launchActivityItem+",clsname = "+launchActivityItem.getClass().getName());Field mIntentField = launchActivityItem.getClass().getDeclaredField("mIntent");mIntentField.setAccessible(true);Intent proxyIntent = (Intent)mIntentField.get(launchActivityItem);//设置目标IntentIntent targetIntent = proxyIntent.getParcelableExtra(TARGET_INTENT);Log.d("Rayman","targetIntent = "+targetIntent+","+proxyIntent.getComponent().getClassName());//判断是否是之前我们hook时候,启动的代理Activityif("com.enjoy.myplugin.ProxyActivity".equals(proxyIntent.getComponent().getClassName())){/*if(targetIntent == null){targetIntent = new Intent();targetIntent.setClassName("com.enjoy.plugin","com.enjoy.plugin.MainActivity");}*/if(targetIntent != null)mIntentField.set(launchActivityItem,targetIntent);}} catch (Exception e) {e.printStackTrace();}break;}return false;}});/*Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),new Class<?>[]{mHObj.getClass()}, new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {return null;}});*/} catch (Exception e) {e.printStackTrace();}
}

到这里,HOOK操作就完成了,只需要再宿主APP的Application的onCreate方法中,分别调用hookAMS和hookHandler方法就可以了。

接下来看下,怎么操作资源的加载,由于插件app没有安装,即便是在插件app中,也是没法调用本身的资源的,运行时会报找不到资源的错误。

一般在Android中通过,getResources获取Resources,然后通过Resources调用app相应资源。需要通过反射替换到原本系统中的Resources,或者说通过反射,自己写代码加载插件APP中的资源,这里当然需要跟踪Android系统是怎样加载APP资源的。

比如通常用如下方式获取调用app资源:

String appName = getResources().getString(R.string.app_name);
InputStream is = getAssets().open("icon_1.png");

getResources()方法的实现是在ContextImpl.java中,看ContextImpl源码:

//android/app/ContextImpl.java
@Override
public Resources getResources() {return mResources;
}void setResources(Resources r) {if (r instanceof CompatResources) {((CompatResources) r).setContext(this);}mResources = r;
}@Override
public Context createApplicationContext(ApplicationInfo application, int flags)throws NameNotFoundException {LoadedApk pi = mMainThread.getPackageInfo(application, mResources.getCompatibilityInfo(),flags | CONTEXT_REGISTER_PACKAGE);if (pi != null) {ContextImpl c = new ContextImpl(this, mMainThread, pi, null, mActivityToken,new UserHandle(UserHandle.getUserId(application.uid)), flags, null, null);final int displayId = getDisplayId();//此处会调用createResources方法创建新的Resourcesc.setResources(createResources(mActivityToken, pi, null, displayId, null,getDisplayAdjustments(displayId).getCompatibilityInfo()));if (c.mResources != null) {return c;}}throw new PackageManager.NameNotFoundException("Application package " + application.packageName + " not found");
}private static Resources createResources(IBinder activityToken, LoadedApk pi, String splitName,int displayId, Configuration overrideConfig, CompatibilityInfo compatInfo) {final String[] splitResDirs;final ClassLoader classLoader;try {splitResDirs = pi.getSplitPaths(splitName);classLoader = pi.getSplitClassLoader(splitName);} catch (NameNotFoundException e) {throw new RuntimeException(e);}return ResourcesManager.getInstance().getResources(activityToken,pi.getResDir(),splitResDirs,pi.getOverlayDirs(),pi.getApplicationInfo().sharedLibraryFiles,displayId,overrideConfig,compatInfo,classLoader);
}

看ResourcesManager的,getResources方法:

//android/app/ResourcesManager.java
public @Nullable Resources getResources(@Nullable IBinder activityToken,@Nullable String resDir,@Nullable String[] splitResDirs,@Nullable String[] overlayDirs,@Nullable String[] libDirs,int displayId,@Nullable Configuration overrideConfig,@NonNull CompatibilityInfo compatInfo,@Nullable ClassLoader classLoader) {try {Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "ResourcesManager#getResources");final ResourcesKey key = new ResourcesKey(resDir,//这个resDir其实就是需要加载资源的app路径splitResDirs,overlayDirs,libDirs,displayId,overrideConfig != null ? new Configuration(overrideConfig) : null, // CopycompatInfo);classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader();return getOrCreateResources(activityToken, key, classLoader);} finally {Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);}}private @Nullable Resources getOrCreateResources(@Nullable IBinder activityToken,@NonNull ResourcesKey key, @NonNull ClassLoader classLoader) {......// If we're here, we didn't find a suitable ResourcesImpl to use, so create one now.ResourcesImpl resourcesImpl = createResourcesImpl(key);if (resourcesImpl == null) {return null;}......
}private @Nullable ResourcesImpl createResourcesImpl(@NonNull ResourcesKey key) {final DisplayAdjustments daj = new DisplayAdjustments(key.mOverrideConfiguration);daj.setCompatibilityInfo(key.mCompatInfo);final AssetManager assets = createAssetManager(key);if (assets == null) {return null;}final DisplayMetrics dm = getDisplayMetrics(key.mDisplayId, daj);final Configuration config = generateConfig(key, dm);final ResourcesImpl impl = new ResourcesImpl(assets, dm, config, daj);if (DEBUG) {Slog.d(TAG, "- creating impl=" + impl + " with key: " + key);}return impl;}protected @Nullable AssetManager createAssetManager(@NonNull final ResourcesKey key) {final AssetManager.Builder builder = new AssetManager.Builder();// resDir can be null if the 'android' package is creating a new Resources object.// This is fine, since each AssetManager automatically loads the 'android' package// already.if (key.mResDir != null) {try {//我们需要跟踪AssetManager是怎么加载app资源的,也就是addApkAssets方法builder.addApkAssets(loadApkAssets(key.mResDir, false /*sharedLib*/,false /*overlay*/));} catch (IOException e) {Log.e(TAG, "failed to add asset path " + key.mResDir);return null;}}......
}

AssetManager

//android/content/res/AssetManager.java
public static class Builder {private ArrayList<ApkAssets> mUserApkAssets = new ArrayList<>();//从这里继续跟踪,需要看mUserApkAssets在哪儿调用public Builder addApkAssets(ApkAssets apkAssets) {mUserApkAssets.add(apkAssets);return this;}public AssetManager build() {// Retrieving the system ApkAssets forces their creation as well.final ApkAssets[] systemApkAssets = getSystem().getApkAssets();final int totalApkAssetCount = systemApkAssets.length + mUserApkAssets.size();final ApkAssets[] apkAssets = new ApkAssets[totalApkAssetCount];System.arraycopy(systemApkAssets, 0, apkAssets, 0, systemApkAssets.length);final int userApkAssetCount = mUserApkAssets.size();for (int i = 0; i < userApkAssetCount; i++) {//mUserApkAssets列表最中会加入apkAssets变量中apkAssets[i + systemApkAssets.length] = mUserApkAssets.get(i);}// Calling this constructor prevents creation of system ApkAssets, which we took care// of in this Builder.final AssetManager assetManager = new AssetManager(false /*sentinel*/);//apkAssets变量又被赋值给assetManager.mApkAssetsassetManager.mApkAssets = apkAssets;//调用native方法添加app资源文件AssetManager.nativeSetApkAssets(assetManager.mObject, apkAssets,false /*invalidateCaches*/);return assetManager;}}//在此利用反射调用Builder的addApkAssets方法可以添加插件app的资源,又或者在9.0之前,我们可以看另一个方法如下:
/*** @deprecated Use {@link #setApkAssets(ApkAssets[], boolean)}* @hide*/
@Deprecated
@UnsupportedAppUsage
public int addAssetPath(String path) {return addAssetPathInternal(path, false /*overlay*/, false /*appAsLib*/);
}private int addAssetPathInternal(String path, boolean overlay, boolean appAsLib) {......//此处有没有很熟悉,跟前面Builder中最终调用的native方法是同一个,而且参数都是mApkAssets,在builder中有assetManager.mApkAssets = apkAssets;赋值语句,殊途同归。。。nativeSetApkAssets(mObject, mApkAssets, true);......
}

用反射调用addAssetPath方法比较简单,因为需要的参数较少,这儿就用addAssetpath方法做反射处理,代码如下:

public static Resources loadResources(Context context){Class<?> assetManagerCls = AssetManager.class;try {Object assetManagerObj = assetManagerCls.newInstance();Method addAssetPathMethod = assetManagerCls.getDeclaredMethod("addAssetPath",String.class);addAssetPathMethod.setAccessible(true);//此处第一个参数请根据自己运行环境(手机或者虚拟机),申请权限,并且能够访问的存储路径addAssetPathMethod.invoke(assetManagerObj,"/storage/emulated/0/plugin-debug.apk");//创建需要替换掉的ResourceResources resources = context.getResources();return new Resources((AssetManager) assetManagerObj,resources.getDisplayMetrics(),resources.getConfiguration());} catch (Exception e) {e.printStackTrace();}return null;}

紧接着在插件APP中创建BaseActivity如下:

public class BaseActivity extends AppCompatActivity {protected Context mContext;@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);Resources resources = LoadResourcesUtils.loadResources(getApplicationContext());// 替换这个 context 的resource 为我们自己通过反射写入的 resourcemContext = new ContextThemeWrapper(getBaseContext(),0);Class<?> contextCls = mContext.getClass();try {/*//ContextThemeWrapper.java@Overridepublic Resources getResources() {return getResourcesInternal();}private Resources getResourcesInternal() {if (mResources == null) {if (mOverrideConfiguration == null) {mResources = super.getResources();} else {final Context resContext = createConfigurationContext(mOverrideConfiguration);mResources = resContext.getResources();}}return mResources;}*/Field mResourcesField = contextCls.getDeclaredField("mResources");mResourcesField.setAccessible(true);mResourcesField.set(mContext,resources);} catch (Exception e) {e.printStackTrace();}}/*@Overridepublic Resources getResources() {Resources resources = LoadUtils.loadResources(getApplicationContext());return resources != null ? resources:super.getResources();}*/
}

插件中的所有Activity都要继承自BaseActivity:

package com.enjoy.plugin;import android.os.Bundle;
import android.util.Log;public class MainActivity extends BaseActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);Log.d("Rayman", "onCreate: this is plugin Activity...");String res = mContext.getResources().getString(R.string.test_plugin_res);Log.d("Rayman","onCreate plugin Activity res = "+res);//下面这种写法是不可用的,因为插件APP没有安装,在调用getResource的时候AssetManager无法获取资源路径,会报找不到资源的错误//Log.d("Rayman", "onCreate: xxx = "+getResources().getString(R.string.test_plugin_xxx));}
}

按照MainActivity中的方法调用资源就可以了,插件中其他的Activity调用资源也是相同的,到这儿通过反射,HOOK等技术调用插件App的Activity和资源就结束了。

总结一下,主要的操作步骤:

  1. 利用ClassLoader加载没有安装的插件APP,这个是前提条件;
  2. 利用HOOK、动态代理、反射等技术绕过AMS的检测,实现启动未注册的Activity;
  3. 利用反射技术,通过AssetManager加载插件APP的资源,实现对安装的APP资源的调用;

以上就是动态加载的基本内容了,实际操作可以参考github上滴滴的VirtualAPK的源码,参考实现,其实也没有必要重复造轮子,只是通过撸源码了解其中原理,做到知其然,知其所以然。

附上一个整个操作的可执行代码:

Android 插件化原理(三),通过hook启动插件Activity,修改Resources,调用插件资源相关推荐

  1. Android 插件化原理入门笔记

    Android开发笔记 onGithub 笔记,参考7.2中所列参考文章所写,DEMO地址在PluginTestDemoApplication 1.综述 2015年是Android插件化技术突飞猛进的 ...

  2. Android 插件化原理学习 —— Hook 机制之动态代理

    前言 为了实现 App 的快速迭代更新,基于 H5 Hybrid 的解决方案有很多,由于 webview 本身的性能问题,也随之出现了很多基于 JS 引擎实现的原生渲染的方案,例如 React Nat ...

  3. 【Android 插件化】Hook 插件化框架 ( hook 插件化原理 | 插件包管理 )

    Android 插件化系列文章目录 [Android 插件化]插件化简介 ( 组件化与插件化 ) [Android 插件化]插件化原理 ( JVM 内存数据 | 类加载流程 ) [Android 插件 ...

  4. Android 插件化原理解析——Hook机制之AMSPMS

    在前面的文章中我们介绍了DroidPlugin的Hook机制,也就是代理方式和Binder Hook:插件框架通过AOP实现了插件使用和开发的透明性.在讲述DroidPlugin如何实现四大组件的插件 ...

  5. android handler的机制和原理_Android 插件化原理——Hook机制之AMSamp;PMS解析

    在前面的文章中我们介绍了DroidPlugin的Hook机制,也就是代理方式和Binder Hook:插件框架通过AOP实现了插件使用和开发的透明性.在讲述DroidPlugin如何实现四大组件的插件 ...

  6. Android插件化原理解析——ContentProvider的插件化

    目前为止我们已经完成了Android四大组件中Activity,Service以及BroadcastReceiver的插件化,这几个组件各不相同,我们根据它们的特点定制了不同的插件化方案:那么对于Co ...

  7. Android 插件化原理解析——Service的插件化

    在 Activity生命周期管理 以及 广播的管理 中我们详细探讨了Android系统中的Activity.BroadcastReceiver组件的工作原理以及它们的插件化方案,相信读者已经对Andr ...

  8. Android 插件化原理解析——Activity生命周期管理

    之前的 Android插件化原理解析 系列文章揭开了Hook机制的神秘面纱,现在我们手握倚天屠龙,那么如何通过这种技术完成插件化方案呢?具体来说,插件中的Activity,Service等组件如何在A ...

  9. Android插件化原理—ClassLoader加载机制

    前面<Android 插件化原理学习 -- Hook 机制之动态代理>一文中我们探索了一下动态代理 hook 实现了 启动没有在 AndroidManifest.xml 中显式声明的 Ac ...

  10. Android 插件化原理 完胜360插件框架 技术实战

    性能优化 Android 性能优化 (一)APK高效瘦身 http://blog.csdn.net/whb20081815/article/details/70140063 Android 性能优化 ...

最新文章

  1. 内存对齐/字节对齐/数据对齐/地址总线对齐
  2. tf.Variable和 tf.get_variable区别(1)
  3. php rtc,php – webRTC与本地网络
  4. 神操作!员工索要工资遭遇“假转账”:转了生气又撤销
  5. C/C++语言的学习策略
  6. ShardingSphere JDBC 语句执行初探
  7. idea增加文件自动添加版本控制
  8. linux之vi,vim命令
  9. 打造人脉关系网,成就事业
  10. 你电脑上「最引以为豪」的软件是什么?
  11. Mycat分库分表案例demo
  12. as常用固定搭配_语法必看:as的几种固定用法
  13. Rayson API 框架分析系列之1: 简介
  14. mediawiki mysql配置_安装MediaWiki
  15. android开启照相功能,Android--启动拍照功能并返回结果
  16. 智能电子秤方案测脂肪模块设计
  17. 沙盒型源码安全解决方案
  18. 微信小程序 之 程序题
  19. 字幕:《李开复谈创新思维》上
  20. 记SWPU2021 GFCTF线下AWD赛

热门文章

  1. 台式计算机识别不了u盘启动,台式机不认U盘启动解决方法
  2. TOP100summit:【分享实录-猫眼电影】业务纵横捭阖背后的技术拆分与融合
  3. 猫眼电影排行榜前100爬取案例学习笔记
  4. 番外4:自动进行功放输出阻抗匹配设计(匹配至4次谐波)
  5. shell实现简单计算机功能,Shell 实现简单计算器功能(示例代码)
  6. 使用mqtt.fx连接腾讯云IoT Cloud——超详细
  7. mysql索引失效的原因
  8. 腾讯微云下载慢解决办法
  9. RTD\RTK\PPK\PPP\DGPS\地基增强系统\星基增强系统
  10. 物联网安全漏洞有哪些