學習插件化前需要了解類加載器、反射及動態代理等基本知識
技術方案:
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.插件化框架如何實現對四大組件的支持
一.插件化框架如何實現插件APK的類加載
通過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<Throwable> suppressedExceptions = new ArrayList<Throwable>();
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<IOException> 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<Throwable> 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<Throwable> 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<Throwable> suppressed) {
return defineClass(name, loader, mCookie, this, suppressed);
}
private static Class defineClass(String name, ClassLoader loader, Object cookie,
DexFile dexFile, List<Throwable> 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<LoadedApk> 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<LoadedApk>(packageInfo));
} else {
mResourcePackages.put(aInfo.packageName,
new WeakReference<LoadedApk>(packageInfo));
}
}
return packageInfo;
}
}
ClassLoader是通過LoadedApk獲取的,LoadedApk是apk在內存的表示,所以我們就只需要將插件apk構造成LoadedApk,而通過該LoadedApk獲取的ClassLoader對象用可以加載未安裝apk的DexClassLoader來加載,然後將構造的LoadedApk存入WeakReference<LoadedApk> ref即可,當加載插件類時,使用插件LoadedApk獲取的ClassLoader加載即可。這個實現過程很複雜,具體方案可查看DroidPlugin實現方案文檔
方式二:宿主和插件合併,系統幫忙,將插件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類加載
二.插件化框架如何實現插件化APK的資源加載
如果直接啓動插件中的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();
}
}
三.插件化框架如何實現對四大組件的支持
1).如何加載四大組件
在沒有處理類加載問題的時候,四大組件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);
2).如何繞過AMS檢測
在啓動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<ClientTransactionItem> 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/
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()