【Android】借助AspectJ捕获第三方Java库崩溃问题

1、问题背景

使用RecyclerView管理item,由于异步数据获取,在做页面编辑的时候,由于手机和内容本身差异,总会有延迟或者卡顿,不可避免的会出现数据变化了没有及时更新的问题,导致两个崩溃出现。

Fatal Exception: java.lang.IndexOutOfBoundsException
Inconsistency detected. Invalid item position 4(offset:4).state:44

androidx.recyclerview.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline (RecyclerView.java:6364)
androidx.recyclerview.widget.GapWorker.prefetchPositionWithDeadline (GapWorker.java:288)
androidx.recyclerview.widget.GapWorker.flushTaskWithDeadline (GapWorker.java:345)
androidx.recyclerview.widget.GapWorker.flushTasksWithDeadline (GapWorker.java:361)
androidx.recyclerview.widget.GapWorker.prefetch (GapWorker.java:368)

Fatal Exception: java.lang.IndexOutOfBoundsException
Inconsistency detected. Invalid view holder adapter positionContentViewHolder{355881d position=29 id=-1, oldPos=-1, pLpos:-1 no parent}

androidx.recyclerview.widget.RecyclerView$Recycler.validateViewHolderForOffsetPosition (RecyclerView.java:6156)
androidx.recyclerview.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline (RecyclerView.java:6339)
androidx.recyclerview.widget.GapWorker.prefetchPositionWithDeadline (GapWorker.java:288)
androidx.recyclerview.widget.GapWorker.flushTaskWithDeadline (GapWorker.java:345)
androidx.recyclerview.widget.GapWorker.flushTasksWithDeadline (GapWorker.java:361)
androidx.recyclerview.widget.GapWorker.prefetch (GapWorker.java:368)
androidx.recyclerview.widget.GapWorker.run (GapWorker.java:399)
android.os.Handler.handleCallback (Handler.java:938)
intcut("execution(* androidx.recyclerview.widget.GapWorker.prefetchPositionWithDeadline(..))")

我们尝试了线程里编辑、编辑后延迟操作等方案,都未能杜绝,只是概率降低了,测试很难复现。但崩溃量确长期排第一。

2、方案探索

数据侧没办法改变同步,只能考虑改变RecyclerView源码逻辑,阻止异常发生或者抛出。

2.1、修改源码逻辑,防止异常发生。

通过查看逻辑,是因为状态错误,抛出异常没有捕获。而这些处理流程都是RecyclerView的private方法,重写父类方法不可行。

复制RecyclerView源码修改,代码量太大,以后升级维护都有风险。

方案不可行。

2.2、反射注入修改

寻找反射点,GapWorker是final,不能继承,反射注入方式也不行。

2.3、编译时技术

RecyclerView是AAR包里代码,要修改就得借助编译时技术,需要考虑自定义插件。技术有javassist和ASM,

在尝试开发javassist插件过程中,我们XAOP框架也有编译修改插件,于是借助AspectJ方案,修改最简单。

于是有了下面方案,直接上代码:

@Aspect
public class CatchRecyclerviewAspectJ {
		    @Pointcut("execution(* androidx.recyclerview.widget.GapWorker.prefetchPositionWithDeadline(..))")
		    public void prefetchPositionWithDeadlineAspectJ() {
		        // Not used
		    }  //方法切入点
		
		 @Pointcut("execution(* androidx.recyclerview.widget.RecyclerView.removeAnimatingView(..))")
		    public void removeAnimatingViewAspectJ() {
		        // Not used
		    }  //方法切入点
		
		    @SuppressLint("LongLogTag")
		    @Around("prefetchPositionWithDeadlineAspectJ()|| removeAnimatingViewAspectJ()")//在连接点进行方法替换
		    public Object aroundJoinPoint(final ProceedingJoinPoint joinPoint) throws Throwable {
		        Object result = null;
		        try {
		            result = joinPoint.proceed();
		        } catch (Throwable e) {
		            Log.e("CatchRecyclerviewAspectJ catch --- ", e.getMessage());
		            CrashReport.postCatchedException(e);  // bugly会将这个throwable上报
		        }
		        return result;
		    }
    }

但遇到一个坑,反编译apk代码发现注解没有生效,反复排查后发现是因为aspectjx插件没有配置目标类的包名支持。

aspectjx {
    include 'androidx.recyclerview.widget'
}

加入后断点调试正常:
在这里插入图片描述

firebase自定义消息弹窗崩溃,也一样拿下:

Fatal Exception: android.view.WindowManager$BadTokenException
Unable to add window -- token null is not valid; is your activity running?
android.view.ViewRootImpl.setView (ViewRootImpl.java:1351)
android.view.WindowManagerImpl.addView (WindowManagerImpl.java:148)
com.google.firebase.inappmessaging.display.internal.FiamWindowManager.show (FiamWindowManager.java:67)
com.google.firebase.inappmessaging.display.FirebaseInAppMessagingDisplay$4$4.run (FirebaseInAppMessagingDisplay.java:416)
android.app.Activity.runOnUiThread (Activity.java:7412)
com.google.firebase.inappmessaging.display.FirebaseInAppMessagingDisplay$4.onSuccess (FirebaseInAppMessagingDisplay.java:412)
com.google.firebase.inappmessaging.display.internal.FiamImageLoader$Callback.onResourceReady (FiamImageLoader.java:157)
com.google.firebase.inappmessaging.display.internal.FiamImageLoader$Callback.onResourceReady (FiamImageLoader.java:135)
com.bumptech.glide.request.SingleRequest.onResourceReady (SingleRequest.java:667)
com.bumptech.glide.request.SingleRequest.onResourceReady (SingleRequest.java:596)
com.bumptech.glide.load.engine.Engine.load (Engine.java:220)

    @Pointcut("execution(* com.google.firebase.inappmessaging.display.internal.FiamWindowManager.show(..))")
    public void fiamWindowManagerShowAspectJ() {
        // Not used
    }  //方法切入点
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值