Android 自定义通知的TransactionTooLargeException问题

836 阅读3分钟

这是我参与「掘金日新计划 · 2 月更文挑战」的第 2 天

自定义通知系列文章包括:

  1. 自定义通知的基础使用、自定义通知样式的UI适配(展开&折叠)
  2. bug修复,包括TransactionTooLargeException、ANR

本文是第二篇,记录了TTL问题以及解决思路和方案,值得一看~

TransactionTooLargeException

报错如下

Fatal Exception: java.lang.RuntimeException 
android.os.TransactionTooLargeException: data parcel size 518960 bytes

image.png

调用notify方法报错,就一行代码

(ContextCompat.getSystemService(context, NotificationManager::class.java) as NotificationManager).notify(
    NotifyConstant.NOTIFY_ID_RESIDENT,
    getResidentNotification(context)
)

第一次尝试:

getResidentNotification方法中有没有大图传输或者intent传输值,唯一和图片相关的就是 setLargeIconsetSmallIcon,尝试将图片压缩到200K以下,线上仍然报错,推测和图片无关

第二次尝试:

问题非必现的,刚上线没有这个问题,时间越往后问题占比越高,结合项目的特点:通知一直运行在后台且每隔5分钟刷新一次界面,考虑和时间的积累有关,尝试修改刷新频率为5ms(之前是5min),进行极限测试,问题复现,初步判断和更新UI有关。刷新UI调用的代码如下,考虑和RemoteViews有关,且是跨进程通信出现的传输数据过大,具体查看更新UI的代码

remoteView?.setTextViewText(R.id.tv_cpu_tem_tip_yellow,"") 
remoteView.setViewVisibility(R.id.small_fl_cpu, View.GONE)

系统并没有通过Binder去直接支持View的跨进程访问,而是提供了一个Action的概念,Action代表一个View操作,Action同样实现了Parcelable接口。系统首先将View操作封装到Action对象,界面上的控件每更新一次,mActions的长度就会加1,调用NotificationManager.notify()就会遍历所有的Action,执行Action的apply方法,通过反射调用TextView的相关方法。

image.png

(图片引用自安卓开发艺术探索)

//调用setTextViewText后,mActions会+1
private ArrayList<Action> mActions;
private void addAction(Action a) {
    if (mActions == null) {
        mActions = new ArrayList<>();
    }
    mActions.add(a);
}

//BaseReflectionAction.java
@Override
public final void apply(View root, ViewGroup rootParent, InteractionHandler handler,
        ColorResources colorResources) {
    final View view = root.findViewById(viewId);
    if (view == null) return;
    Class<?> param = getParameterType(this.type);
    if (param == null) {
        throw new ActionException("bad type: " + this.type);
    }
    Object value = getParameterValue(view);
    //调用setText方法
    try {
        getMethod(view, this.methodName, param, false /* async */).invoke(view, value);
    } catch (Throwable ex) {
        throw new ActionException(ex);
    }
}

考虑跨进程通信时,mActions对象过大,导致抛出TTL,解决:mActions的大小超过一定限制就重新初始化RemoteView,伪代码如下

private var mActionsSize = 0  
private var mRefreshTime = 0

mRefreshTime++
runCatching {
    val remoteViewsClass = Class.forName("android.widget.RemoteViews")
    val mActionsField: Field = remoteViewsClass.getDeclaredField("mActions")
    mActionsField.isAccessible = true
    //反射拿到mActions的大小
    val d = mActionsField.get(residentRemoteView) as MutableList<*>
    mActionsSize = d.size
}
//这里有一个兜底逻辑,如果反射获取mActionsSize失败,就走mRefreshTime的逻辑
//mRefreshTime是指调用RemoteViews API的次数
// 100 和 15是一个粗略值,大佬有更好的建议请在文末留言,谢谢啦~
if (mActionsSize >= 100 || mRefreshTime >= 15) {
    mActionsSize = 0
    mRefreshTime = 0
    residentRemoteView = null //手动将RemoteView置null
    residentSmallRemoteView = null
}
if (residentRemoteView == null || residentSmallRemoteView == null) {
    initResidentRemoteView(context)
}

通知的覆盖问题(探索性问题)

image.png

需求:

当用户卸载应用时,更晚的弹出问题,这样用户最后看到的就是我们的app,下拉通知栏,我们的应用就会显示在第一个。

思路:

1.提高通知的优先级,目前项目代码里已经是PRIORITY_MAX

2.使用window方式显示在屏幕中间,更具有干扰性

切图.png

3.尝试在展示通知之前开个delay 10秒,log确实是有延迟了10秒,但是在通知栏里面,那条通知并没有置顶显示,此方案不可行

image.png

image.png

参考链接

www.jianshu.com/p/16120f7ea…

www.jianshu.com/p/fc237e90c…

How to update Notification with RemoteViews?