前言

消失大半年,其中一个原因是工作了,大部分时间在给公司做开发,业余时间在写外包赚米,另一个原因就是拖延症犯了,越拖越想拖

过年放了半个月假,稍微有点时间做点自己一直以来就很想做的事——给NGA去广告,当然,JS教程也会安排上的

正文

GitHub 仓库链接

开发环境配置

首先新建一个新的新项目,我为了方便选了 Settings Activity 作为模板
当然,你乐意的话完全可以不创建 Activity

选择项目模板

然后设置项目名称以及语言,推荐使用 Kotlin 浅浅用了下感觉还是很方便的,虽然依旧不如 C# 优雅

完成以后需要等待 Gradle Sync 一次,视网络情况可能会比较久

前天弄了半天一直Build失败,然后换了最新的 Android Studio,最后发现是我 Gradle 全局配置被 Android Studio 自动配置了一个根本不通的 proxy,删掉以后就正常了

Gradle Sync完成后

然后需要在模块级 build.gradle 中添加 Xposed 库

compileOnly 'de.robv.android.xposed:api:82'

添加 Xposed 库

接着在 setting.gradle 中添加仓库链接,不然默认仓库中是没有这个库的

maven { url "https://api.xposed.info/" }

添加仓库链接

然后要创建 assets 文件夹

创建 assets 目录

接着创建 xposed_init 声明文件,xposed_init 文件内容就是 Xposed Hook 入口函数类,根据实际情况修改

创建 xposed_init 文件

然后编辑 AndroidManifest.xml 添加 meta 信息

Meta 信息

  • xposedmodule
    标记此 app 是一个 Xposed 模块,必须为 true
  • xposeddescription
    模块描述信息,显示在 Xposed 模块管理器中
  • xposedminversion
    兼容的最低 Xposed API 版本,与引用的库版本一致即可
  • xposedscope
    说明该模块推荐对哪些 app 启用,是一个包名数组,内容如下图

arrays.xml 内容

接着创建 Xposed 入口类,类名和 xposed_init 里面写的保持一致

入口函数

package com.chrxw.purenga

//import 省略

class XposedInit : IXposedHookLoadPackage, IXposedHookZygoteInit {
    override fun initZygote(startupParam: IXposedHookZygoteInit.StartupParam) {
    }

    @Throws(Throwable::class)
    override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) {
    }
}

到此准备工作就做完了

核心代码

篇幅所限本文只贴出核心代码,完整代码参考 开源仓库

首先分析APP启动逻辑,我使用 GDA 来逆向,先找启动入口

启动 Activity

定位到 gov.pianzong.androidnga.activity.LoadingActivity

LoadingActivity 类

逻辑挺好看的,调用 finishPage 或者 goHome 应该就能自动关闭广告了,不过这个方法是实例方法,需要想办法获取 LoadingActivity 实例

关闭广告的逻辑

用交叉引用找不到别的地方引用,不过无伤大雅,用字符串搜索可以在 com.donews.nga.interfaces.ActivityLifecycleImpl 类中的 toForeGround 静态方法方法里判断了参数是否为 LodingActivity,而且这个类是一个静态类,这就非常方便了

ActivityLifecycleImpl 类

然后发现一个控制切屏广告的方法,如果前后台的时间戳满足条件就显示广告,也一起处理掉

LoadingActivity 类

上代码

try {
    // 获取ActivityLifecycleImpl类
    val clsLifeCycle =
        XposedHelpers.findClass("com.donews.nga.interfaces.ActivityLifecycleImpl", mClassLoader)

    // 获取LoadingActivity类
    val clsLoadingActivity =
        XposedHelpers.findClass("gov.pianzong.androidnga.activity.LoadingActivity", mClassLoader)

    // Hook toForeGround 方法
    XposedHelpers.findAndHookMethod(
        clsLifeCycle,
        "toForeGround",
        Activity::class.java,
        object : XC_MethodHook() {
            @Throws(Throwable::class)
            override fun beforeHookedMethod(param: MethodHookParam?) {
                super.beforeHookedMethod(param)
                Log.i("Before")
                val activity = param?.args?.get(0) as Activity
                if (activity.javaClass == clsLoadingActivity) {
                    Log.i("跳过启动页")
                    XposedHelpers.setBooleanField(activity, "canJump", true)
                    XposedHelpers.setBooleanField(activity, "isADShow", true)
                    XposedHelpers.callMethod(activity, "goHome")
                }
                Log.i(activity.toString())
            }
        }
    )

    // 获取SPUtil类
    val clsSPUtil = XposedHelpers.findClass("com.donews.nga.common.utils.SPUtil", mClassLoader)

    // 修改时间戳实现切屏无广告
    XposedHelpers.findAndHookMethod(
        clsSPUtil,
        "getInt",
        String::class.java,
        Int::class.java,
        object : XC_MethodHook() {
            @Throws(Throwable::class)
            override fun afterHookedMethod(param: MethodHookParam) {
                when (param.args[0] as String) {
                    "AD_FORGROUND_TIME" -> {
                        Log.i("FG " + param.result.toString())
                        param.result = 0
                    }
                    "AD_BACKGROUND_TIME" -> {
                        Log.i("BG " + param.result.toString())
                        param.result = 0
                    }
                    else -> {
                        Log.i(param.args[0].toString() + " " + param.result.toString())
                    }
                }
            }
        }
    )
} catch (e: Exception) {
    Log.e(e)
}

原理很简单,先获取 ActivityLifecycleImplActivityLifecycleImpl 类的类型,因为 ActivityLifecycleImpl 是静态类,所以可以用类型直接 Hook toForeGround 方法,在方法执行前后执行 Hook 代码,判断传入的 activity 参数是否为 LoadingActivity 类,如果是的话先修改 canJumpisADShow 字段为 true,然后调用 goHome 方法就完成了关闭开屏广告的操作。

然后获取静态类 SPUtil 并 Hook 它的 getInt 方法,判断传入的参数是否为 AD_FORGROUND_TIMEAD_BACKGROUND_TIME, 如果是的话就修改返回值为 0,这样切屏回来的时候就不会展示广告了(当然,你乐意的话可以让"AD_FORGROUND_TIME"-"AD_BACKGROUND_TIME" >= 1800,这样的效果就是100%显示切屏广告了)

最后编译一下就可以了,除了第一次打开APP会有个从黑屏切到主界面的过程,开屏广告和切屏广告已经被屏蔽了

最后修改:2023 年 01 月 29 日
Null