学习插件化前需要了解类加载器、反射及动态代理等基本知识
技术方案:
1.宿主apk和插件apk都是使用PathClassLoader加载,合并宿主和插件的ClassLoader
2.宿主apk资源和插件apk资源是隔离的,重写Activity的getResources和getAssets
3.Hook IActivityManager.startActivity和ActivityThread.mH.mCallback来骗过AMS对Activity的检测
支持范围:
1.四大组件只支持Activity
现在只对插件中的Activity做了支持
2.Activity具有生命周期
因为在new Activity之前就已经替换掉占坑Activity,之后的操作都是插件Activity的参与的,所以插件Activity具有生命周期
3.Activity的launchMode只支持standard
本项目中只支持一个占坑的Activity,占坑的Activity的launchMode是什么,启动的插件Activity就是什么launchMode,如果需要支持四种launchMode,需要在manifest中分别注册四种启动模式的Activity用于占坑,具体实现可以参考VirtualAPK
插件化是将项目拆分成多个模块,分别对应一个宿主模块和多个插件模块,宿主和插件都是一个独立的工程,可以生成相应的apk,打包时可以将插件apk放入宿主包中也可以分开通过网络获取,插件化是用于加载插件的一种技术。插件化有助于减少宿主APP项目功能并减少宿主APK文件过大的问题。
要想实现插件化框架,就需要解决以下三个技术点
1.插件化框架如何实现插件APK的类加载
2.插件化框架如何实现插件化APK的资源加载
3.插件化框架如何实现对四大组件的支持
通过context.getClassLoader()获取的是PathClassLoader(其实是通过LoadedApk获取),由类加载机制的『双亲委派』特性,只要有一个应用程序类由某一个ClassLoader加载,那么它引用到的别的类除非父加载器能加载,否则都是由这同一个加载器加载的(不遵循双亲委派模型的除外)。
PathClassLoader加载安装到系统中的apk文件中的class文件,DexClassLoader是加载未安装的apk,那么ClassLoader内部到底是怎么去加载类的呢,查看类加载器源码如下:
public abstract class ClassLoader {
private final ClassLoader parent;
public Class> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
protected Class> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
// First, check if the class has already been loaded
Class> c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
c = findClass(name);
}
}
return c;
}
protected final Class> findLoadedClass(String name) {
ClassLoader loader;
if (this == BootClassLoader.getInstance())
loader = null;
else
loader = this;
return VMClassLoader.findLoadedClass(loader, name);
}
private Class> findBootstrapClassOrNull(String name) {
return null;
}
protected Class> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
}
//可以加载*.jar和*.apk中的类文件,可以加载并没有安装到系统中的class类,所以DexClassLoader是动态加载的核心
public class DexClassLoader extends BaseDexClassLoader {
/**
dexPath:指定要加载的dex文件路径
optimizedDirectory:指定的dexPath文件要被拷贝到那个路径(应用程序内部路径)中,不能为空
librarySearchPath :native库的路径,可为空
parent:父类加载器
*/
public DexClassLoader(String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) {
super((String)null, (File)null, (String)null, (ClassLoader)null);
}
}
//只能加载已经安装到系统中的dex文件
public class PathClassLoader extends BaseDexClassLoader {
public PathClassLoader(String dexPath, ClassLoader parent) {
super((String)null, (File)null, (String)null, (ClassLoader)null);
}
/**
//和DexClassLoader的构造比少了optimizedDirectory参数,正是因为此,PathClassLoader只能加载已经安装到系统中的apk中的dex文件(类)
dexPath:指定要加载的dex文件路径
librarySearchPath :native库的路径,可为空
parent:父类加载器
*/
public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
super((String)null, (File)null, (String)null, (ClassLoader)null);
}
}
public class BaseDexClassLoader extends ClassLoader {
private final DexPathList pathList;
@Override
protected Class> findClass(String name) throws ClassNotFoundException {
List suppressedExceptions = new ArrayList();
Class c = pathList.findClass(name, suppressedExceptions);
if (c == null) {
ClassNotFoundException cnfe = new ClassNotFoundException(
"Didn't find class \"" + name + "\" on path: " + pathList);
for (Throwable t : suppressedExceptions) {
cnfe.addSuppressed(t);
}
throw cnfe;
}
return c;
}
}
final class DexPathList {
private static final String DEX_SUFFIX = ".dex";
private static final String zipSeparator = "!/";
/**
* class definition context
*/
private final ClassLoader definingContext;
/**
* List of dex/resource (class path) elements.
* Should be called pathElements, but the Facebook app uses reflection
* to modify 'dexElements' (http://b/7726934).
*/
private Element[] dexElements;
public DexPathList(ClassLoader definingContext, ByteBuffer[] dexFiles) {
this.definingContext = definingContext;
//...
this.dexElements = makeInMemoryDexElements(dexFiles, suppressedExceptions);
//...
}
private static Element[] makeInMemoryDexElements(ByteBuffer[] dexFiles,
List suppressedExceptions) {
Element[] elements = new Element[dexFiles.length];
int elementPos = 0;
for (ByteBuffer buf : dexFiles) {
try {
DexFile dex = new DexFile(buf);
elements[elementPos++] = new Element(dex);
} catch (IOException suppressed) {
System.logE("Unable to load dex file: " + buf, suppressed);
suppressedExceptions.add(suppressed);
}
}
if (elementPos != elements.length) {
elements = Arrays.copyOf(elements, elementPos);
}
return elements;
}
public Class> findClass(String name, List suppressed) {
for (Element element : dexElements) {
Class> clazz = element.findClass(name, definingContext, suppressed);
if (clazz != null) {
return clazz;
}
}
if (dexElementsSuppressedExceptions != null) {
suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
}
return null;
}
static class Element {
private final File path;
private final DexFile dexFile;
private ClassPathURLStreamHandler urlHandler;
private boolean initialized;
public Class> findClass(String name, ClassLoader definingContext,
List suppressed) {
return dexFile != null ? dexFile.loadClassBinaryName(name, definingContext, suppressed)
: null;
}
}
}
public final class DexFile {
private Object mCookie;
private Object mInternalCookie;
private final String mFileName;
public Class loadClass(String name, ClassLoader loader) {
String slashName = name.replace('.', '/');
return loadClassBinaryName(slashName, loader, null);
}
public Class loadClassBinaryName(String name, ClassLoader loader, List suppressed) {
return defineClass(name, loader, mCookie, this, suppressed);
}
private static Class defineClass(String name, ClassLoader loader, Object cookie,
DexFile dexFile, List suppressed) {
Class result = null;
try {
result = defineClassNative(name, loader, cookie, dexFile);
} catch (NoClassDefFoundError e) {
if (suppressed != null) {
suppressed.add(e);
}
} catch (ClassNotFoundException e) {
if (suppressed != null) {
suppressed.add(e);
}
}
return result;
}
private static native Class defineClassNative(String name, ClassLoader loader, Object cookie,
DexFile dexFile)
throws ClassNotFoundException, NoClassDefFoundError;
}
由此可以类加载流程:ClassLoader.findClass() -> BaseDexClassLoader.findClass() -> DexPathList.findClass() -> DexFile.loadClassBinaryName(),而DexPathList中的findClass()是通过类型是Element[]的字段dexElements类完成的,而每个Element对应一个dex或apk等。所以系统的类加载器PathClassLoader所能加载的类对应的dex都存储在Element中,所以能否加载某些类的关键在于Element。apk被安装之后,apk文件的代码以及资源会被系统存放在固定的目录(比如/data/app/package_name/base-1.apk )系统在进行类加载的时候,会自动去这一个或者几个特定的路径来寻找这个类。而插件apk系统类加载器PathClassLoader是不知道存在哪里,自然无法加载插件中的类或Activity。在Activity启动的时候,Activity的创建是由什么加载的,可以在Activity的启动流程代码中发现,如下:
java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
StrictMode.incrementExpectedActivityCount(activity.getClass());
r.intent.setExtrasClassLoader(cl);
可以发现activity 是使用ClassLoader对象cl
把这个类加载进虚拟机,最后使用反射创建了这个Activity类的实例对象。而这个ClassLoader就是PathClassLoader,如果我们Hook ClassLoader,使其获取的ClassLoader是DexClassLoader,让其可以加载插件apk中的类,就可以解决无法加载插件类问题。
由此可以发现解决这个问题有两个思路,要么全盘接管这个类加载的过程;要么告知系统我们使用的插件存在于哪里,让系统帮忙加载。则:
方式一:宿主和插件隔离,全盘接管,每个插件构造自己的ClassLoader
通过r.packageInfo.getClassLoader()可知,ClassLoader是由r.packageInfo返回的对象回去,跟踪代码发现r.packageInfo是LoadedApk类型,而LoadedApk对象是APK文件在内存中的表示,LoadedApk创建在
public final LoadedApk getPackageInfoNoCheck(ApplicationInfo ai,
CompatibilityInfo compatInfo) {
return getPackageInfo(ai, compatInfo, null, false, true, false);
}
private LoadedApk getPackageInfo(ApplicationInfo aInfo, CompatibilityInfo compatInfo,
ClassLoader baseLoader, boolean securityViolation, boolean includeCode,
boolean registerPackage) {
final boolean differentUser = (UserHandle.myUserId() != UserHandle.getUserId(aInfo.uid));
synchronized (mResourcesManager) {
WeakReference ref;
if (differentUser) {
// Caching not supported across users
ref = null;
} else if (includeCode) {
ref = mPackages.get(aInfo.packageName);
} else {
ref = mResourcePackages.get(aInfo.packageName);
}
LoadedApk packageInfo = ref != null ? ref.get() : null;
if (packageInfo == null || (packageInfo.mResources != null
&& !packageInfo.mResources.getAssets().isUpToDate())) {
if (localLOGV) Slog.v(TAG, (includeCode ? "Loading code package "
: "Loading resource-only package ") + aInfo.packageName
+ " (in " + (mBoundApplication != null
? mBoundApplication.processName : null)
+ ")");
packageInfo =
new LoadedApk(this, aInfo, compatInfo, baseLoader,
securityViolation, includeCode &&
(aInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0, registerPackage);
if (mSystemThread && "android".equals(aInfo.packageName)) {
packageInfo.installSystemApplicationInfo(aInfo,
getSystemContext().mPackageInfo.getClassLoader());
}
if (differentUser) {
// Caching not supported across users
} else if (includeCode) {
mPackages.put(aInfo.packageName,
new WeakReference(packageInfo));
} else {
mResourcePackages.put(aInfo.packageName,
new WeakReference(packageInfo));
}
}
return packageInfo;
}
}
ClassLoader是通过LoadedApk获取的,LoadedApk是apk在内存的表示,所以我们就只需要将插件apk构造成LoadedApk,而通过该LoadedApk获取的ClassLoader对象用可以加载未安装apk的DexClassLoader来加载,然后将构造的LoadedApk存入WeakReference
方式二:宿主和插件合并,系统帮忙,将插件apk添加进系统ClassLoader (我使用了这种,简单)
PathClassLoader是无法加载插件apk中的类的,通过上面分析可知,ClassLoader最终是通过Element中的DexFile加载类的,而Element对应一个dex或apk的存放路径,则我们可以插件apk的路径手动构造出Element或使用DexClassLoader加载插件apk后从DexClassLoader中取出Element,然后将插件对应的Element合并到系统加载器PathLoaderLoader中,从而使得PathClassLoader具有加载插件apk的能力。现在MultiDex和很多热修复框架都是通过改变DexPathList中的dexElements来实现的。
DexClassLoader如何加载插件apk,代码如下:
DexClassLoader dcl = new DexClassLoader("apk路径", getDir("opt", MODE_PRIVATE).getAbsolutePath(), null, getClassLoader());
Class> Cls = dcl.loadClass("com.cj.oneplugin.SecondActivity");
如何合并DexClassLoader和PathClassLoader中的Element,代码如下:
//获取PathClassLoader(BaseDexClassLoader)的DexPathList对象变量pathList
Object pathList = ReflectUtil.getFieldValue(BaseDexClassLoader.class, pathClassLoaderClass, "pathList");
//获取DexPathList的Element[]对象变量dexElements
Object[] dexElements = (Object[]) ReflectUtil.getFieldValue(pathList.getClass(),pathList,"dexElements");
//获取DexClassLoader(BaseDexClassLoader)的DexPathList对象变量pathList
Object pathList = ReflectUtil.getFieldValue(BaseDexClassLoader.class, dexClassLoaderClass, "pathList");
//获取DexPathList的Element[]对象变量dexElements
Object[] dexElements = (Object[]) ReflectUtil.getFieldValue(pathList.getClass(),pathList,"dexElements");
//合并到PathClassLoader
本项目加载插件apk中的类使用方案:合并宿主和插件的ClassLoader (简单),插件中的所有类都使用PathClassLoader类加载
如果直接启动插件中的Activity,未对资源进行处理的话,会发现虽然加载的是插件中的Activity,但是Activity中的布局和资源都是加载的是宿主中的,就算把插件中的资源合并过来,也会产生资源冲突,具体分析如下:
1).资源ID冲突分析
从打包流程中可知,Application Resources -> aapt -> R.java和resouces.arsc ,而资源文件都有一个唯一的ID,ID的生成规则是0x7f010000(PackageId(7f) + TypeId(01开始,有attr,color,string等) + ItemValue(0001开始)),因为插件apk也是一个apk,生成R.java的规则和宿主的R.java是一样的,所以就会导致插件的资源ID和宿主的资源ID重复,导致加载插件中的资源时,加载的确实宿主中同ID的资源文件。
加载资源的方式有两种 (我选了方式2,简单)
1).方式1,资源合并
要想资源合并,就需要修改插件中资源ID生成的规则,则需要修改aapt工具源码,发现可以通过修改PackageId让宿主资源ID和插件资源ID予以区分,因为PackageId的只都是0x7f,插件中则可以修改成0x71,0x72等,具体修改方式见。
还可以干预编译过程,修改resouces.arsc和R.java
2).方式2,资源隔离
应用程序的每一个Activity组件都关联有一个ContextImpl对象,这个ContextImpl对象就是用来描述Activity组件的运行上下文环境的。我们在Activity组件调用的大部分成员函数都是转发给与它所关联的一个ContextImpl对象的对应的成员函数来处理的,其中就包括用来访问应用程序资源的两个成员函数getResources和getAssets。所以我只需要让插件中的Activity继承BasePluginActivity,BasePluginActivity中重写getResources和getAssets,让其返回插件apk生成的Resources和AssetManager,读取插件apk中的资源信息。只有这样严格的隔离,才能防止插件和宿主、插件之间的资源id不会冲突。
/**
* 插件中的Activity需要继承BasePluginActivity
* 主要用于资源隔离
*/
public abstract class BasePluginActivity extends AppCompatActivity {
private PluginInfo pluginInfo;
@Override
protected void attachBaseContext(Context newBase) {
super.attachBaseContext(newBase);
pluginInfo = PluginManager.getInstance().getPluginInfo(getApkPackageName());
}
/**
* 插件apk的包名
* @return
*/
public abstract String getApkPackageName();
@Override
public Resources getResources() {
return (pluginInfo == null || pluginInfo.getResources() == null) ? super.getResources() : pluginInfo.getResources();
}
@Override
public AssetManager getAssets() {
return (pluginInfo == null || pluginInfo.getAssetManager() == null) ? super.getAssets() : pluginInfo.getAssetManager();
}
}
在没有处理类加载问题的时候,四大组件Activity在使用DexClassLoader加载时并且启动Activity时,会直接报类找不到异常而崩溃,因为在Activity的生命周期中,系统中Activity是使用系统类加载器PathClassLoader加载的。因为插件中的类并不是已经安装到系统dex中的类,未安装到系统中的类使用PathClassLoader加载是加载不到的。通过上面分析可以使用宿主和插件ClassLoader合并的方式来解决加载插件类问题,这样PathClassLoader就可以加载插件中的四大组件类了(热修复也是这种原理),这样加载插件中的四大组件就可以在应用中启动了。也就是合并宿主和插件的ClassLoader ,这样一来,加载插件中的类就可以像加载宿主中的类一样,但是需要注意插件中的类不可以和宿主重复,合并代码如下:
//获取PathClassLoader
ClassLoader pathClassLoaderClass = context.getClassLoader();
//获取PathClassLoader(BaseDexClassLoader)的DexPathList对象变量pathList
Object pathList = ReflectUtil.getFieldValue(BaseDexClassLoader.class, pathClassLoaderClass, "pathList");
//获取DexPathList的Element[]对象变量dexElements
Object[] dexElements = (Object[]) ReflectUtil.getFieldValue(pathList.getClass(),pathList,"dexElements");
//获得Element类型
Class> dexElementsType = dexElements.getClass().getComponentType();
//创建一个新的Element[], 将用于替换原始的数组
Object[] newdexElementList = (Object[]) Array.newInstance(dexElementsType, dexElements.length + 1);
//构造插件的Element
File apkFile = new File(apkPath);
File optDexFile = new File(apkPath.replace(".apk", ".dex"));
Class[] parameterTypes = {DexFile.class,File.class};
Object[] initargs = {DexFile.loadDex(apkPath, optDexFile.getAbsolutePath(), 0),apkFile};
Object pluginDexElements = ReflectUtil.newInstance(dexElementsType, parameterTypes, initargs);
Object[] pluginDexElementsList = new Object[]{pluginDexElements};
//把原来PathClassLoader中的elements复制进去新的Element[]中
System.arraycopy(dexElements, 0, newdexElementList, 0, dexElements.length);
//把插件的element复制进去新的Element[]中
System.arraycopy(pluginDexElementsList, 0, newdexElementList, dexElements.length, pluginDexElementsList.length);
//替换原来PathClassLoader中的dexElements值
ReflectUtil.setFieldValue(pathList.getClass(),pathList,"dexElements",newdexElementList);
在启动Activity的时候,AMS会Activity是否在manifest中注册进行检测,如果没有注册就会报错。那么如果绕过AMS的检测呢?两种方案:(我选择了第二种)
方案一:Hook Instrumentation (简单一点)
public class Instrumentation {
//启动Activity的时候,调用此方法时,替换调Intent
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
}
//AMS检测后,创建Activity之前替换回Intent
public Activity newActivity(ClassLoader cl, String className,
Intent intent)
throws InstantiationException, IllegalAccessException,
ClassNotFoundException {
}
}
方案二:Hook IActivityManager.startActivity和ActivityThread.mH.mCallback
不管方案一和方案二都需要熟悉Activity的启动流程,这样才能在合适的地方绕过清单文件检测,下面是简单的Activity代码启动执行流程
//******************************************************************************
类的继承关系,方便阅读源码
SDK 28
class ActivityThread extends ClientTransactionHandler{
}
//可以放入对象池的所有生命周期项的基本接口。
interface ObjectPoolItem{
}
//从服务器到客户端的各个请求的基本接口。
//它们中的每一个都可以在调度之前准备好,并最终执行。
interface BaseClientRequest extends ObjectPoolItem{
}
//可以调度和执行的客户端回调消息。
//这些示例可能是Activity配置更改,多窗口模式更改,活动结果交付等
class ClientTransactionItem implements BaseClientRequest{
}
//请求Activity应达到的生命周期状态。
class ActivityLifecycleItem extends ClientTransactionItem{
}
//请求将Activity移至暂停状态。
class PauseActivityItem extends ActivityLifecycleItem{
}
//请求启动Activity
public class LaunchActivityItem extends ClientTransactionItem {
}
//*******************************************************************************
Activity的启动流程
SDK 28
Activity.java
startActivity() -> startActivity() -> startActivityForResult() -> mInstrumentation.execStartActivity()
Instrumentation.java
execStartActivity() -> ActivityManager.getService().startActivity()
ActivityManagerService.java
startActivity() -> startActivityAsUser() -> startActivityAsUser() -> mActivityStartController.obtainStarter().setMayWait(){mRequest.mayWait = true}.execute()
ActivityStarter.java
execute() -> mRequest.mayWait = true -> startActivityMayWait() -> startActivity() -> startActivity() -> startActivity() -> startActivityUnchecked() -> mSupervisor.resumeFocusedStackTopActivityLocked()
ActivityStackSupervisor.java
resumeFocusedStackTopActivityLocked() -> resumeFocusedStackTopActivityLocked() -> targetStack.resumeTopActivityUncheckedLocked()
ActivityStack.java
resumeTopActivityUncheckedLocked() -> resumeTopActivityInnerLocked()
//1.执行Pause
1.startPausingLocked() -> mService.getLifecycleManager().scheduleTransaction(prev.app.thread, prev.appToken,PauseActivityItem.obtain(prev.finishing, userLeaving,prev.configChangeFlags, pauseImmediately))
ClientLifecycleManager.java
scheduleTransaction() ->{传递的ActivityLifecycleItem对象(PauseActivityItem)存储在ClientTransaction中的mLifecycleStateRequest字段} scheduleTransaction() -> transaction.schedule()
ClientTransaction.java
schedule() -> mClient.scheduleTransaction(this)
ActivityThread.ApplicationThread.java
scheduleTransaction() -> ActivityThread.this.scheduleTransaction(transaction)
ClientTransactionHandler.java
scheduleTransaction() -> sendMessage(ActivityThread.H.EXECUTE_TRANSACTION, transaction) ->
ActivityThread.java
ActivityThread.H.EXECUTE_TRANSACTION -> mTransactionExecutor.execute(transaction)
TransactionExecutor.java
execute() ->
1. executeCallbacks(transaction) //现在还没有Callback
2. executeLifecycleState(transaction) -> lifecycleItem.execute() -> {lifecycleItem是ClientTransaction中的mLifecycleStateRequest字段,即PauseActivityItem}
PauseActivityItem.java
execute() -> client.handlePauseActivity()
ActivityThread:ClientTransactionHandler.java
handlePauseActivity() -> performPauseActivity() -> {callActivityOnSaveInstanceState()} performPauseActivityIfNeeded() -> mInstrumentation.callActivityOnPause()
Instrumentation.java
callActivityOnPause() -> activity.performPause()
Activity.java
performPause() -> onPause() //onPause
//onCreate
2.mStackSupervisor.startSpecificActivityLocked()
ActivityStackSupervisor.java
startSpecificActivityLocked()
1.if (app != null && app.thread != null)//App已启动
realStartActivityLocked() -> goto realStartActivityLocked //启动activity
2.//App未启动
mService.startProcessLocked()
ActivityManagerService.java
startProcessLocked() -> startProcessLocked() -> startProcessLocked() -> startProcessLocked() -> startProcessLocked() -> startProcessLocked() -> startProcess() -> Process.start()
Process.java
Process.start() -> zygoteProcess.start()
ZygoteProcess.java
start() -> startViaZygote() -> zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi), argsForZygote) -> openZygoteSocketIfNeeded(abi) -> ZygoteState.connect(mSocket) //Zygote并通过socket通信的方式让Zygote进程fork出一个新的进程,并根据传递的”android.app.ActivityThread”字符串,反射出该对象并执行ActivityThread的main方法对其进行初始化
ActivityThread.java
main() -> thread.attach() -> mgr.attachApplication()
ActivityManagerService.java
attachApplication() -> attachApplicationLocked() -> mStackSupervisor.attachApplicationLocked(app)
ActivityStackSupervisor.java
attachApplicationLocked() -> realStartActivityLocked() -> goto realStartActivityLocked //启动activity
rerealStartActivityLocked:
ActivityStackSupervisor.java
realStartActivityLocked() -> {clientTransaction.addCallback(LaunchActivityItem.obtain(new Intent(r.intent)...),lifecycleItem = ResumeActivityItem ,clientTransaction.setLifecycleStateRequest(lifecycleItem)} -> mService.getLifecycleManager().scheduleTransaction(clientTransaction)
ClientLifecycleManager.java
scheduleTransaction() -> {传递的ActivityLifecycleItem对象(ResumeActivityItem)存储在ClientTransaction中的mLifecycleStateRequest字段,mActivityCallbacks中存储的是LaunchActivityItem} transaction.schedule()
ClientTransaction.java
schedule() -> mClient.scheduleTransaction(this)
ActivityThread.ApplicationThread.java
scheduleTransaction() -> ActivityThread.this.scheduleTransaction(transaction)
ClientTransactionHandler.java
scheduleTransaction() -> sendMessage(ActivityThread.H.EXECUTE_TRANSACTION, transaction) ->
ActivityThread.java
ActivityThread.H.EXECUTE_TRANSACTION -> mTransactionExecutor.execute(transaction)
TransactionExecutor.java
execute()
1.执行LaunchActivityItem,mActivityCallbacks
executeCallbacks(transaction) -> item.execute()
LaunchActivityItem.java
execute() -> handleLaunchActivity()
ActivityThread.ApplicationThread.java
handleLaunchActivity() -> performLaunchActivity()
1.mInstrumentation.newActivity()
Instrumentation.java
newActivity() -> getFactory(pkg).instantiateActivity(cl, className, intent)
AppComponentFactory.java
instantiateActivity() -> (Activity) cl.loadClass(className).newInstance() //反射创建Activity
2.r.packageInfo.makeApplication()
LoadedApk.java
makeApplication() -> {if (mApplication != null) return mApplication} -> {ContextImpl.createAppContext()} -> mActivityThread.mInstrumentation.newApplication() {instrumentation.callApplicationOnCreate(app) -> app.onCreate()} //2.Application : onCreate
Instrumentation.java
newApplication() -> getFactory(context.getPackageName()).instantiateApplication(cl,className) {app.attach(context)} //1.Application : attach
AppComponentFactory.java
(Application) cl.loadClass(className).newInstance()
3.activity.attach()
Activity.java
attach() -> {mWindow = new PhoneWindow()}
4.activity.setTheme(theme)
Activity.java
setTheme() -> mWindow.setTheme(resid)
5.mInstrumentation.callActivityOnCreate()
Instrumentation.java
callActivityOnCreate() -> activity.performCreate()
Activity.java
performCreate() -> performCreate() -> onCreate() //onCreate
2.执行ResumeActivityItem,mLifecycleStateRequest
executeLifecycleState(transaction)
1.cycleToPath()
2.lifecycleItem.execute() //lifecycleItem为ResumeActivityItem
ResumeActivityItem.java
execute() -> client.handleResumeActivity()
ActivityThread.ApplicationThread.java
handleResumeActivity()
1.performResumeActivity()
1.deliverNewIntents() -> mInstrumentation.callActivityOnNewIntent()
Instrumentation.java
callActivityOnNewIntent() -> activity.performNewIntent()
Activity.java
performNewIntent() -> onNewIntent(intent) //onNewIntent
2.deliverResults() -> r.activity.dispatchActivityResult()
Activity.java
dispatchActivityResult() -> onActivityResult() //onActivityResult
3.r.activity.performResume()
Activity.java
performResume()
1.performRestart()
1 mInstrumentation.callActivityOnRestart()
Instrumentation.java
callActivityOnRestart() -> activity.onRestart()
Activity.java
onRestart() //onRestart
2.performStart(reason) -> mInstrumentation.callActivityOnStart(this)
Instrumentation.java
callActivityOnStart() -> activity.onStart()
Activity.java
onStart() //onStart
2.mInstrumentation.callActivityOnResume(this)
Instrumentation.java
callActivityOnResume() -> activity.onResume()
Activity.java
onResume() //onResume
2.Looper.myQueue().addIdleHandler(new Idler())
ActivityThread.Idler.java
queueIdle() -> am.activityIdle()
ActivityManagerService.java
activityIdle() -> mStackSupervisor.activityIdleInternalLocked()
ActivityStackSupervisor.java
activityIdleInternalLocked() -> stack.stopActivityLocked(r)
ActivityStack.java
stopActivityLocked() -> mService.getLifecycleManager().scheduleTransaction(r.app.thread,r.appToken,StopActivityItem.obtain(r.visible, r.configChangeFlags)) {StopActivityItem存在ClientTransaction的mLifecycleStateRequest中}
ClientLifecycleManager.java
scheduleTransaction() -> scheduleTransaction() -> transaction.schedule()
ClientTransaction.java
schedule() -> mClient.scheduleTransaction(this)
ActivityThread.ApplicationThread.java
scheduleTransaction() -> ActivityThread.this.scheduleTransaction()
ClientTransactionHandler.java
scheduleTransaction() -> sendMessage(ActivityThread.H.EXECUTE_TRANSACTION, transaction)
ActivityThread.java
ActivityThread.H.EXECUTE_TRANSACTION -> mTransactionExecutor.execute(transaction)
TransactionExecutor.java
execute() -> executeLifecycleState() -> lifecycleItem.execute()
StopActivityItem.java
execute() -> client.handleStopActivity()
ActivityThread.ClientTransactionHandler.java
handleStopActivity() -> performStopActivityInner() -> {performPauseActivityIfNeeded() -> if (r.paused) return } -> callActivityOnStop() -> r.activity.performStop()
Activity.java
performStop() -> mInstrumentation.callActivityOnStop(this)
Instrumentation.java
callActivityOnStop() -> activity.onStop()
Activity.java
onStop() // onStop
从Activity的启动中可以发现,可以在ActivityManager.getService().startActivity()这个地方把Intent送AMS检测之前替换调Intent(或Intent中要启动Activity的className),和ClientTransaction.mActivityCallbacks,在ActivityThread.mH处理的msg.what是EXECUTE_TRANSACTION的时候,取出mActivityCallbacks中第一个是LaunchActivityItem的元素,获取LaunchActivityItem中的mIntent,替换mIntent中的占坑Activity,使用插件中的Activity。(这里是针对SDK 28的Hook流程,SDK 25、26Hook有所不同,项目源码中做了3个版本的兼容处理)
Hook StartActivity的部分代码如下:
Class> iActivityManagerClass = Class.forName("android.app.IActivityManager");
//获取Singleton实例
Class> activityManagerClass = Class.forName("android.app.ActivityManager");
Field iActivityManagerSingletonField = activityManagerClass.getDeclaredField("IActivityManagerSingleton");
iActivityManagerSingletonField.setAccessible(true);
Object singleton = iActivityManagerSingletonField.get(null);
//从Singleton实例中获取IActivityManager对象mInstance
Class> singletonClass = Class.forName("android.util.Singleton");
Field mInstanceField = singletonClass.getDeclaredField("mInstance");
mInstanceField.setAccessible(true);
Object am = mInstanceField.get(singleton);
Object amProxy = Proxy.newProxyInstance(
HookStartActivityApi26.class.getClassLoader(),
new Class[]{iActivityManagerClass},
new StartActivityInvocationHandler(context, am, proxyClass)
);
//重新指定代理的IActivityManager实例
mInstanceField.set(singleton, amProxy);
Hook LauncherActivity的部分代码如下:
//先把相关@hide的类都建好
Class> ClientTransactionClz = Class.forName("android.app.servertransaction.ClientTransaction");
Class> LaunchActivityItemClz = Class.forName("android.app.servertransaction.LaunchActivityItem");
Field mActivityCallbacksField = ClientTransactionClz.getDeclaredField("mActivityCallbacks");//ClientTransaction的成员
mActivityCallbacksField.setAccessible(true);
//类型判定,好习惯
if (!ClientTransactionClz.isInstance(msg.obj)) return true;
Object mActivityCallbacksObj = mActivityCallbacksField.get(msg.obj);//根据源码,在这个分支里面,msg.obj就是 ClientTransaction类型,所以,直接用
//拿到了ClientTransaction的List mActivityCallbacks;
List list = (List) mActivityCallbacksObj;
if (list.size() == 0) return true;
Object LaunchActivityItemObj = list.get(0);//所以这里直接就拿到第一个就好了
if (!LaunchActivityItemClz.isInstance(LaunchActivityItemObj)) return true;
//这里必须判定 LaunchActivityItemClz,
// 因为 最初的ActivityResultItem传进去之后都被转化成了这LaunchActivityItemClz的实例
Field mIntentField = LaunchActivityItemClz.getDeclaredField("mIntent");
mIntentField.setAccessible(true);
Intent mIntent = (Intent) mIntentField.get(LaunchActivityItemObj);
if(mIntent.hasExtra(EXTRA_ORIGIN_INTENT)) {
Intent oriIntent = (Intent) mIntent.getExtras().getParcelable(EXTRA_ORIGIN_INTENT);
//将占坑intent替换成插件中的intent
mIntentField.set(LaunchActivityItemObj, oriIntent);
//解决AppCompatActivity重新检测清单文件注册问题
handleAppCompatActivityCheck(mIntent);
}
以上就是四大组件之Activity绕过AMS检测的处理方式,绕过Service组件的原理和Activity类似。
出现的问题
1.在加载插件中的Activity的时候,如果Activity使用的是AppCompatActivity,就会报如下错误
2019-04-18 16:28:56.812 15184-15184/com.example.hookplugin E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.hookplugin, PID: 15184
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.hookplugin/com.example.hookplugin.SecondActivity}: java.lang.IllegalArgumentException: android.content.pm.PackageManager$NameNotFoundException: ComponentInfo{com.example.hookplugin/com.example.hookplugin.SecondActivity}
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3157)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3268)
at android.app.ActivityThread.-wrap12(Unknown Source:0)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1888)
at android.os.Handler.dispatchMessage(Handler.java:109)
at android.os.Looper.loop(Looper.java:166)
at android.app.ActivityThread.main(ActivityThread.java:7367)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:469)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:963)
Caused by: java.lang.IllegalArgumentException: android.content.pm.PackageManager$NameNotFoundException: ComponentInfo{com.example.hookplugin/com.example.hookplugin.SecondActivity}
at android.support.v4.app.NavUtils.getParentActivityName(NavUtils.java:222) // 再次检测,点击该错误位置,跟踪代码,分析出现该错误的原因
at android.support.v7.app.AppCompatDelegateImplV9.onCreate(AppCompatDelegateImplV9.java:155)
at android.support.v7.app.AppCompatDelegateImplV14.onCreate(AppCompatDelegateImplV14.java:59)
at android.support.v7.app.AppCompatActivity.onCreate(AppCompatActivity.java:72)
at com.example.hookplugin.SecondActivity.onCreate(SecondActivity.java:12)
at android.app.Activity.performCreate(Activity.java:7337)
at android.app.Activity.performCreate(Activity.java:7328)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1219)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3110)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3268)?
at android.app.ActivityThread.-wrap12(Unknown Source:0)?
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1888)?
at android.os.Handler.dispatchMessage(Handler.java:109)?
at android.os.Looper.loop(Looper.java:166)?
at android.app.ActivityThread.main(ActivityThread.java:7367)?
at java.lang.reflect.Method.invoke(Native Method)?
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:469)?
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:963)?
Caused by: android.content.pm.PackageManager$NameNotFoundException: ComponentInfo{com.example.hookplugin/com.example.hookplugin.SecondActivity}
at android.app.ApplicationPackageManager.getActivityInfo(ApplicationPackageManager.java:430)
at android.support.v4.app.NavUtils.getParentActivityName(NavUtils.java:240)
at android.support.v4.app.NavUtils.getParentActivityName(NavUtils.java:219)
at android.support.v7.app.AppCompatDelegateImplV9.onCreate(AppCompatDelegateImplV9.java:155)?
at android.support.v7.app.AppCompatDelegateImplV14.onCreate(AppCompatDelegateImplV14.java:59)?
at android.support.v7.app.AppCompatActivity.onCreate(AppCompatActivity.java:72)?
at com.example.hookplugin.SecondActivity.onCreate(SecondActivity.java:12)?
at android.app.Activity.performCreate(Activity.java:7337)?
at android.app.Activity.performCreate(Activity.java:7328)?
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1219)?
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3110)?
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3268)?
at android.app.ActivityThread.-wrap12(Unknown Source:0)?
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1888)?
at android.os.Handler.dispatchMessage(Handler.java:109)?
at android.os.Looper.loop(Looper.java:166)?
at android.app.ActivityThread.main(ActivityThread.java:7367)?
at java.lang.reflect.Method.invoke(Native Method)?
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:469)?
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:963)?
原因分析
出现的因为是AppConpatActivity中又去重新检测了该Activity是否在Manifest中注册,根据报错信息(入口位置NavUtils.getParentActivityName),可以分析出现的原因如下:
NavUtils.java
public static String getParentActivityName(@NonNull Activity sourceActivity) {
try {
return getParentActivityName(sourceActivity, sourceActivity.getComponentName());
} catch (NameNotFoundException e) {
// Component name of supplied activity does not exist...?
throw new IllegalArgumentException(e);
}
}
@Nullable
public static String getParentActivityName(@NonNull Context context,
@NonNull ComponentName componentName)
throws NameNotFoundException {
PackageManager pm = context.getPackageManager(); //PackageManager 是ApplicationPackageManager对象
ActivityInfo info = pm.getActivityInfo(componentName, PackageManager.GET_META_DATA);
if (Build.VERSION.SDK_INT >= 16) {
String result = info.parentActivityName;
if (result != null) {
return result;
}
}
if (info.metaData == null) {
return null;
}
String parentActivity = info.metaData.getString(PARENT_ACTIVITY);
if (parentActivity == null) {
return null;
}
if (parentActivity.charAt(0) == '.') {
parentActivity = context.getPackageName() + parentActivity;
}
return parentActivity;
}
ContextImpl.java
每个ContextImpl中只会存在一份mPackageManager ,IPackageManager是AIDL(IBinder),是在ActivityThread也只会存在一份,而返回的PackageManager是ApplicationPackageManager对象
@Override
public PackageManager getPackageManager() {
if (mPackageManager != null) {
return mPackageManager;
}
IPackageManager pm = ActivityThread.getPackageManager();
if (pm != null) {
// Doesn't matter if we make more than one instance.
return (mPackageManager = new ApplicationPackageManager(this, pm));
}
return null;
}
ActivityThread.java
public static IPackageManager getPackageManager() {
if (sPackageManager != null) {
//Slog.v("PackageManager", "returning cur default = " + sPackageManager);
return sPackageManager;
}
IBinder b = ServiceManager.getService("package");
//Slog.v("PackageManager", "default service binder = " + b);
sPackageManager = IPackageManager.Stub.asInterface(b);
//Slog.v("PackageManager", "default service = " + sPackageManager);
return sPackageManager;
}
在ApplicationPackageManager中获取ActivityInfo
ApplicationPackageManager.java
@Override
public ActivityInfo getActivityInfo(ComponentName className, int flags)
throws NameNotFoundException {
try {
ActivityInfo ai = mPM.getActivityInfo(className, flags, mContext.getUserId());
if (ai != null) {
return ai;
}
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
throw new NameNotFoundException(className.toString());
}
ApplicationPackageManager中的mPM是IPackageManager,在ActivityThread中只存在一份,并且是interface,而且PackageManager的getActivityInfo也是调用IPackageManager中的getActivityInfo(className, flags, mContext.getUserId()),它是会重新去验证className对应的Activity是否在清单文件中注册,所以我们只需要hook IPackageManager中的getActivityInfo,替换第一个参数className,替换成清单文件中注册的Activity的className即可。
根据以上分析,只需要hook IPackageManager中的getActivityInfo,替换第一个参数className ,具体处理代码如下:
Class> atClass = Class.forName("android.app.ActivityThread");
Method getPackageManagerMethod = atClass.getDeclaredMethod("getPackageManager");
getPackageManagerMethod.setAccessible(true);
Object ipm = getPackageManagerMethod.invoke(null);
Object ipmProxy = Proxy.newProxyInstance(ipm.getClass().getClassLoader(),
ipm.getClass().getInterfaces(), new PackageManagerInvocationHandler(ipm,originIntent.getComponent(),intent.getComponent()));
Field sPackageManagerField = atClass.getDeclaredField("sPackageManager");
sPackageManagerField.setAccessible(true);
sPackageManagerField.set(null,ipmProxy);
public class PackageManagerInvocationHandler implements InvocationHandler{
private Object obj;
private ComponentName proxyClass;
public PackageManagerInvocationHandler(Object obj,ComponentName proxyClass) {
this.obj = obj;
this.proxyClass = proxyClass;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("getActivityInfo".equals(method.getName())) {
args[0] = proxyClass;
}
Object invoke = method.invoke(obj, args);
return invoke;
}
}
DroidPlugin | VirtualAPK | HookPlugin(我的Demo) | |
类加载 | 隔离 | 合并 | 合并 |
资源加载 | 隔离 | 合并 | 隔离 |
项目地址:https://github.com/xiaojinwei/HookPlugin
参考:https://blog.csdn.net/kc58236582/article/details/53187485
http://weishu.me/2016/04/05/understand-plugin-framework-classloader/
1.插件化框架无法加载aar,只能加载apk
插件化框架的原理是动态加载dex文件,而aar包里还没有dex文件,只有jar,并且这个jar也不是dex格式的,因为DexClassLoader只能加载dex,所以插件化框架无法加载aar。
可以使用:dx --dex --output=
DexFile.loadDex(apkPath, optDexFile.getAbsolutePath(), 0),DexFile对应一个x.dex文件路径,最后调用Native openDexFileNative()加载dex,如果非dex格式的文件会报如下错误:
java.io.IOException: No original dex files found for dex location /data/user/0/com.cj.plugin/files/twoplugin-debug.apk
twoplugin-debug.apk其实是aar,只是将其后缀改成了apk
Activity的启动流程
SDK 27
private class ApplicationThread extends IApplicationThread.Stub {}
public class ActivityManagerService extends IActivityManager.Stub
implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback {}
Activity -> startActivityForResult() ->
Instrumentation -> execStartActivity()
ActivityManager -> getService()
ActivityManagerService -> startActivity() -> startActivityAsUser()
ActivityStarter -> startActivityMayWait()
//ActivityStackSupervisor -> resolveIntent() -> resolveIntent()
//ActivityManagerService -> getPackageManagerInternalLocked()
ActivityStarter -> startActivityMayWait() -> startActivityLocked() -> startActivityLocked() -> startActivity() -> startActivity() -> startActivityUnchecked()
ActivityStackSupervisor -> resumeFocusedStackTopActivityLocked() -> resumeFocusedStackTopActivityLocked() ->
ActivityStack -> resumeTopActivityUncheckedLocked() -> resumeTopActivityInnerLocked() -> startPausingLocked() -> prev.app.thread.schedulePauseActivity
ActivityThread$ApplicationThread -> schedulePauseActivity() -> ActivityThread$H(Handler) -> PAUSE_ACTIVITY -> handlePauseActivity() -> performPauseActivity() -> performPauseActivity() -> performPauseActivityIfNeeded()
Instrumentation -> callActivityOnPause()
Activity -> performPause() -> onPause()
ActivityStack -> resumeTopActivityUncheckedLocked() -> resumeTopActivityInnerLocked() -> mStackSupervisor.startSpecificActivityLocked
ActivityStackSupervisor -> startSpecificActivityLocked() -> realStartActivityLocked() -> app.thread.scheduleLaunchActivity
ActivityThread$ApplicationThread -> scheduleLaunchActivity() -> ActivityThread$H.LAUNCH_ACTIVITY -> LAUNCH_ACTIVITY -> handleLaunchActivity() -> performLaunchActivity() ->
1.mInstrumentation.newActivity()
Instrumentation -> newActivity() -> (Activity)cl.loadClass(className).newInstance()
2.activity.attach()
Activity -> attach()
1).attachBaseContext(context)
2).mWindow = new PhoneWindow()
3.activity.setTheme(theme)
4.mInstrumentation.callActivityOnCreate
Instrumentation -> callActivityOnCreate() -> activity.performCreate()
Activity -> performCreate() -> performCreate() -> onCreate()
5.activity.performStart()
Activity -> performStart() -> mInstrumentation.callActivityOnStart()
Instrumentation -> callActivityOnStart() -> activity.onStart()
Activity -> onStart()
6.mInstrumentation.callActivityOnRestoreInstanceState
Instrumentation -> callActivityOnRestoreInstanceState() -> activity.performRestoreInstanceState
Activity -> performRestoreInstanceState() -> onRestoreInstanceState()
7.mInstrumentation.callActivityOnPostCreate
Instrumentation -> callActivityOnPostCreate() -> activity.onPostCreate()
Activity -> onPostCreate() -> onPostCreate()
ActivityStack -> resumeTopActivityUncheckedLocked() -> resumeTopActivityInnerLocked() -> next.app.thread.scheduleResumeActivity
ActivityThread$ApplicationThread -> scheduleResumeActivity() -> ActivityThread$H.RESUME_ACTIVITY -> RESUME_ACTIVITY -> handleResumeActivity()
1.performResumeActivity()
1).deliverNewIntents() -> Instrumentation.callActivityOnNewIntent
Instrumentation -> callActivityOnNewIntent() -> activity.performNewIntent()
Activity -> performNewIntent() -> onNewIntent()
2).deliverResults(r, r.pendingResults);
2).r.activity.performResume()
Activity -> performResume()
1.performRestart() -> mInstrumentation.callActivityOnRestart()
Instrumentation -> callActivityOnRestart() -> activity.onRestart()
Activity -> onRestart()
2.mInstrumentation.callActivityOnResume
Instrumentation -> callActivityOnResume() -> activity.onResume()
Activity -> onResume()
3.mFragments.dispatchResume()
2.wm = a.getWindowManager() -> wm.addView(decor,l)
3.performConfigurationChangedForActivity() -> activity.onConfigurationChanged()
Activity -> onConfigurationChanged()