最近发现自己的 android 机在开网易新闻,知乎等 app 的时候,明明自己没有杀进程,从后台返回回来的时候还是会再次显示广告,而且又正巧在知乎上看到了有人在提问,于是逆向分析。** 就用知乎作为例子吧。** 我用的 5.4.1 版本
先说结果:
不是因为杀进程、杀后台的原因造成的,这是知乎的业务逻辑。
知乎就是为了显示更多次数的广告,故意设计成这个样子的。
你启动知乎的时候如果没显示广告,那么等你切到后台再回来就显示一次。
你启动知乎的时候如果显示广告了,那么你切到后台了不杀死知乎,那么他就不显示。
讨厌的广告界面如下
准备工作,利用 apktool 拆包,然后开始逆向分析。 首先通过搜索字符串方法,搜索 "发现更大的直接" 然后找到了下面的字符串
- <string name="title_find_bigger_world">发现更大的世界</string>
然后根据这个 name 的值发现了这个广告界面布局文件是 "fragment_launch_ad.xml" 继续找, 根据 id 然后对应 "public.xml" 等方法, 可以最终找到其实这个页面就是 "LaunchAdFragment.java" 首先我们找到讨厌的广告页面了
我从后台切回来的时候肯定是当前 Activity 在作祟,他执行了一些程序才唤起的 "LaunchAdFragment"
按照这个思路我就像看看知乎嘴上层的 activity 叫什么名字,连上 adb 执行如下命令
- adb shell dumpsys activity
然后发现了,原来这个 activity 的名字叫 "MainActivity" 啊 activity 栈如下
- Running activities (most recent first):
- TaskRecord{b4d2961 #339 A=com.zhihu.android U=0 StackId=1 sz=1}
- Run #4: ActivityRecord{bc733c u0 com.zhihu.android/.app.ui.activity.MainActivity t339}
- TaskRecord{35c713e #340 A=com.android.gallery3d U=0 StackId=1 sz=1}
- Run #3: ActivityRecord{cd14b9f u0 com.android.gallery3d/com.huawei.gallery.app.GalleryMain t340}
- TaskRecord{1fb8a4b #333 A=com.android.settings U=0 StackId=1 sz=1}
- Run #2: ActivityRecord{d3eec28 u0 com.android.settings/.HWSettings t333}
- TaskRecord{69570b7 #335 A=com.tencent.mm U=0 StackId=1 sz=1}
- Run #1: ActivityRecord{a82f024 u0 com.tencent.mm/.ui.LauncherUI t335}
- TaskRecord{e43bab9 #295 I=com.android.settings/.deviceinfo.UsbConnModeChooserActivity U=0 StackId=1 sz=1}
- Run #0: ActivityRecord{9d10efe u0 com.android.settings/.deviceinfo.UsbConnModeChooserActivity t295}
综上所述,这个 MainActivity 和 LaunchAdFragment 肯定有关系,我就在 MainActivity 里面,然后就在 MainActivity 里面搜索 LaunchAdFragment,
发现了居然在 onResume 里面调用了 LaunchAdFragment 根据直觉来说,我觉得应该问题就在这里了。我找到了知乎居然有下面这些逻辑。(看不懂没关系,往下读,我已经手动的转成了 Java)
- .line 533
- :cond_3
- invoke-static {}, Lcom/zhihu/android/app/util/LaunchAdHelper;->getInstance()Lcom/zhihu/android/app/util/LaunchAdHelper;
- move-result-object v5
- invoke-virtual {v5}, Lcom/zhihu/android/app/util/LaunchAdHelper;->isShowLaunchAd()Z
- move-result v5
- if-eqz v5, :cond_4
- .line 535
- const-string v5, "input_method"
- invoke-virtual {p0, v5}, Lcom/zhihu/android/app/ui/activity/MainActivity;->getSystemService(Ljava/lang/String;)Ljava/lang/Object;
- move-result-object v0
- check-cast v0, Landroid/view/inputmethod/InputMethodManager;
- .line 536
- .local v0, "imm":Landroid/view/inputmethod/InputMethodManager;
- invoke-virtual {v0}, Landroid/view/inputmethod/InputMethodManager;->isActive()Z
- move-result v5
- if-nez v5, :cond_4
- .line 538
- invoke-static {}, Lcom/zhihu/android/app/ui/fragment/LaunchAdFragment;->buildIntent()Lcom/zhihu/android/app/util/ZHIntent;
- move-result-object v5
- invoke-virtual {p0, v5}, Lcom/zhihu/android/app/ui/activity/MainActivity;->startFragment(Lcom/zhihu/android/app/util/ZHIntent;)V
对应的 Java 代码
- void onResume() {
- // 判断是否需要加载广告,
- boolean showAd = LaunchAdHelper.getInstance().isShowLaunchAd();
- if (!showAd) return;
- //判断系统键盘是否是active的
- InputMethodManager imm = ((InputMethodManager) getSystemService("input_method"));
- if (imm.isActive()) return;
- //打开广告页面
- Intent intent = LaunchAdFragment.buildIntent();
- startFragment(intent);
- }
具体的逻辑已经在代码里面加了注释,所以说,到底显示还是不显示广告主要看这个方法了
- LaunchAdHelper.getInstance().isShowLaunchAd()
然后找到这个类,跟进这个方法,具体看源码
- .method public isShowLaunchAd() Z.locals 2
- .prologue const / 4 v0,
- 0x0
- .line 105 iget - boolean v1,
- p0,
- Lcom / zhihu / android / app / util / LaunchAdHelper; - >mIsAllowShowLaunchAd: Z
- if - eqz v1,
- :cond_0
- .line 106 iput - boolean v0,
- p0,
- Lcom / zhihu / android / app / util / LaunchAdHelper; - >mIsAllowShowLaunchAd: Z
- .line 107 const / 4 v0,
- 0x1
- .line 110 : cond_0
- return v0.end method
转换成 java 代码如下
- public
- boolean
- isShowLaunchAd
- ()
- {
- if(!mIsAllowShowLaunchAd) {
- return false;
- }
- mIsAllowShowLaunchAd = false;
- return true;
- }
好了,至少到现在大家应该知道了,显示不显示广告主要靠这个 LaunchAdHelper 里面的成员变量 mIsAllowShowLaunchAd
谁能够改变控制这个变量,业务逻辑主要就是控制他了。 而且已经出现了业务逻辑就是,如果显示了一次,那么这个变量就会为 false,然后就不显示了。(至少现在是这样)
然后搜索 LaunchAdHelper 发现了他在 onStart 里面初始了这个变量
- .method public onStart(Landroid/content/Context;)V
- //------略------
- iput-boolean v0, p0, Lcom/zhihu/android/app/util/LaunchAdHelper;->mIsAllowShowLaunchAd:Z
- .line 88
- :cond_0
- const/4 v0, 0x0
- iput-boolean v0, p0, Lcom/zhihu/android/app/util/LaunchAdHelper;->mIsFromInnerActivity:Z
- .line 89
- iget-boolean v0, p0, Lcom/zhihu/android/app/util/LaunchAdHelper;->mIsAllowShowLaunchAd:Z
- iput-boolean v0, p0, Lcom/zhihu/android/app/util/LaunchAdHelper;->mIsLaunchAdShow:Z
- //------略------
- .end method
其实这段代码的意思就是初始化了一下这个 mIsAllowShowLaunchAd,设置成 true,那么紧接着问题就来了,谁调用了 LaunchAdHelper 的 onStart 方法呢,
他的父类是 Object 啊,只是一个普通的类,
继续全局搜索
然后发现绕了一圈我们又绕回来了,在 MainActivity 里面调用了这个方法,结果如下
- .method protected onStart()V
- .locals 1
- .prologue
- .line 460
- invoke-super {p0}, Lcom/zhihu/android/app/ui/activity/BaseFragmentActivity;->onStart()V
- .line 461
- invoke-static {}, Lcom/zhihu/android/app/util/LaunchAdHelper;->getInstance()Lcom/zhihu/android/app/util/LaunchAdHelper;
- move-result-object v0
- #下面这两句就是调用了初始化广告的方法
- invoke-virtual {v0, p0}, Lcom/zhihu/android/app/util/LaunchAdHelper;->onStart(Landroid/content/Context;)V
- .line 462
- return-void
- .end method
好了,现在为止,大概明白了吧。
在 MainActivity 的 onStart 方法里面调用了 LaunchAdHelper 的 onStart 方法,随后在这里面初始化将 mIsAllowShowLaunchAd 设置成 true 的。
然后再 onResume 里面再显示广告显示一次了再次 onResume 就不显示了 但是如果再次调用 onStart 那么又初始化了,设置成了显示广告, 那么具体什么时候会调用 Activity 的 onStart 呢, 官方文档是这么说的:
具体意思就是显示的时候就会调用 onStart。
其实结果已经在文章开始部分说过了,不记得的话往前翻翻吧
这就是知乎的这个闪屏广告显示的逻辑,
有个以为接到了这种业务逻辑的时候程序员会不会心里内心一万只羊驼奔腾飞过
还有,他们没混淆 app 吗,混淆过的方法不应该是
a.b.a1(p1,p3);
这个样子的吗。
转眼一看软件版本 5.4.1,恍然大悟。
个人博客: MartinHan 的小站
博客网站: hanhan12312 的专栏
知乎: MartinHan01
来源: https://juejin.im/post/5a373a275188254b8b353ec5