Android 插件化原理(三),通过hook启动插件Activity,修改Resources,调用插件资源
此文章基于Android 插件化原理(一)和Android 插件化原理(二)
众所周知,在Android中,四大组件是需要在AndroidManifest.xml文件中注册之后才能调用的,但是插件APP可能并没有安装,只是放在终端的某个存储路径上,故系统会找不到插件里面用到的四大组件;那么如果宿主APP需要调用插件APP的四大组件呢,比如,宿主APP要启动插件APP中的某个Activity,需要怎么操作呢?
本篇文章只讲述两个问题:
- 宿主如何启动插件中的Activity;
- 插件APP不安装的情况下,插件APP中的资源并没有解压到系统data目录,需要怎么去调用插件中的资源(String,drawable等);
用到的技术:
- Hook Activity;
- Java动态代理;
- Java 反射;
- 熟悉AMS启动Activity相关流程,可参考https://blog.csdn.net/lj19851227/article/details/82562115;
- 熟悉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的大概位置:
- 在进入AMS之前,找HOOK点,将目标Activity(插件中的Activity)替换为ProxyActivity;
- 在从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的动作:
- 通过动态代理替换掉startActivity方法中的intent参数;
- 通过反射替换到调用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和资源就结束了。
总结一下,主要的操作步骤:
- 利用ClassLoader加载没有安装的插件APP,这个是前提条件;
- 利用HOOK、动态代理、反射等技术绕过AMS的检测,实现启动未注册的Activity;
- 利用反射技术,通过AssetManager加载插件APP的资源,实现对安装的APP资源的调用;
以上就是动态加载的基本内容了,实际操作可以参考github上滴滴的VirtualAPK的源码,参考实现,其实也没有必要重复造轮子,只是通过撸源码了解其中原理,做到知其然,知其所以然。
附上一个整个操作的可执行代码:
Android 插件化原理(三),通过hook启动插件Activity,修改Resources,调用插件资源相关推荐
- Android 插件化原理入门笔记
Android开发笔记 onGithub 笔记,参考7.2中所列参考文章所写,DEMO地址在PluginTestDemoApplication 1.综述 2015年是Android插件化技术突飞猛进的 ...
- Android 插件化原理学习 —— Hook 机制之动态代理
前言 为了实现 App 的快速迭代更新,基于 H5 Hybrid 的解决方案有很多,由于 webview 本身的性能问题,也随之出现了很多基于 JS 引擎实现的原生渲染的方案,例如 React Nat ...
- 【Android 插件化】Hook 插件化框架 ( hook 插件化原理 | 插件包管理 )
Android 插件化系列文章目录 [Android 插件化]插件化简介 ( 组件化与插件化 ) [Android 插件化]插件化原理 ( JVM 内存数据 | 类加载流程 ) [Android 插件 ...
- Android 插件化原理解析——Hook机制之AMSPMS
在前面的文章中我们介绍了DroidPlugin的Hook机制,也就是代理方式和Binder Hook:插件框架通过AOP实现了插件使用和开发的透明性.在讲述DroidPlugin如何实现四大组件的插件 ...
- android handler的机制和原理_Android 插件化原理——Hook机制之AMSamp;PMS解析
在前面的文章中我们介绍了DroidPlugin的Hook机制,也就是代理方式和Binder Hook:插件框架通过AOP实现了插件使用和开发的透明性.在讲述DroidPlugin如何实现四大组件的插件 ...
- Android插件化原理解析——ContentProvider的插件化
目前为止我们已经完成了Android四大组件中Activity,Service以及BroadcastReceiver的插件化,这几个组件各不相同,我们根据它们的特点定制了不同的插件化方案:那么对于Co ...
- Android 插件化原理解析——Service的插件化
在 Activity生命周期管理 以及 广播的管理 中我们详细探讨了Android系统中的Activity.BroadcastReceiver组件的工作原理以及它们的插件化方案,相信读者已经对Andr ...
- Android 插件化原理解析——Activity生命周期管理
之前的 Android插件化原理解析 系列文章揭开了Hook机制的神秘面纱,现在我们手握倚天屠龙,那么如何通过这种技术完成插件化方案呢?具体来说,插件中的Activity,Service等组件如何在A ...
- Android插件化原理—ClassLoader加载机制
前面<Android 插件化原理学习 -- Hook 机制之动态代理>一文中我们探索了一下动态代理 hook 实现了 启动没有在 AndroidManifest.xml 中显式声明的 Ac ...
- Android 插件化原理 完胜360插件框架 技术实战
性能优化 Android 性能优化 (一)APK高效瘦身 http://blog.csdn.net/whb20081815/article/details/70140063 Android 性能优化 ...
最新文章
- 内存对齐/字节对齐/数据对齐/地址总线对齐
- tf.Variable和 tf.get_variable区别(1)
- php rtc,php – webRTC与本地网络
- 神操作!员工索要工资遭遇“假转账”:转了生气又撤销
- C/C++语言的学习策略
- ShardingSphere JDBC 语句执行初探
- idea增加文件自动添加版本控制
- linux之vi,vim命令
- 打造人脉关系网,成就事业
- 你电脑上「最引以为豪」的软件是什么?
- Mycat分库分表案例demo
- as常用固定搭配_语法必看:as的几种固定用法
- Rayson API 框架分析系列之1: 简介
- mediawiki mysql配置_安装MediaWiki
- android开启照相功能,Android--启动拍照功能并返回结果
- 智能电子秤方案测脂肪模块设计
- 沙盒型源码安全解决方案
- 微信小程序 之 程序题
- 字幕:《李开复谈创新思维》上
- 记SWPU2021 GFCTF线下AWD赛
热门文章
- 台式计算机识别不了u盘启动,台式机不认U盘启动解决方法
- TOP100summit:【分享实录-猫眼电影】业务纵横捭阖背后的技术拆分与融合
- 猫眼电影排行榜前100爬取案例学习笔记
- 番外4:自动进行功放输出阻抗匹配设计(匹配至4次谐波)
- shell实现简单计算机功能,Shell 实现简单计算器功能(示例代码)
- 使用mqtt.fx连接腾讯云IoT Cloud——超详细
- mysql索引失效的原因
- 腾讯微云下载慢解决办法
- RTD\RTK\PPK\PPP\DGPS\地基增强系统\星基增强系统
- 物联网安全漏洞有哪些