Android开发;Activity-Hook你了解多少?一起来debug

享学课堂特邀作者:周周
转载请声明出处!

前言

手把手讲解系列文章,是我写给各位看官,也是写给我自己的。文章可能过分详细,但是这是为了帮助到尽量多的人,毕竟工作5,6年,不能老吸血,也到了回馈开源的时候。
这个系列的文章:

1、用通俗易懂的讲解方式,讲解一门技术的实用价值
2、详细书写源码的追踪,源码截图,绘制类的结构图,尽量详细地解释原理的探索过程
3、提供Github 的 可运行的Demo工程,但是我所提供代码,更多是提供思路,抛砖引玉,请酌情cv
4、集合整理原理探索过程中的一些坑,或者demo的运行过程中的注意事项
5、用gif图,最直观地展示demo运行效果

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

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

引子

HOOK系列是 今年年初大概3月份写的,其中《手把手讲解 Android Hook-实现无清单启动Activity》 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 checkout945df9如果结果为:HEADisnow at945df96使用androidX 则切换成功。这里是,已经出现问题的版本节点。

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

然后发现,生命周期函数并不执行。

跳转过程中,只出现了 onCreate onStart onResume, 照理说,跳转之后应该有 onPause onStop. 并且我回到这个Activity时,应该会有 onRestart onStart onResume, 但是也没有。

源码探索

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

前期准备:

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

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

这里产生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

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

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

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

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

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

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

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

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

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

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

debug 一下:image

发现了 ActivityThread,自然就联想到它的 HextendsHandler ,ok, 就快接近真相了,表激动,暂且压制一下,我们还没有找到真凭实据。

又是一个抽象方法,直接找他的子类 ActivityThread,

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

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

小结论

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

解决方案

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

上一章节中图示的正常现象 PauseActivityItem , ResumeActivityItem, StopActivityItem,在这里并未看到,或者说,这里 EXECUTE_TRANSACTION 分支缺失了。肯定是因为我们hook的代码导致 ActivityThreadH mH=newH()classHextendsHandlervoidhandleMessage(Messagemsg) 部分 switchcase 没有执行。

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

现在进入hook代码:

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

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

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

这里 list.size()是0,刚好这里 returntrue了。这里 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又一年过去了,与 各位技术人,共勉!

喜欢本文的话可以关注我们的官方账号,第一时间获取资讯。
你的关注是对我们更新最大的动力哦~

最后

给大家送一个小福利

资料都是免费分享的,附送高清脑图,高清知识点讲解教程,以及一些面试真题及答案解析。送给需要的提升技术、准备面试跳槽、自身职业规划迷茫的朋友们。点我免费领取!!!

转存中…(img-PvzvTlZK-1623556289498)]

资料都是免费分享的,附送高清脑图,高清知识点讲解教程,以及一些面试真题及答案解析。送给需要的提升技术、准备面试跳槽、自身职业规划迷茫的朋友们。点我免费领取!!!

[外链图片转存中…(img-x6M0Sz3i-1623556289498)]

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/386078.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Android架构师谈:View-Pager-性能优化之-无限循环

作者:享学课堂Alvin老师 转载请声明出处! ViewPager实现无限滑动 **方案一:**将viewpager上限设置成一个很大的数,第一个页面设置到中间。然后滑动的时候,用当前的序号与viewpager页面数取余得到目标页面的序号&#…

你知道如何用面向对象思想写好并发编程吗?

在工作中,我发现很多人在设计之初都是直接按照单线程的思路来写程序的,而忽略了本应该重视的并发问题;等上线后的某天,突然发现诡异的 Bug,再历经千辛万苦终于定位到问题所在,却发现对于如何解决已经没有了…

你知道怎么在生产环境下部署tomcat吗?

享学课堂特邀作者:老顾 转载请声明出处! 一、前言 小伙伴们在网上看到的很多文章,都是对tomcat的一些介绍,什么配置啊,怎么启动。其实在生产环境中怎么部署,和网上介绍的有很大区别。这篇文章老顾就带着大…

浅谈HashMap

Java集合类的整体架构 比较重要的集合类图如下: 有序否 允许元素重复否 Collection 否 是 List 是 是 Set AbstractSet 否 否 HashSet TreeSet 是(用二叉树排序) Map AbstractMap 否 使用 key-value 来映射和存储数据, Key 必须惟…

互联网寒冬!“996”为什么还没实行?我还等着早点下班呢!

“喊了十多个月的‘996’,说要实行‘996’,上班上到现在,影子都没看到,我还能早点下班吗?” 我一个在广州上班的朋友小李,在我去广州出差期间,与他聊天的时候发出了这样的牢骚,我刚…

matlab求解常微分方程组/传染病模型并绘制SIR曲线

