Activity Hook填坑过程中温故而知新

前言

手把手讲解系列文章,是我写给各位看官,也是写给我自己的。
文章可能过分详细,但是这是为了帮助到尽量多的人,毕竟工作5,6年,不能老吸血,也到了回馈开源的时候.
这个系列的文章:
1、用通俗易懂的讲解方式,讲解一门技术的实用价值
2、详细书写源码的追踪,源码截图,绘制类的结构图,尽量详细地解释原理的探索过程
3、提供Github 的 可运行的Demo工程,但是我所提供代码,更多是提供思路,抛砖引玉,请酌情cv
4、集合整理原理探索过程中的一些坑,或者demo的运行过程中的注意事项
5、用gif图,最直观地展示demo运行效果

如果觉得细节太细,直接跳过看结论即可。
本人能力有限,如若发现描述不当之处,欢迎留言批评指正。

学到老活到老,路漫漫其修远兮。与众君共勉 !


引子

HOOK系列是 今年年初大概3月份写的,其中《手把手讲解 Android Hook-实现无清单启动Activity》 这一篇的文章链接为:https://www.jianshu.com/p/eb772e50c690
Demo地址为:https://github.com/18598925736/ActivityHookDemo/tree/startActivityWithoutRegiste

当时是基于最新的SDK 28 android 9 进行的hook,但是近期有一位朋友提出,在SDK 28的设备上,hook之后会导致作为LauncherActivity的生命周期完全失效。并且在SDK 29 android10的设备上,会崩溃。
这位朋友解决了崩溃的问题,在此对他(github名为:fangding)表示感谢!
崩溃的问题我大致看过,也验证过,没有问题,已经合并到 开发分支上,很简单,只是SDK 29改了一些类名,各位可以到 github上去自行查看。现在要解决的是生命周期失效的问题。

声明一个debug源码的坑

hook开发的初期,一般不要用真机。因为真机的系统都是经过了手机厂家深度定制的,如果你想要进行代码debug,使用真机做不到的,因为代码的行数根本对应不上。推荐使用谷歌原生的模拟器

本文采用的是 android 9 sdk 28 谷歌原生androidStudio自带AVD模拟器

适合阅读的人群

如果你对hook 有概念,并且对具体如何去hook有 兴趣深入了解,那么这篇文章可以帮到你很多。

正文

  • bug 表征
  • 源码探索
  • 解决方案
  • 完美效果
  • 可能隐患

bug 表征

Demo : https://github.com/18598925736/ActivityHookDemo/tree/startActivityWithoutRegiste
请切换到 git时间点: 945df9
git checkout 945df9
如果结果为:HEAD is now at 945df96 使用androidX 则切换成功。
这里是,已经出现问题的版本节点。

运行Demo,启动as自带模拟器 sdk28版本。进行跳转,

简单的跳转.gif

然后发现,生命周期函数并不执行。
image.png
跳转过程中,只出现了onCreate onStart onResume, 照理说,跳转之后应该有 onPause onStop. 并且我回到这个Activity时,应该会有 onRestart onStart onResume, 但是也没有。

源码探索

为什么生命周期函数都不执行了?要找到这个原因,我必须先弄清楚一个问题: Activity的生命周期函数到底是由谁来调用的。

前期准备:

这里我不使用Demo工程,而是另外自己新建一个工程,写一个普通的startActivity跳转(这个我就不贴代码了). 因为我们要观察的是正常跳转

开工,进入源码(SDK 28 注意,app module的sdk也要28,必须用 androidStudio自带的AVD SDK 28模拟器才能 debug ):
(为了确保源码探索的完整流程,我们从 startActivity开始 . )

image.png

image.png
image.png

这里产生2个分支,但是仔细观察之后,其实他们最终都走到了同一段逻辑:

        Instrumentation.ActivityResult ar =
            mInstrumentation.execStartActivity(
                this, mMainThread.getApplicationThread(), mToken, child,
                intent, requestCode, options);
        if (ar != null) {
            mMainThread.sendActivityResult(
                mToken, child.mEmbeddedID, requestCode,
                ar.getResultCode(), ar.getResultData());
        }    

来解析这一段逻辑:

两句代码,一个是 mInstrumentation.execStartActivity

image.png

看来这一段并没有涉及到Activity生命周期函数的逻辑。
那么,看下一段:

mMainThread.sendActivityResult ,从 mInstrumentation.execStartActivity 执行之后,得到了一个ar,现在把这个ar 交给mMainThread(ActivityThread类的对象),于是,进入 ActivityThread源码:

image.png

这里的 scheduleTransaction方法,在ActivityThread的父类ClientTransactionHandler中:
image.png

看到sendMessage,就怀疑,这里可能和Handler扯上关系了。

还记不记得 ActivityThread的父类ClientTransactionHandler scheduleTransaction()方法中 sendMessage(ActivityThread.H.EXECUTE_TRANSACTION, transaction); 用到了 ActivityThread.H.EXECUTE_TRANSACTION. 我们在H extends Handler中找到这个switch case的分支:
image.png

一共就两句代码可能和Activity的生命周期函数调用有关,那么我有理由怀疑就是这一段代码在执行 生命周期函数. 那么 如何验证我的猜想是否成立? 答: debug源码(前面之所以要源码版本,AVD模拟器版本,项目版本gradle SDK版本都写成28,就是为了这里debug)
加上断点之后,开始debug,按下跳转按钮, 我们发现了惊人的现象:


一次跳转,我们debug发现了3个可能和生命周期函数有关的细节:
PauseActivityItem ,ResumeActivityItem,StopActivityItem,这3个是不是分别对应了 Activity的3个生命周期函数?
继续探索:
找到 TransactionExecutor类的execute()方法:
image.png

我们需要跟踪的是transaction参数 (因为这里只有一个参数...不跟踪它跟踪谁呢)
然而,这里,使用到这个参数的是两个方法,executeCallbacks()executeLifecycleState(),
而,在这两个方法中,我都找到了类似下面这样的代码:

final ActivityLifecycleItem finalStateRequest = transaction.getLifecycleStateRequest();
item.execute(mTransactionHandler, token, mPendingActions);
item.postExecute(mTransactionHandler, token, mPendingActions);

推断出,生命周期函数一定和ActivityLifecycleItemexecute()postExecute()有关,还记不记得之前我们debug出来的PauseActivityItem ,ResumeActivityItem,StopActivityItem . 这3个类就是ActivityLifecycleItem的子类,进去看看:

image.png

debug 一下:
image.png

发现了ActivityThread,自然就联想到它的 H extends Handler ,ok, 就快接近真相了,表激动,暂且压制一下,我们还没有找到真凭实据。
又是一个抽象方法,直接找他的子类ActivityThread,
image.png
image.png
image.png
image.png

终于接近真相了,这里已经很明显了,参数是Activity对象,并且执行了 activity.performPause().

image.png

真相大白,原来一个Activity的生命周期函数 onPause是这么调用的,绕了一大圈,最终我们重写的onPause()方法才被执行。

小结论

从我们startActivity开始,如果用一张图来展示 Activity生命周期函数如何被执行。无谓的绕绕绕去的省略中间环节,只展示重点环节,那就是这样。

Activity生命周期函数追踪.jpg

解决方案

现在可以运行我的Demo工程,注意 请切换到 git时间点: 945df9, 按照上面的步骤去debug一下,结果发现,

image.png

上一章节中图示的正常现象 PauseActivityItem ,ResumeActivityItem,StopActivityItem ,在这里并未看到,或者说,这里 EXECUTE_TRANSACTION 分支缺失了。肯定是因为我们hook的代码导致ActivityThreadH mH = new H()class H extends Handlervoid handleMessage(Message msg) 部分switch case 没有执行。

既然已经确定是 ActivityThread的 mH 有问题, 那么应该检查的,则是 hook mH的时候。

现在进入hook代码:

image.png
image.png
回顾一下handler的责任链模式:
image.png

按照我通常的hook思路,mH通常执行的是 第三级 成员函数handlerMessage(msg)的逻辑,我hook一下,给mH的成员变量mCallback赋值。 然后可以通过return 返回值来控制要不要继续执行 原handlerMessage(msg)的逻辑。如果mCallback.handlerMessage(msg)返回了true,那么就没有后续了,handlerMessage(msg)永远不会执行。如果是return false,handlerMessage(msg)仍会执行。

已经很接近真凶了。我确定是mCallback.handlerMessage(msg)return true导致的问题。就检查一下我hook的时候,哪里return true了。Debug一下:

image.png
这里list.size()是0,刚好这里return true了。
这里return一下,是因为源码中使用了list.get(0),我不能让它在list为0的情况下去get(0). 对,就是这么单纯,没有别的想法。

完美效果

行动吧,把 这里的return true 改为 return false。然后再 重新运行,观察日志,依然是从1跳转到2。
日志如下:


生命周期函数已经完整。问题解决!

可能隐患

问题解决,可喜可贺。当我兴高采烈地想在新买的华为mate30手机上试验效果时,发现。正常的Activity跳转和hook之后的跳转速度截然不同. 肉眼可见的速度差别,hook之后慢了不止一拍。其他手机尚未发现。也不知道是不是华为底层做了什么事情。总之,这又是下一阶段应该考虑的问题了。

结语

技术研究就是这样,问题是无止境的,优化是无止境的,一项技术的诞生,永远会伴随这无穷无尽的优化,重构,升级,增强,扩展。学习也是如此,所谓活到老学到老,技术人应该保持对技术的热情,执着和追求,坚持学习。像是今天Activity 生命周期函数是如何被调用的,以前只是疑惑,现在终于解开谜团,完完全全抽丝剥茧,得到真相.

所谓坑坑更健康,做技术不可以害怕坑坑洞洞,有坑,解决了坑,自己才能有收获。而且类似这种问题,我解决问题,就是将true改为false,花了1秒。但是我检查问题,找出真相的过程,花了1天。我相信工作中类似这种问题,不在少数。在没有足够了解核心代码逻辑的情况下,去编程,很有可能出一些细节性的小错误,这些错误往往是致命的。 所以,虽然SDK系统源码,一些第三方库源码,很大,很复杂,很多弯弯绕绕,各种回掉,各种映射,各种设计模式,看上去很可怕,但是我们没有退路,退缩不前只会让35岁被离职的风险加大。花点时间去补充基础知识的缺失,鼓起勇气去读源码,只要掌握正确的方法,也许能在源码的世界中找到一片不一样的天空。

2019又一年过去了,与各位技术人,共勉!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容