当前位置: 首页 > article >正文

Android Lancet Aop 字节编码修复7.1系统Toast问题(WindowManager$BadTokenException)

近期在Bugly上出现7.1以下设备上出现大量BadTokenException:

android.view.WindowManager$BadTokenException

Unable to add window -- token android.os.BinderProxy@6c0415d is not valid; is your activity running?

报错堆栈,如下所示:
在这里插入图片描述

1.定位分析

查看Toast的源码可知,在android 7.1版本及其以下,没有对wm.addview()进行异常捕捉:
在这里插入图片描述
官方在android8.0 以上修复该问题,源码如下:
在这里插入图片描述

2.解决方案

2.1 先hook Toast 进行代理捕捉异常

通过查看源码可知,TN#Handler是一个hook点,可以对其进行hook 替代,捕捉异常,核心代码如下:

public class SafetToast {
   private static final String TAG="SafetToast";

   /**
    * 处理7.x 的toast 异常 ,代理TN#Handler
    * <p>
    * toast 源码地址:
    * https://cs.android.com/android/platform/superproject/+/android-7.1.0_r1:frameworks/base/core/java/android/widget/Toast.java;bpv=1;bpt=1
    *
    * @param toast
    */
   public static Toast fixToastWithAndroid7(Toast toast) {
      if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.N_MR1) {
         try {
            Class<?> toastClass = Toast.class;
            Field mTNField = toastClass.getDeclaredField("mTN");
            mTNField.setAccessible(true);
            Object mTN = mTNField.get(toast);
            Field handleField = mTN.getClass().getDeclaredField("mHandler");
            handleField.setAccessible(true);
            final Handler handler = (Handler) handleField.get(mTN);
            Handler proxyHandler = new Handler(handler.getLooper()) {
               @Override
               public void dispatchMessage(Message msg) {
                  try {
                     Log.w(TAG," proxy toast handle");
                     handler.dispatchMessage(msg);
                  } catch (Exception e) {
                     e.printStackTrace();
                  }
               }
            };
            handleField.set(mTN, proxyHandler);
            Log.w(TAG,"fixToastWithAndroid7");
         } catch (Exception e) {
            e.printStackTrace();
         }
      }else{
         Log.w(TAG," current device not need fix toast");
      }
      return toast;
   }
}

2.2 Lancet aop字节编码进行全局替换

在实际上开发中,存在各种第三方的sdk, 存在Toast 处理点不同的问题,需要通过Aop方式进行替换。

编写Lancet 核心代码

public class LancetTools {
    private static final  String TAG="LancetTools";
    @Proxy(value = "show")
    @TargetClass(value = "android.widget.Toast")
    public  void show() {
       Toast toast= (Toast) This.get();
       SafetToast.fixToastWithAndroid7(toast);
       Origin.callVoid();
    }

}

以上代码比较简单,在编译过程中,生成dex文件之前,对class文件中每个Toast.show()进行编码操作,替换成以上代码。进行hook,接着继续调用原有逻辑;

在实际上开发中,存在多个渠道包问题和Lancet 代码修改后会全量编译问题。
最佳的做法是:根据渠道动态加载Lancet 插件和抽象出一个Library模块管理有关Lancet api 代码

若是不存在多渠道包,或者变种包,则进行正常的配置便可

因项目中在vivo渠道包中使用该功能,进行验证修复效果。
进行以下操作:
在Root目录下的build.gradle中:

buildscript {
    //定义一个开关变量, 判断渠道包任务
    ext.lancet_open = gradle.startParameter.taskNames.any {
         it.contains('vivo')|| it.contains('Vivo')
    }
    dependencies {
     
        //classpath 'me.ele:lancet-plugin:1.0.6'
        // 用于解决asm6问题
        classpath 'com.bytedance.tools.lancet:lancet-plugin-asm6:1.0.2'
    }
}

在App module中:

apply plugin: 'com.android.application'
if (lancet_open) {
    //动态依赖该plugin插件
    apply plugin: 'me.ele.lancet'
}

dependencies {
    //vivo 渠道中依赖
     vivoImplementation project(':lancetLib')
     // lancetLib中已经依赖该库,因此不需要再次依赖
    //compileOnly 'me.ele:lancet-base:1.0.6'  
}

最后创建一个LancetLib的moudle, 编写lancet api 相关的代码:
在这里插入图片描述

3.测试验证