看了很多关于传染病模型的matlab程序,大都是绘制出两条曲线(I、S)的,本文最大的不同是绘出SIR三条曲线。 先给出SIR微分方程组 函数文件: run的程序:

互联网寒冬!技术站最全MySQL数据库实战规范

享学课堂特邀作者:老顾 转载请声明出处! 前言 我们小伙伴们经常使用到mysql数据库,一般就这么一用,很少会考虑mysql里面的细节问题,如sql语句的规范,或索引有没有起到相应的效果,今天老顾就给大…

SQL求一个表中非重复数据及其出现的次数

mysql中,我们可以用distinct求不重复的数据有多少,也可以用group by。 这里有个例子,如下表sheet1,共有5411条数据 查询语句 共有3446条不重复数据,每条不重复数据出现的次数在第二列显示:

什么是微服务扩展性和高可用-可扩展性、高可用性和性能

欢迎关注专栏:Java架构技术进阶。里面有大量batj面试题集锦,还有各种技术分享,如有好文章也欢迎投稿哦。 Overview 可扩展性、高可用性和性能 术语可扩展性、高可用性、性能和关键任务对于不同的组织或组织内的不同部门来说可能意味着不同的…

SQL实现当前行等于前面两行数据之和

sql实现类似斐波那契数列的功能,即当前数据等于前面两个数据之和,详看本文例子 原表: sql语句(此处要熟悉JION ON的用法) 结果

【大牛系列教学】靠着这份面试题跟答案

开篇闲扯 打工人,打工魂,我们生而人上人。当“资本主义”逐渐禁锢我们人(大)上(韭)人(菜)肉体的时候,那一刻我才明白那个日不落帝国资本主义收割机瑞民族之光幸瑞幸咖啡…

【工作感悟】成功入职阿里月薪45K

前言 苦苦寻觅找工作之间,殊不知今日之时乃我心之痛,难道是我不配拥有工作嘛。自面试后他所谓的等待都过去一段时日,可惜在下京东上的小金库都要见低啦。每每想到不由心中一紧。正处为难之间,手机忽然来了个短信预约后续面试。 我…

【工作经验分享】不会真有人觉得mybatis很难学吧

什么是自旋锁和互斥锁? 由于CLH锁是一种自旋锁,那么我们先来看看自旋锁是什么? 自旋锁说白了也是一种互斥锁,只不过没有抢到锁的线程会一直自旋等待锁的释放,处于busy-waiting的状态,此时等待锁的线程不会…

【工作经验分享】这些新技术你们都知道吗

前言 近年来,微服务架构(Microservices Architecture)已经成为一种主流的软件开发方法论,所谓微服务( Microservices ),就是一些具有足够小的粒度、能够相互协作且自治的服务体系。 微服务架构基于分布式系统,同时借助了面向服务架构和企业服…

【微信小程序】使用Hystrix的插件机制

前言 在本篇文章开始前,我想想来回答一个问题:我为什么要写这一篇关于面试的文章? 原因有三:第一,我想为每一个为梦想时刻准备着的”有心人“尽一份自己的力量,提供一份高度精华的Java面试清单&#xff1…

【微信小程序】目前最全的《Java面试题及解析》

开头 在找工作的过程中,对于 Redis 技术知识的掌握已经成为必须的技能。美团面试常常就会被问到Redis相关知识,而这次我就差点倒在了美团3面,面试官连问我以下几个Redis的问题,然后就卡壳了… redis了解吗?你说说怎么…

大话数据结构——算法

算法:算法是解决特定问题求解步骤的描述,在计算机中表现为指令的有限序列,并且每条指令表示一个或多个操作。 为什么把数据结构和算法一起说? 想想罗密欧与朱丽叶,梁山伯和祝英台,少了一个你总会觉得奇怪…

java线上培训班学费一般多少,成长路线图

前言 众所皆知的,Linux的核心原型是1991年由托瓦兹(Linus Torvalds)写出来的,但是托瓦兹为何可以写出Linux这个操作系统?为什么它要选择386的计算机来开发?为什么Linux的发展可以这么迅速?又为什么Linux是免费的?以及目前为何有这么多的 Linux版本(…

java线程池使用实战,太牛了!

前言 今天这篇文章中简单介绍一下一个 Java 程序员必知的 Linux 的一些概念以及常见命令。 如果文章有任何需要改善和完善的地方,欢迎在评论区指出,共同进步!笔芯! 正式开始 Linux 之前,简单花一点点篇幅科普一下操作…

大数据基础技术和应用

大数据概述 数据的表现形式: 线下数据信息化:数据库、文字记录、照片……互联网-移动互联网:网页数据、用户行为记录、数字图像……传感器:设备监控、智能家居、摄像头…… 大数据的4V特征: 大量化(Vol…