Hook点IActivityManager是一个接口,建议采用动态代理。在注释1处拦截startActivity方法,接着获取参数args中第一个Intent对象,它是原本要启动插件TargetActivity的Intent。注释2、3处新建一个subIntent用来启动的StubActivity,注释4处将这个TargetActivity的Intent保存到subIntent中,便于以后还原TargetActivity。注释5处用subIntent赋值给参数args,这样启动的目标就变为了StubActivity,用来通过AMS的校验。 最后用代理类IActivityManagerProxy来替换IActivityManager,如下所示。
HookHelper.java
public class HookHelper {
public static final String TARGET_INTENT = “target_intent”;
public static void hookAMS() throws Exception {
Object defaultSingleton=null;
if (Build.VERSION.SDK_INT >= 26) {//1
Class<?> activityManageClazz = Class.forName(“android.app.ActivityManager”);
//获取activityManager中的IActivityManagerSingleton字段
defaultSingleton= FieldUtil.getField(activityManageClazz ,null,“IActivityManagerSingleton”);
} else {
Class<?> activityManagerNativeClazz = Class.forName(“android.app.ActivityManagerNative”);
//获取ActivityManagerNative中的gDefault字段
defaultSingleton= FieldUtil.getField(activityManagerNativeClazz,null,“gDefault”);
}
Class<?> singletonClazz = Class.forName(“android.util.Singleton”);
Field mInstanceField= FieldUtil.getField(singletonClazz ,“mInstance”);//2
//获取iActivityManager
Object iActivityManager = mInstanceField.get(defaultSingleton);//3
Class<?> iActivityManagerClazz = Class.forName(“android.app.IActivityManager”);
Object proxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
new Class<?>[] { iActivityManagerClazz }, new IActivityManagerProxy(iActivityManager));
mInstanceField.set(defaultSingleton, proxy);
}
}
首先在注释1处对系统版本进行区分,最终获取的是 Singleton<IActivityManager>
类型的IActivityManagerSingleton或者gDefault字段。注释2处获取Singleton类中的mInstance字段,从前面Singleton类的代码可以得知mInstance字段的类型为T,在注释3处得到IActivityManagerSingleton或者gDefault字段中的T的类型,T的类型为IActivityManager。最后动态创建代理类IActivityManagerProxy,用IActivityManagerProxy来替换IActivityManager。 自定义一个Application,在其中调用HookHelper 的hookAMS方法,如下所示。
MyApplication.java
public class MyApplication extends Application {
@Override
public void attachBaseContext(Context base) {
super.attachBaseContext(base);
try {
HookHelper.hookAMS();
} catch (Exception e) {
e.printStackTrace();
}
}
}
在MainActivity中启动TargetActivity,如下所示。
MainActivity.java
public class MainActivity extends Activity {
private Button bt_hook;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bt_hook = (Button) this.findViewById(R.id.bt_hook);
bt_hook.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(MainActivity.this, TargetActivity.class);
startActivity(intent);
}
});
}
}
点击Button时,启动的并不是TargetActivity而是SubActivity,同时Log中打印了"hook成功",说明我们已经成功用SubActivity通过了AMS的校验。
2.3 还原插件Activity
前面用占坑Activity通过了AMS的校验,但是我们要启动的是插件TargetActivity,还需要用插件TargetActivity来替换占坑的SubActivity,这一替换的时机就在图2的步骤2之后。 ActivityThread启动Activity的过程,如图3所示。
图3
ActivityThread会通过H将代码的逻辑切换到主线程中,H类是ActivityThread的内部类并继承自Handler,如下所示。 frameworks/base/core/java/android/app/ActivityThread.java
private class H extends Handler {
public static final int LAUNCH_ACTIVITY = 100;
public static final int PAUSE_ACTIVITY = 101;
…
public void handleMessage(Message msg) {
if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
switch (msg.what) {
case LAUNCH_ACTIVITY: {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, “activityStart”);
final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
r.packageInfo = getPackageInfoNoCheck(
r.activityInfo.applicationInfo, r.compatInfo);
handleLaunchActivity(r, null, “LAUNCH_ACTIVITY”);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
} break;
…
}
…
}
H中重写的handleMessage方法会对LAUNCH_ACTIVITY类型的消息进行处理,最终会调用Activity的onCreate方法。那么在哪进行替换呢?接着来看Handler的dispatchMessage方法: frameworks/base/core/java/android/os/Handler.java
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
Handler的dispatchMessage用于处理消息,看到如果Handler的Callback类型的mCallback不为null,就会执行mCallback的handleMessage方法。因此,mCallback可以作为Hook点,我们可以用自定义的Callback来替换mCallback,自定义的Callback如下所示。 HCallback.java
public class HCallback implements Handler.Callback{
public static final int LAUNCH_ACTIVITY = 100;
Handler mHandler;
public HCallback(Handler handler) {
mHandler = handler;
}
@Override
public boolean handleMessage(Message msg) {
if (msg.what == LAUNCH_ACTIVITY) {
Object r = msg.obj;
try {
//得到消息中的Intent(启动SubActivity的Intent)
Intent intent = (Intent) FieldUtil.getField(r.getClass(), r, “intent”);
//得到此前保存起来的Intent(启动TargetActivity的Intent)
Intent target = intent.getParcelableExtra(HookHelper.TARGET_INTENT);
//将启动SubActivity的Intent替换为启动TargetActivity的Intent
intent.setComponent(target.getComponent());
} catch (Exception e) {
e.printStackTrace();
}
}
mHandler.handleMessage(msg);
return true;
}
}
HCallback实现了Handler.Callback,并重写了handleMessage方法,当收到消息的类型为LAUNCH_ACTIVITY时,将启动SubActivity的Intent替换为启动TargetActivity的Intent。接着我们在HookHelper中定义一个hookHandler方法如下所示。
HookHelper.java
public static void hookHandler() throws Exception {
Class<?> activityThreadClass = Class.forName(“android.app.ActivityThread”);
Object currentActivityThread= FieldUtil.getField(activityThreadClass ,null,“sCurrentActivityThread”);//1
Field mHField = FieldUtil.getField(activityThread,“mH”);//2
Handler mH = (Handler) mHField.get(currentActivityThread);//3
FieldUtil.setField(Handler.class,mH,“mCallback”,new HCallback(mH));
}
ActivityThread类中有一个静态变量sCurrentActivityThread,用于表示当前的ActivityThread对象,因此在注释1处获取ActivityThread中定义的sCurrentActivityThread对象。注释2处获取ActivityThread类的mH字段,接着在注释3处获取当前ActivityThread对象中的mH对象,最后用HCallback来替换mH中的mCallback。 在MyApplication的attachBaseContext方法中调用HookHelper的hookHandler方法,运行程序,当我们点击启动插件按钮,发现启动的是插件TargetActivity。
2.4 插件Activity的生命周期
插件TargetActivity确实启动了,但是它有生命周期吗?这里从源码角度来进行分析,Activity的finish方法可以触发Activity的生命周期变化,和Activity的启动过程类似,finish方法如下所示。 frameworks/base/core/java/android/app/Activity.java
public void finish() {
finish(DONT_FINISH_TASK_WITH_ACTIVITY);
}
private void finish(int finishTask) {
if (mParent == null) {
int resultCode;
Intent resultData;
synchronized (this) {
resultCode = mResultCode;
resultData = mResultData;
}
if (false) Log.v(TAG, “Finishing self: token=” + mToken);
try {
if (resultData != null) {
resultData.prepareToLeaveProcess(this);
}
if (ActivityManager.getService()
.finishActivity(mToken, resultCode, resultData, finishTask)) {//1
mFinished = true;
}
} catch (RemoteException e) {
// Empty
}
} else {
mParent.finishFromChild(this);
}
}
finish方法的调用链和Activity的启动过程是类似的,注释1处会调用的AMS的finishActivity方法,接着是AMS通过ApplicationThread调用ActivityThread,ActivityThread向H发送DESTROY_ACTIVITY类型的消息,H接收到这个消息会执行handleDestroyActivity方法,handleDestroyActivity方法又调用了performDestroyActivity方法,如下所示。
frameworks/base/core/java/android/app/ActivityThread.java
private ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing,
int configChanges, boolean getNonConfigInstance) {
ActivityClientRecord r = mActivities.get(token);//1
…
try {
r.activity.mCalled = false;
mInstrumentation.callActivityOnDestroy(r.activity);//2
…
} catch (SuperNotCalledException e) {
…
}
}
mActivities.remove(token);
StrictMode.decrementExpectedActivityCount(activityClass);
return r;
注释1处通过IBinder类型的token来获取ActivityClientRecord,ActivityClientRecord用于描述应用进程中的Activity。在注释2处调用Instrumentation的callActivityOnDestroy方法来调用Activity的OnDestroy方法,并传入了r.activity。前面的例子我们用SubActivity替换了TargetActivity通过了AMS的校验,这样AMS只知道SubActivity的存在,那么AMS是如何能控制TargetActivity生命周期的回调的?我们接着往下看,启动Activity时会调用ActivityThread的performLaunchActivity方法,如下所示。
frameworks/base/core/java/android/app/ActivityThread.java
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
…
java.lang.ClassLoader cl = appContext.getClassLoader();
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);//1
…
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.referrer, r.voiceInteractor, window, r.configCallback);
…
mActivities.put(r.token, r);//2
…
return activity;
}
注释1处根据Activity的类名用ClassLoader加载Acitivty,接着调用Activity的attach方法,将r.token赋值给Activity的成员变量mToken。在注释2处将ActivityClientRecord根据r.token存在mActivities中(mActivities类型为ArrayMap<IBinder, ActivityClientRecord>
),再结合Activity的finish方法的注释1处,可以得出结论:AMS和ActivityThread之间的通信采用了token来对Activity进行标识,并且此后的Activity的生命周期处理也是根据token来对Activity进行标识的。 回到我们这个例子来,我们在Activity启动时用插件TargetActivity替换占坑SubActivity,这一过程在performLaunchActivity方法调用之前,因此注释2处的r.token指向的是TargetActivity,在performDestroyActivity的注释1处获取的就是代表TargetActivity的ActivityClientRecord,可见TargetActivity是具有生命周期的。
3.Hook Instrumentation方案实现
Hook Instrumentation实现要比Hook IActivityManager实现要简洁一些,示例代码会和Hook IActivityManager实现有重复,重复的部分这里不再赘述。 Hook Instrumentation实现同样也需要用到占坑Activity,与Hook IActivityManager实现不同的是,用占坑Activity替换插件Activity以及还原插件Activity的地方不同。Acitivty的startActivity方法调用时序图如图4所示。
图4
从图4可以发现,在Activity通过AMS校验前,会调用Activity的startActivityForResult方法: frameworks/base/core/java/android/app/Activity.java
public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,
@Nullable Bundle options) {
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级安卓工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
最后
简历首选内推方式,速度快,效率高啊!然后可以在拉钩,boss,脉脉,大街上看看。简历上写道熟悉什么技术就一定要去熟悉它,不然被问到不会很尴尬!做过什么项目,即使项目体量不大,但也一定要熟悉实现原理!不是你负责的部分,也可以看看同事是怎么实现的,换你来做你会怎么做?做过什么,会什么是广度问题,取决于项目内容。但做过什么,达到怎样一个境界,这是深度问题,和个人学习能力和解决问题的态度有关了。大公司看深度,小公司看广度。大公司面试你会的,小公司面试他们用到的你会不会,也就是岗位匹配度。
面试过程一定要有礼貌!即使你觉得面试官不尊重你,经常打断你的讲解,或者你觉得他不如你,问的问题缺乏专业水平,你也一定要尊重他,谁叫现在是他选择你,等你拿到offer后就是你选择他了。
另外,描述问题一定要慢!不要一下子讲一大堆,慢显得你沉稳、自信,而且你还有时间反应思路接下来怎么讲更好。现在开发过多依赖ide,所以会有个弊端,当我们在面试讲解很容易不知道某个方法怎么读,这是一个硬伤…所以一定要对常见的关键性的类名、方法名、关键字读准,有些面试官不耐烦会说“你到底说的是哪个?”这时我们会容易乱了阵脚。正确的发音+沉稳的描述+好听的嗓音决对是一个加分项!
最重要的是心态!心态!心态!重要事情说三遍!面试时间很短,在短时间内对方要摸清你的底子还是比较不现实的,所以,有时也是看眼缘,这还是个看脸的时代。
希望大家都能找到合适自己满意的工作!
如果需要PDF版本可以在GitHub中自行领取!
进阶学习视频
附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题 (含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)
依赖ide,所以会有个弊端,当我们在面试讲解很容易不知道某个方法怎么读,这是一个硬伤…所以一定要对常见的关键性的类名、方法名、关键字读准,有些面试官不耐烦会说“你到底说的是哪个?”这时我们会容易乱了阵脚。正确的发音+沉稳的描述+好听的嗓音决对是一个加分项!
最重要的是心态!心态!心态!重要事情说三遍!面试时间很短,在短时间内对方要摸清你的底子还是比较不现实的,所以,有时也是看眼缘,这还是个看脸的时代。
希望大家都能找到合适自己满意的工作!
如果需要PDF版本可以在GitHub中自行领取!
进阶学习视频
[外链图片转存中…(img-o5Wr5owr-1710505965267)]
附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题 (含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)
[外链图片转存中…(img-IuZNQF9N-1710505965267)]