3.1 查看apk中字节编码后代码

项目中原本的代码:
在这里插入图片描述

经过lancet aop 字节编码后的代码,查看apk中代码:
在这里插入图片描述

3.2 运行Logcat 日志:

在Android 7.1及其以下设备运行:
在这里插入图片描述
成功打印日志,进入到hook toast的dispatchMessage()中,一次Toast 会有一次show 一次hide,因此会打印两遍proxy toast handle

4.进一步学习Lancet 字节编码

Lancet 常用的两种纺织方式

1.@Insert 指令
顾名思义,是在原本函数执行前或者执行后插入一段逻辑,在中转函数中接着调用原本的旧逻辑函数。通常用于项目或者sdk中创建的类。

2.@Proxy指令
顾名思义,是代理原本的方法逻辑,进行替换,执行新的逻辑操作(在中转函数中可摒弃旧的函数,也可以继续调用旧的函数)。通用对Android系统类 Api 调用。

匹配目标类
1.@TargetClass 通过类名来匹配
Scope.SELF 代表仅匹配 value 指定的目标类.
Scope.DIRECT 代表匹配 value 指定类的直接子类.
Scope.All 代表匹配 value 指定类的所有子类.
Scope.LEAF 代表匹配 value 指定类的最终子类.众所周知java是单继承,所以继承关系是树形结构,所以这里代表了指定类为顶点的继承树的所有叶子节点

2.@ImplementedInterface 通过接口来匹配
Scope.SELF : 代表直接实现所有指定接口的类.
Scope.DIRECT : 代表直接实现所有指定接口,以及指定接口的子接口的类.
Scope.ALL: 代表 Scope.DIRECT 指定的所有类及他们的所有子类.
Scope.LEAF: 代表 Scope.ALL 指定的森林结构中的所有叶节点.

申明方法注意点
保持 Hook 方法的 public/protected/private static 信息与目标方法一致,参数类型,返回类型与目标方法一致。返回类型可以用 Object 代替。方法名不限.。异常声明也不限。

通过一个案例进一步了解Lancet ,在AppCompatActivity的子类中onStop()执行前插入一段
System.out.println("hello world");

    @TargetClass(value = "androidx.appcompat.app.AppCompatActivity", scope = Scope.LEAF)
    @Insert(value = "onStop",mayCreateSuper = true)
    protected void onStop(){ // 修复符 和static 信息与目标方法一致,参数类型,返回类型与目标方法一致
        System.out.println("hello world");
        Origin.callVoid();
    }

Scope.LEAF :该类中所有的子类节点上
mayCreateSuper true: 当该方法没有重写时,会自动重写。

接着构建apk ,查看字节编码后的效果:
在这里插入图片描述

更多详细,请阅读Lancet 开源地址


http://www.kler.cn/a/2182.html

相关文章:

  • [机器学习]XGBoost(3)——确定树的结构
  • 《剑网三》遇到找不到d3dx9_42.dll的问题要怎么解决?缺失d3dx9_42.dll是什么原因?
  • 【Unity】【VR开发】实现VR屏幕共享应用的几个重要插件和参考资料分享
  • POD 存储、PV、PVC
  • Springboot3.x配置类(Configuration)和单元测试
  • 【游戏中orika完成一个Entity的复制及其Entity异步落地的实现】 1.ctrl+shift+a是飞书下的截图 2.落地实现
  • Vite中ant design vue按需引入以及css预处理配置
  • 【Java SE】变量的本质
  • 【Spring-boot源码剥析】| 启动原理之侠客行篇
  • 谷歌外链怎么挑选?谷歌外链高质量平台有哪些?
  • 磁盘I/O %util特别高
  • 基于“遥感+”融合技术在碳储量、碳收支、碳循环等多领域监测与模拟
  • [ 漏洞复现篇 ] Joomla未授权访问Rest API漏洞(CVE-2023-23752)
  • linux中写定时任务
  • Linux之磁盘分区、挂载
  • 【JavaSE】类和对象(中)
  • TypeScript(七)类
  • day12函数进阶作业
  • JVM学习.02 内存分配和回收策略
  • 三维点云转深度图
  • STM32的推挽输出和开漏输出
  • 【ChatGPT】教你搭建多任务模型
  • VxWorkds 内存管理(3)
  • 单元测试、反射、注解、动态代理
  • c++STL急急急
  • 禁用非必需插件,让 IDEA 飞起