Android --- Bug调查经验记录

发布于:2025-08-09 ⋅ 阅读:(12) ⋅ 点赞:(0)

1.布局中Pag不显示的问题

在调查一个pag不显示的问题,整体逻辑没有问题,但是就是不显示
pag不显示的根本原因大概有文件找不到,一个是路径问题,一个是配置文件的问题

PAGFile.Load(context.getAssets(),"abc.png")

这个一般不会是路径问题,因为,context,getAssets()就是assets目录下去找了,除非你的代码是这样写的

PAGFile.Load(context.getAssets(),"../abc.pag")

这样应该就找不到了

我这个问题是多渠道包的问题,我有2个渠道一个是A渠道,一个是B渠道,我的资源放在了A
渠道的assets下面,但是如果多渠道没有配置的话,他默认是找main/assets的
但是main/assets里面并没有我的abc.pag,所以就不能显示
最终重新配置渠道就解决了,如下:

sourceSets {
	A {
		assets.srcDirs "src/main/assets", "src/A/assets"
	}
}

2.数据库降级问题

android.database.sqlite.SQLiteException,Can't downgrade database from version 25 to 24

数据库问题,不能降级,要升级,否则直接崩溃。

3.RecycleView 列表滑动卡顿

背景是这样的,我使用的这个列表主要是加载图片,一开始几张图片并没有卡顿,当我添加了几个上百张之后滑动出现卡顿。通过trace分析,发现一帧干了300多ms,正常60HZ的屏幕一帧也就是 1000ms ÷ 60 ≈ 16.67 毫秒(ms)。所以还是APP自身的问题。
在这里插入图片描述

从代码上看没有任何问题,Glide.with加载图片,其余Glide之外的操作也没有耗时,没有任何头绪。
紧接着我通过top命令查看CPU占用率发现当我滑动的时候,当前应用的CPU占用率非常高,手指停止之后,CPU占用率就恢复正常了,这证明滑动的过程中一直吃在系统资源,所以我再次开始分析,只有Glide这里有对bitmap加载图片的处理,代码如下:

Glide.with(imageView.getContext())
     .load(url)
     .override(targetWidth, targetHeight)
     .centerCrop()
     .error(R.drawable.logd_err)
     .into(imageView);

上面这串代码是在ListAdapter中的onBinderViewHolder()当我们滑动列表的时候,onBinderViewHolder会不断的加载图片,导致频繁的执行压缩Bitmap导致占用内存。所以直接将 override(targetWidth, targetHeight) 删掉就可以了。

3.new Thread() 优化

当我们new Thread 中有少量操作,可以快速释放,那这个Thread相对来说还是可以的,但是如果频繁的创建Thread就不太好。我们可以用HandlerThread 来替代new Thread。HandlerThread 是串行的消息队列,而new Thread 并行的线程消息。HandlerThread 创建一个线程,可以减少一定的开销。具体的写法如下:

HandlerThread handlerThread = new HandlerThread("MyHandlerThread");
handlerThread.start();

// 获取 HandlerThread 的 Looper 并创建 Handler
Handler handler = new Handler(handlerThread.getLooper()) {
    @Override
    public void handleMessage(Message msg) {
        // 处理消息
        performTask(msg);
    }
};

// 发送消息到 HandlerThread
Message message = handler.obtainMessage();
message.what = 1;
handler.sendMessage(message);

private void performTask(Message msg) {
    // 执行耗时操作
}

5.RemoteCallbackList

异常信息:

 java.lang.IllegalStateException: beginBroadcast() called while already in a broadcast 

这个异常 java.lang.IllegalStateException: beginBroadcast() called while already in a broadcast 是由于 RemoteCallbackList 的广播过程没有正确收尾 导致的,具体分析和解决方案如下:
一、异常原因解析
RemoteCallbackList 是 Android 提供的用于管理跨进程回调的工具类,其核心作用是安全地遍历和触发注册的回调(尤其是跨进程场景)。它有两个关键方法:

beginBroadcast():开始遍历回调列表,获取当前回调数量(返回值为回调总数),此时列表会被「锁定」,防止遍历过程中回调被移除导致异常。
finishBroadcast():结束遍历,解锁列表,允许后续的回调添加 / 移除操作。

异常触发条件:当 beginBroadcast() 被调用时,如果上一次的广播(beginBroadcast() 之后)没有通过 finishBroadcast() 收尾,就会抛出此异常 —— 因为 RemoteCallbackList 不允许嵌套的广播过程(同一时间只能有一个活跃的广播)。

// 错误示例:缺少 finishBroadcast()
RemoteCallbackList<MyCallback> mCallbacks = new RemoteCallbackList<>();

new Thread(new Runnable() {
    @Override
    public void run() {
        int count = mCallbacks.beginBroadcast(); // 第928行附近
        for (int i = 0; i < count; i++) {
            MyCallback callback = mCallbacks.getBroadcastItem(i);
            // 触发回调...
        }
        // 遗漏了 finishBroadcast()!导致下次 beginBroadcast() 失败
    }
}).start();
// 正确示例:begin 和 finish 成对出现,用 try-finally 确保收尾
RemoteCallbackList<MyCallback> mCallbacks = new RemoteCallbackList<>();

new Thread(new Runnable() {
    @Override
    public void run() {
        int count = mCallbacks.beginBroadcast(); // 对应第928行
        try {
            // 遍历回调并触发
            for (int i = 0; i < count; i++) {
                MyCallback callback = mCallbacks.getBroadcastItem(i);
                // 执行回调逻辑(例如 callback.onUserChanged())
            }
        } finally {
            // 必须调用 finishBroadcast() 结束广播,释放锁定
            mCallbacks.finishBroadcast(); 
        }
    }
}).start();

如果只添加了 finally { mCallbacks.finishBroadcast() } 但没有加同步锁,仍然可能出现 beginBroadcast() called while already in a broadcast 异常。

原因如下:

RemoteCallbackList 的 beginBroadcast() 和 finishBroadcast() 本身不是线程安全的。即使你保证了单次广播过程中 begin 和 finish 成对成对(通过 try-finally),但如果有多个线程同时调用广播逻辑,就可能出现以下场景:

线程 A 调用 beginBroadcast() 成功(此时广播状态被标记为「活跃」);
线程 B 在线程 A 未调用 finishBroadcast() 前,也调用 beginBroadcast();
线程 B 检测到已有活跃的广播,直接抛出 IllegalStateException。

简言之:try-finally 只能保证「单次广播的开始和结束成对出现」,但无法阻止「多个线程同时发起广播」,而 RemoteCallbackList 不允许并发广播(同一时间只能有一个广播过程)。
结论
必须加同步锁:通过 synchronized 等机制确保同一时间只有一个线程执行广播逻辑(从 beginBroadcast() 到 finishBroadcast() 的完整过程)。
try-finally 是基础:保证即使广播过程中出现异常,finishBroadcast() 也会执行,避免永久锁死广播状态。

两者缺一不可,才能彻底避免该异常。


网站公告

今日签到

点亮在社区的每一天
去签到