AGP8 混淆打包,反射实例化启动闪退

在这里插入图片描述

1、问题

正如标题所示

同一个工程打包,配置 AGP8+ 打包启动闪退,配置 AGP7+ 打包启动运行正常

Process: com.primer.hello, PID: 29758
	java.lang.RuntimeException: Unable to instantiate application com.primer.hello.ai_reply_keyborad.AiKeyboardApplication 
	package com.primer.hello: java.lang.NullPointerException:
	Attempt to invoke virtual method 'java.lang.String com.channel.reader.ChannelReader.getChannel(android.content.Context)' on a null object reference

	at android.app.LoadedApk.makeApplicationInner(LoadedApk.java:1495)
	at android.app.LoadedApk.makeApplicationInner(LoadedApk.java:1424)
	at android.app.ActivityThread.handleBindApplication(ActivityThread.java:7274)
	at android.app.ActivityThread.-$$Nest$mhandleBindApplication(Unknown Source:0)
	at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2374)
	at android.os.Handler.dispatchMessage(Handler.java:106)
	at android.os.Looper.loopOnce(Looper.java:223)
	at android.os.Looper.loop(Looper.java:324)
	at android.app.ActivityThread.main(ActivityThread.java:8524)
	at java.lang.reflect.Method.invoke(Native Method)
	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:582)
	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1059)
Caused by: java.lang.NullPointerException: 
	Attempt to invoke virtual method 'java.lang.String com.channel.reader.ChannelReader.getChannel(android.content.Context)' on a null object reference

	at com.primer.core.kinetic.common.param.Utils.getChannel(Unknown Source:23)
	at com.primer.core.kinetic.common.param.Utils.setContext(Unknown Source:11)
	at com.primer.core.kinetic.api.CoreManager.applicationAttachBaseContext(Unknown Source:14)
	at com.primer.core.kinetic.api.DNSDK.applicationAttachBaseContext(Unknown Source:15)
	at com.primer.hello.ai_reply_keyborad.AiKeyboardApplication.attachBaseContext(SourceFile:48)
	at android.app.Application.attach(Application.java:374)
	at android.app.Instrumentation.newApplication(Instrumentation.java:1233)
	at android.app.LoadedApk.makeApplicationInner(LoadedApk.java:1487)
	at android.app.LoadedApk.makeApplicationInner(LoadedApk.java:1424) 
	at android.app.ActivityThread.handleBindApplication(ActivityThread.java:7274) 
	at android.app.ActivityThread.-$$Nest$mhandleBindApplication(Unknown Source:0) 
	at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2374) 
	at android.os.Handler.dispatchMessage(Handler.java:106) 
	at android.os.Looper.loopOnce(Looper.java:223) 
	at android.os.Looper.loop(Looper.java:324) 
	at android.app.ActivityThread.main(ActivityThread.java:8524) 
	at java.lang.reflect.Method.invoke(Native Method) 
	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:582) 
	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1059) 
Caused by: java.lang.NullPointerException: 
	Attempt to invoke virtual method 'java.lang.String com.channel.reader.ChannelReader.getChannel
  • 代码是在 Android application applicationAttachBaseContext下执行的
  • ChannelReader 是一个静态单例对象,通过 newInstance() 反射调用无参构造函数实例化对象 【注:按道理类加载时或调用就被初始化了】

显然,是 ChannelReader 对象实例化异常导致空指针!

2、猜想

反射让人很容易联想到代码混淆

  • 可能是混淆了 ChannelReader 类,导致无法 newInstance 实例化
  • 可能是混淆了 ChannelReader 构造方法,导致无法 newInstance 实例化
  • 可能是混淆移除了 ChannelReader 构造方法,导致无法 newInstance 实例化

以上猜想很容易验证

2.1 构造函数

查看 apk 内 ChannelReader 类是否有无参构造方法

为什么要找的是无参构造方法?

因为该对象是通过 newInstance() 实例化的!

在这里插入图片描述

没有无参构造方法,不过还不能确定是没有无参构造方法的问题。

因为一个类没有显式写无参构造方法,会默认存在一个无参构造方法的,看个例子:

这样运行和编译都是没有问题的

public class Animal {
}


public class C {
    public static void main(String[] args) {
        new Animal();
    }
}

smali 方法查看字节码更加直观,这个 <init> ()V 就是无参构造方法,从字节码层面称它为实例初始化方法,从 java 层面看他就是构造方法(无参构造方法),区别于 <clinit> 类的初始化方法

在这里插入图片描述

<clinit> 从我们看就是静态代码块

public class Animal {
    static {

    }
}

在这里插入图片描述

这两个初始化方法的执行顺序是:

  • 先执行<clinit>:静态代码块
  • 再执行<init>:构造函数

查看一个类及成员是否被移除:查看 usage.txt 文件
查看一个类及成员被混淆成什么样:查看 mapping.txt 文件
查看一个类及成员那部分 keep 了:查看 seeds.txt 文件

这三个文件是开启混淆后,在build/outputs/mapping 目录下生成

反射与混淆关联,不妨分别查看两次不同 AGP 版本打包这个ChannelReader类的混淆情况

2.2 AGP7+ 运行正常

seeds.txt

在这里插入图片描述

说明这个 ChannelReader 类的无参构造方法是需要 keep 的(实际上是否 keep 还要查看生成的包是否存在无参构造方法或查看 smali 是否有 init 实例初始化方法)

usage.txt

没有 ChannelReader 相关信息输出,说明没有该类的任何成员等没有被移除


既然类没有被混淆、也没有被移除构造函数,反射 ChannelReader 通过 newInstance 实例化对象是没有问题的,所以没有空指针异常

2.3 AGP8.5 运行闪退

seeds.txt

在这里插入图片描述

跟上述 AGP7+ 一样,说这个类的无参构造方法是需要被 keep 的,不能移除

usage.txt

在这里插入图片描述

重点:

说明 ChannelReader 类的无参构造方法需要被移除,这问题就出现了!

练默认的构造方法都被移除了,还怎么实例化对象,怎么 new 对象?


再仔细查看闪退日志,果然是 newInstance 实例化对象失败,原因是缺少无参构造方法

在这里插入图片描述

查看该类是否存在实例初始化方法,是没有找到的,证明<init>方法确实被移除,不能正确通过反射实例化对象

在这里插入图片描述

3. 解决

当然了,目标就是保留无参构造方法在混淆打包过程中不被移除,反射实例化对象才正常

所以,需要新增一条混淆规则 keep ,禁止 ChannelReader <init> 实例初始化方法被移除

-keep class com.channel.reader.ChannelReader{*;}

这可能只是临时解决方法,投机取巧

实际上真正的疑惑还没解决:

为什么 AGP8.5 混淆打包,明明 seeds.txt 输出说明要 keep ChannelReader() 无参构造方法,实际结果得到的安装包查看该类却没有无参构造方法 <init>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值