Hook IActivityManager .startActivity ActivityThread .mCallback
Activity
public void startActivityForResult(Intent intent, int requestCode, @Nullable Bundle options) {
if (mParent == null) {
Instrumentation.ActivityResult ar =
mInstrumentation.execStartActivity(
this, mMainThread.getApplicationThread(), mToken, this,
intent, requestCode, options);
if (ar != null) {
mMainThread.sendActivityResult(
mToken, mEmbeddedID, requestCode, ar.getResultCode(),
ar.getResultData());
}
Instrumentation
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
binder跨进程通信, ActivityManagerProxy代理远程 通信到 ActivityManagerNative ActivityManagerService
int result = ActivityManagerNative.getDefault()
.startActivity(whoThread, who.getBasePackageName(), intent,
class ActivityManagerProxy implements IActivityManager
{
public ActivityManagerProxy(IBinder remote)
{
mRemote = remote;
}
public IBinder asBinder()
{
return mRemote;
}
ActivityManagerProxy
public int startActivity(IApplicationThread caller, String callingPackage, Intent intent,
String resolvedType, IBinder resultTo, String resultWho, int requestCode,
int startFlags, ProfilerInfo profilerInfo, Bundle options) throws RemoteException {
mRemote.transact(START_ACTIVITY_TRANSACTION, data, reply, 0);
ActivityManagerNative
public abstract class ActivityManagerNative extends Binder implements IActivityManager
{
@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
throws RemoteException {
switch (code) {
case START_ACTIVITY_TRANSACTION:
{
int result = startActivity(app, callingPackage, intent, resolvedType,
resultTo, resultWho, requestCode, startFlags, profilerInfo, options);
ActivityManagerService
public final class ActivityManagerService extends ActivityManagerNative
@Override
public final int startActivity(IApplicationThread caller, String callingPackage,
Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode,
int startFlags, ProfilerInfo profilerInfo, Bundle options) {
return startActivityAsUser(caller, callingPackage, intent, resolvedType, resultTo,
resultWho, requestCode, startFlags, profilerInfo, options,
UserHandle.getCallingUserId());
}
ActivityStackSupervisor mStackSupervisor;
return mStackSupervisor.startActivityMayWait(caller, -1, callingPackage, intent,
int res = startActivityLocked(caller, intent, resolvedType, aInfo,
err = startActivityUncheckedLocked(r, sourceRecord, voiceSession, voiceInteractor,
startFlags, true, options, inTask);
resumeTopActivitiesLocked(targetStack, null, options);
boolean pausing = mStackSupervisor.pauseBackStacks(userLeaving, true, dontWaitForPause);
someActivityPaused |= stack.startPausingLocked(userLeaving, false, resuming,
final ActivityStack stack = stacks.get(stackNdx);
prev.app.thread.schedulePauseActivity(prev.appToken, prev.finishing,
IApplicationThread thread; // the actual proc... may be null only if
class ApplicationThreadProxy implements IApplicationThread {
public final void schedulePauseActivity(IBinder token, boolean finished,
boolean userLeaving, int configChanges, boolean dontReport) throws RemoteException {
Parcel data = Parcel.obtain();
data.writeInterfaceToken(IApplicationThread.descriptor);
data.writeStrongBinder(token);
data.writeInt(finished ? 1 : 0);
data.writeInt(userLeaving ? 1 :0);
data.writeInt(configChanges);
data.writeInt(dontReport ? 1 : 0);
mRemote.transact(SCHEDULE_PAUSE_ACTIVITY_TRANSACTION, data, null,
IBinder.FLAG_ONEWAY);
data.recycle();
}
public ApplicationThreadNative() {
attachInterface(this, descriptor);
}
@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
throws RemoteException {
switch (code) {
case SCHEDULE_PAUSE_ACTIVITY_TRANSACTION:
{
schedulePauseActivity(b, finished, userLeaving, configChanges, dontReport);
ActivityThread
private class ApplicationThread extends ApplicationThreadNative {
public final void schedulePauseActivity(IBinder token, boolean finished,
boolean userLeaving, int configChanges, boolean dontReport) {
sendMessage(
finished ? H.PAUSE_ACTIVITY_FINISHING : H.PAUSE_ACTIVITY,
token,
(userLeaving ? 1 : 0) | (dontReport ? 2 : 0),
configChanges);
}
ActivityThread
sendMessage(
finished ? H.PAUSE_ACTIVITY_FINISHING : H.PAUSE_ACTIVITY,
case PAUSE_ACTIVITY:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityPause");
handlePauseActivity((IBinder)msg.obj, false, (msg.arg1&1) != 0, msg.arg2,
(msg.arg1&2) != 0);
ActivityManagerProxy Binder代理 Remote进程 和本地进程通信 pauseActivity
ActivityManagerNative.getDefault().activityPaused(token);
ActivityManagerService
@Override
public final void activityPaused(IBinder token) {
ActivityStack
private void completePauseLocked(boolean resumeNext) {
ActivityStackSupervisor
final boolean resumeTopActivityLocked(ActivityRecord prev, Bundle options) {
final ActivityManagerService mService;
mService.startProcessLocked(r.processName, r.info.applicationInfo, true, 0,
"activity", r.intent.ge
ActivityManagerService本地进程
private final void startProcessLocked(ProcessRecord app, String hostingType,
String hostingNameStr, String abiOverride, String entryPoint, String[] entryPointArgs) {
启动Zygote进程fork一个app进程 ActivityThread
Process.ProcessStartResult startResult = Process.start(entryPoint,
app.processName, uid, uid, gids, debugFlags, mountExternal,
app.info.targetSdkVersion, app.info.seinfo, requiredAbi, instructionSet,
app.info.dataDir, entryPointArgs);
ZygoteInit
private static void runSelectLoop(String abiList) throws MethodAndArgsCaller {
ArrayList<FileDescriptor> fds = new ArrayList<FileDescriptor>();
done = peers.get(index).runOnce();
//fork一个新进程
pid = Zygote.forkAndSpecialize(parsedArgs.uid, parsedArgs.gid, parsedArgs.gids,
handleChildProc(parsedArgs, descriptors, childPipeFd, newStderr);
cloader = new PathClassLoader(parsedArgs.classpath,
ClassLoader.getSystemClassLoader());
ZygoteInit.invokeStaticMain(cloader, className, mainArgs);
cl = loader.loadClass(className);
//调用ActivityThread的main方法
m = cl.getMethod("main", new Class[] { String[].class });
ActivityThread
public static void main(String[] args) {
//binderProxy代理 Remote进程(App进程)和本地进程通信 attachApplication
final IActivityManager mgr = ActivityManagerNative.getDefault();
try {
mgr.attachApplication(mAppThread);
} catch (RemoteException ex) {
// Ignore
}
ActivityManagerService本地进程
@Override
public final void attachApplication(IApplicationThread thread) {
if (mStackSupervisor.attachApplicationLocked(app)) {
ActivityStackSupervisor
boolean attachApplicationLocked(ProcessRecord app) throws RemoteException {
binder代理 本地进程和Remote进程(App进程)通信 lunch一个activity
final boolean realStartActivityLocked(ActivityRecord r,
ProcessRecord app, boolean andResume, boolean checkConfig)
class ApplicationThreadProxy implements IApplicationThread {
scheduleLaunchActivity为ApplicationThreadProxy远程代理的方法
app.thread.scheduleLaunchActivity(new Intent(r.intent), r.appToken,
ActivityThread
private class ApplicationThread extends ApplicationThreadNative {
public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
public void handleMessage(Message msg) {
if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
switch (msg.what) {
case LAUNCH_ACTIVITY: {
r.packageInfo = getPackageInfoNoCheck(
r.activityInfo.applicationInfo, r.compatInfo);
handleLaunchActivity(r, null);
@Override
public Activity handleLaunchActivity(ActivityClientRecord r,
PendingTransactionActions pendingActions, Intent customIntent) {
Activity a = performLaunchActivity(r, customIntent);
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
java.lang.ClassLoader cl = appContext.getClassLoader();
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
StrictMode.incrementExpectedActivityCount(activity.getClass());
r.intent.setExtrasClassLoader(cl);
r.intent.prepareToEnterProcess();
java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
activity = mInstrumentation.newActivity(
LoadedApk packageInfo;
ActivityThread
@Override
public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
ActivityInfo info, Configuration curConfig, Configuration overrideConfig,
CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor,
sendMessage(H.LAUNCH_ACTIVITY, r);
handleLaunchActivity(r, null);
Activity a = performLaunchActivity(r, customIntent);
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
return (Activity)cl.loadClass(className).newInstance();
ActivityManagerService
private final void startProcessLocked(ProcessRecord app, String hostingType,
String hostingNameStr, String abiOverride, String entryPoint, String[] entryPointArgs) {
Process.ProcessStartResult startResult = Process.start(entryPoint,
app.processName, uid, uid, gids, debugFlags, mountExternal,
app.info.targetSdkVersion, app.info.seinfo, requiredAbi, instructionSet,
app.info.dataDir, entryPointArgs);
Process
public static final ProcessStartResult start(final String processClass,
final String niceName,
int uid, int gid, int[] gids,
int debugFlags, int mountExternal,
int targetSdkVersion,
String seInfo,
String abi,
String instructionSet,
String appDataDir,
String[] zygoteArgs) {
try {
return startViaZygote(processClass, niceName, uid, gid, gids,
debugFlags, mountExternal, targetSdkVersion, seInfo,
abi, instructionSet, appDataDir, zygoteArgs);
} catch (ZygoteStartFailedEx ex) {
//通过 ZygoteInit创建fork新的进程 Process Socket
private static ProcessStartResult zygoteSendArgsAndGetResult(
热修复
ClassLoader
DexClassLoader PathClassLoader
loadClass findLoadedClass findClass
DexClassLoader BaseDexClassLoader
DexPathList pathList
Element dexElements dex
javac .java .class d8 patch.dex
dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.github.xch168.hotfixdemo-2/base.apk"],
nativeLibraryDirectories=[/data/app/com.github.xch168.hotfixdemo-2/lib/arm64, /system/lib64, /vendor/lib64, /system/vendor/lib64, /product/lib64]]]
class dalvik.system.BaseDexClassLoader
DexPathList[[zip file "/data/app/com.github.xch168.hotfixdemo-2/base.apk"],
nativeLibraryDirectories=[/data/app/com.github.xch168.hotfixdemo-2/lib/arm64, /system/lib64, /vendor/lib64, /system/vendor/lib64, /product/lib64]]
zip file "/data/app/com.github.xch168.hotfixdemo-2/base.apk"
pathList dexElements
DexClassLoader
.loadClass .newInstance
.invoke
loadClass
lo
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
Class clazz = pathList.findClass(name);
if (clazz == null) {
throw new ClassNotFoundException(name);
}
return clazz;
}
DexPathList pathList
parent.loadClass
findBootstrapClassOrNull
findClass
DexPathList.findClass
public Class findClass(String name) {
for (Element element : dexElements) {
DexFile dex = element.dexFile;
if (dex != null) {
Class clazz = dex.loadClassBinaryName(name, definingContext);
if (clazz != null) {
return clazz;
}
}
}
return null;
}
dexElements
PathClassLoader dexElements
dex =dexElements[0] setField
1
接来下开始load我们刚刚写入在dex文件中的ClassStudent类:
Class<?> aClass = dexClassLoader.loadClass("ClassStudent");
1
然后我们对其进行初始化,并调用相关的get/set方法对其进行验证,在这里我传给ClassStudent对象一个字符串,然后调用它的get方法获取在方法内合并后的字符串:
Object instance = aClass.newInstance();
Method method = aClass.getMethod("setName", String.class);
method.invoke(instance, "Sahadev");
Method getNameMethod = aClass.getMethod("getName");
Object invoke = getNameMethod.invoke(instance););
1
2
3
4
5
6
最后我们实现的代码可能是这样的:
/**
* 加载指定路径的dex
*
* @param apkPath
*/
private void loadClass(String apkPath) {
ClassLoader classLoader = getClassLoader();
File file = new File(apkPath);
try {
DexClassLoader dexClassLoader = new DexClassLoader(apkPath, file.getParent() + "/optimizedDirectory/", "", classLoader);
Class<?> aClass = dexClassLoader.loadClass("ClassStudent");
mLog.i(TAG, "ClassStudent = " + aClass);
Object instance = aClass.newInstance();
Method method = aClass.getMethod("setName", String.class);
method.invoke(instance, "Sahadev");
伪代码相当于 void setName(string) {}
Method getNameMethod = aClass.getMethod("getName");
Object invoke = getNameMethod.invoke(instance);
伪代码相当于 void getName() {}
mLog.i(TAG, "invoke result = " + invoke);
} catch (Exception e) {
e.printStackTrace();
}
}
插件化原理分析
插件化要解决的三个核心问题:类加载、资源加载、组件生命周期管理。
类加载:Android中常用的两种类加载器:PathClassLoader和DexClassLoader,它们都继承于BaseDexClassLoader。
DexClassLoader的构造函数比PathClassLoader多了一个,optimizedDirectory参数,这个是用来指定dex的优化产物odex的路径,在源码注释中,指出这个参数从API 26后就弃用了。
PathClassLoader主要用来加载系统类和应用程序的类,在ART虚拟机上可以加载未安装的apk的dex,在Dalvik则不行。
DexClassLoader用来加载未安装apk的dex。
资源加载:Android系统通过Resource对象加载资源,因此只需要添加资源(即apk文件)所在路径到AssetManager中,即可实现对插件资源的访问。由于AssetManager的构造方法时hide的,需要通过反射区创建。
组件生命周期管理:对于Android来说,并不是说类加载进来就可以使用了,很多组件都是有“生命”的;因此对于这些有血有肉的类,必须给他们注入活力,也就是所谓的组件生命周期管理。
在解决插件中组件的生命周期,通常的做法是通过Hook相应的系统对象,实现欺上瞒下,后面将通过Activity的插件化来进行讲解。
// 第一步:反射得到 ListenerInfo 对象
Method getListenerInfo = View.class.getDeclaredMethod("getListenerInfo");
//android.view.View$ListenerInfo android.view.View.getListenerInfo()
getListenerInfo.setAccessible(true);
Object listenerInfo = getListenerInfo.invoke(view);//View$ListenerInfo
// 第二步:得到原始的 OnClickListener事件方法
Class<?> listenerInfoClz = Class.forName("android.view.View$ListenerInfo");//class android.view.View$ListenerInfo
Field mOnClickListener = listenerInfoClz.getDeclaredField("mOnClickListener");//public android.view.View$OnClickListener android.view.View$ListenerInfo.mOnClickListener
mOnClickListener.setAccessible(true);
View.OnClickListener originOnClickListener = (View.OnClickListener) mOnClickListener.get(listenerInfo);//MainActivity@4846
// 第三步:用 Hook代理类 替换原始的 OnClickListener
View.OnClickListener hookedOnClickListener = new HookedClickListenerProxy(originOnClickListener);
mOnClickListener.set(listenerInfo, hookedOnClickListener);
@UnsupportedAppUsage
static public INotificationManager getService()
{
if (sService != null) {
return sService;
}
IBinder b = ServiceManager.getService("notification");
sService = INotificationManager.Stub.asInterface(b);
return sService;
}
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
//Object
nvoke newProxyInstance
InvocationHandler
reflectClas.getMethod
typeArguments
Java Field.set 向对象的这个Field属性设置新值value
fields.get
try {
ReflectUtil.hookNotificationManager(this);
} catch (Exception e) {
e.printStackTrace();
}
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
createPrintingNotification(notificationManager);
}
public void killApp(View view) {
android.os.Process.killProcess(android.os.Process.myPid());
}
private void createPrintingNotification(NotificationManager notificationManager) {
Notification.Builder builder = new Notification.Builder(this)
.setSmallIcon(R.drawable.ic_launcher_background);
notificationManager.notify(0, builder.build());
}
private static String TAG = "robin";
public static void hookNotificationManager(final Context context) throws Exception {
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
Method getService = NotificationManager.class.getDeclaredMethod("getService");
getService.setAccessible(true);
// 第一步:得到系统的 sService
final Object sOriginService = getService.invoke(notificationManager);
Class iNotiMngClz = Class.forName("android.app.INotificationManager");
// 第二步:得到我们的动态代理对象
Object proxyNotiMng = Proxy.newProxyInstance(context.getClass().getClassLoader(), new
Class[]{iNotiMngClz}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Log.d(TAG, "invoke(). method:" + method);
//android.app.INotificationManager$Stub$Proxy@84b2c77
//public abstract void android.app.INotificationManager.enqueueNotificationWithTag(java.lang.String,java.lang.String,java.lang.String,int,android.app.Notification,int[],int) throws android.os.RemoteException
//[0]com.github.xch168.hotfixdemo
//[1]com.github.xch168.hotfixdemo
//[2] 1
//[3]Notification(pri=0 contentView=null vibrate=null sound=null defaults=0x0 flags=0x0 color=0x00000000 vis=PRIVATE)
String name = method.getName();
Log.d(TAG, "invoke: name=" + name);
if (args != null && args.length > 0) {
for (Object arg : args) {
Log.d(TAG, "invoke: arg=" + arg);
}
}
Toast.makeText(context.getApplicationContext(), "检测到有人发通知了", Toast.LENGTH_SHORT).show();
// 操作交由 sOriginService 处理,不拦截通知
return method.invoke(sOriginService, args);
// 拦截通知,什么也不做
// return null;
// 或者是根据通知的 Tag 和 ID 进行筛选
}
});
// 第三步:偷梁换柱,使用 proxyNotiMng 替换系统的 sService
Field sServiceField = NotificationManager.class.getDeclaredField("sService");
sServiceField.setAccessible(true);
sServiceField.set(notificationManager, proxyNotiMng);
}
invoke newProxyInstance InvocationHandler
forName Proxy
//获取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
classes resources.arsc res assets\ src\jar
libs\ gen\ .java\\\ bin\ META-INF\
AAPT(Android AIDL工具会将所有的 JAVAC工具将R dex
apkbuilder脚本将资源文件和 jarsigner对apk进行签名。
R.java\\\
Application Resources aapt R.java resource.arsc
打包资源文件:使用aapt(Android Asset Package Tool)把res目录下的资源生成相应的R.java文件和resource.arsc文件,同时为AndroidManifest.xml生成二进制的AndroidManifest.java文件。
处理aidl文件:使有aidl(Android Interface Denifition Language)把项目自定义的aidl文件生成相应的Java代码文件。
编译Java代码文件:使用javac(Java 编译器)把项目中所有的Java代码编译成class文件。包括Java源文件、aapt生成的R.java文件 以及 aidl生成的Java接口文件。
代码混淆处理:若配置了启用Proguard混淆,就会对代码进行混淆处理并生成proguardMapping.txt文件。
把class文件转成dex文件:使用dx.bat将所有的class文件(包括第三方库中的class文件)转换成dex文件。
生成apk文件:使用apkbuilder(主要用到的是sdk/tools/lib/sdklib.jar文件中的ApkBuilderMain类)将所有的dex文件、resource.arsc、res文件夹、assets文件夹、AndroidManifest.xml 打包为.apk文件。
apk文件签名:使用apksigner(Android官方针对apk签名及验证工具)或jarsigner(JDK提供针对jar包签名工具)对未签名的apk文件进行签名。
对齐处理:使用zipalign对签名后的apk文件进行对齐处理,以便在运行时可节省内存。
Proguard
dx.bat将所有的class文件(包括第三方库中的class文件)转换成dex文件。
apkbuilder脚本将资源文件和
dex resource.arsc res assets\ AndroidManifest
.apk
apksigner zipalign
ContextImpl
PluginManager getResources getAssets
scheduleTransaction
ClientTransaction本地
ClientTransactionHandler远程代理
从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个版本的兼容处理)
Instrumentation.java
execStartActivity() -> ActivityManager.getService().startActivity()
ClientTransactionHandler.java
scheduleTransaction() -> sendMessage(ActivityThread.H.EXECUTE_TRANSACTION, transaction) ->
ActivityThread.java
ActivityThread.H.EXECUTE_TRANSACTION -> mTransactionExecutor.execute(transaction)
IActivityManagerSingleton
package com.cj.jplugin.hook;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.os.Message;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class HookStartActivityApi25 extends BaseHookStartActivity {
public static final int LAUNCH_ACTIVITY = 100;
public HookStartActivityApi25(Context context, Class<?> proxyClass) {
super(context, proxyClass);
}
@Override
public void hookStartActivity() {
try {
Class<?> iamClass = Class.forName("android.app.IActivityManager");
//interface android.app.IActivityManager
Class<?> amnClass = Class.forName("android.app.ActivityManagerNative");//class android.app.ActivityManagerNative
Field gDefaultField = amnClass.getDeclaredField("gDefault");//private static final android.util.Singleton android.app.ActivityManagerNative.gDefault
gDefaultField.setAccessible(true);
Object singleton = gDefaultField.get(null);//ActivityManagerNative
Class<?> singletonClass = Class.forName("android.util.Singleton");//class android.util.Singleton
Field mInstanceField = singletonClass.getDeclaredField("mInstance");
mInstanceField.setAccessible(true);//private java.lang.Object android.util.Singleton.mInstance
Object iam = mInstanceField.get(singleton);//ActivityManagerProxy类型
Object iamProxy = Proxy.newProxyInstance(iam.getClass().getClassLoader(),
iam.getClass().getInterfaces(),
new StartActivityInvocationHandler(context, iam, proxyClass)
);//android.app.ActivityManagerProxy@e5e80f
//将Singleton中的mInstance替换成代理的iamProxy
mInstanceField.set(singleton, iamProxy);
} catch (Exception e) {
e.printStackTrace();
}
}
class StartActivityInvocationHandler implements InvocationHandler {
private Object obj;
private Context context;
private Class<?> proxyClass;
public StartActivityInvocationHandler(Context context, Object obj, Class<?> proxyClass) {
this.context = context;
this.obj = obj;
this.proxyClass = proxyClass;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//替换Intent,过AndroidManifest.xml检测
if ("startActivity".equals(method.getName())) {
//1.首先获取原来的Intent
Intent originIntent = (Intent) args[2];//第二个参数就是intent
if(!isExistIntentActivity(originIntent)) {
//创建一个安全的Intent
Intent proxyIntent = new Intent(context, proxyClass);
//保存原来的Intent
proxyIntent.putExtra(EXTRA_ORIGIN_INTENT, originIntent);
//设置新的Intent过检测
args[2] = proxyIntent;
}
}
return method.invoke(obj,args);
}
}
@Override
public void hookLaunchActivity() {
try {
// 获取ActivityThread
Class<?> atClass = Class.forName("android.app.ActivityThread");///class android.app.ActivityThread
Field sCurrentActivityThreadField = atClass.getDeclaredField("sCurrentActivityThread");//private static volatile android.app.ActivityThread android.app.ActivityThread.sCurrentActivityThread
sCurrentActivityThreadField.setAccessible(true);
Object at = sCurrentActivityThreadField.get(null);//ActivityThread
//获取Handler mH
Field mHField = atClass.getDeclaredField("mH");//final android.app.ActivityThread$H android.app.ActivityThread.mH
mHField.setAccessible(true);
Object mH = mHField.get(at);//Handler (android.app.ActivityThread$H) {f0bfc36}
//给mH设置Callback
Class<?> handlerClass = Class.forName("android.os.Handler");
Field mCallbackField = handlerClass.getDeclaredField("mCallback");//final android.os.Handler$Callback android.os.Handler.mCallback
mCallbackField.setAccessible(true);
mCallbackField.set(mH,new HookCallback());//
} catch (Exception e) {
e.printStackTrace();
}
}
protected boolean handleCallbackMessage(Message msg){
if (msg.what == LAUNCH_ACTIVITY){
return handleLaunchActivity(msg);
}
return false;
}
private boolean handleLaunchActivity(Message msg) {
try {
//获取ActivityClientRecord
Object record = msg.obj;
Field intentField = record.getClass().getDeclaredField("intent");
intentField.setAccessible(true);
//代理的Intent,proxyIntent
Intent intent = (Intent) intentField.get(record);
if(intent.hasExtra(EXTRA_ORIGIN_INTENT)) {
//获取原始的Intent
Intent originIntent = intent.getParcelableExtra(EXTRA_ORIGIN_INTENT);
//防止为null
if (originIntent != null) {
//将原始的Intent重新设置回去,这样才能跳转到原始的Activity
intentField.set(record, originIntent);
}
//解决AppCompatActivity重新检测清单文件注册问题
handleAppCompatActivityCheck(intent);
}
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
/**
* 解决AppCompatActivity重新检测清单文件注册问题
* 如果使用的Activity集成的是AppCompatActivity,它会重新检测该Activity是否注册,所以这里也需要hook
* @param intent
*/
protected void handleAppCompatActivityCheck(Intent intent){
try {
//解决AppCompatActivity重新检测清单文件注册问题
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, intent.getComponent()));
Field sPackageManagerField = atClass.getDeclaredField("sPackageManager");
sPackageManagerField.setAccessible(true);
sPackageManagerField.set(null, ipmProxy);
} catch (Exception e) {
e.printStackTrace();
}
}
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);
/*if ("getActivityInfo".equals(method.getName())) {
ai.parentActivityName = originClass.getClassName();
}*/
return invoke;
}
}
class HookCallback implements Handler.Callback{
@Override
public boolean handleMessage(Message msg) {//onresume,各种生命周期都可以收到
return handleCallbackMessage(msg);
}
}
}
public void click(View view) {
try {
Class clazz = getClassLoader().loadClass("com.cj.oneplugin.SecondActivity");
Intent intent = new Intent(this,clazz);
startActivity(intent);
} catch (Exception e) {
e.printStackTrace();
}
}