java.lang.ClassCastException: android.view.AbsSavedState$1 cannot be cast to androidx.appcompat.widget.AppCompatSpinner$SavedState

今天遇到一个返回上一个Fragment闪退的问题,下面是大致代码:

// xxx_layout.xml
<layout>
    <include layout="@layout/view_edit_spinner" />
    <include layout="@layout/view_edit_textview" />
</layout>

// view_edit_spinner.xml
<LinearLayout>

    <androidx.appcompat.widget.AppCompatSpinner
            android:id="@+id/edit_value"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>
</LinearLayout>

// view_edit_textview.xml
<LinearLayout>
    <androidx.appcompat.widget.AppCompatTextView
            android:id="@+id/edit_value"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>
</LinearLayout>

第一次进入页面正常,当跳转到下一个页面,再返回当前页面时,会报错闪退,而且是系统平台级的,经过排查,是由于引用其他布局文件,不同控件的resId 一样,导致系统尝试恢复原来状态时类型转换错误。

解决方法:android:id=”@+id/edit_value” 中的id不同控件改成不一样的即可

RecyclerView显示所有元素,不需要包含在NestedScrollView中

import android.content.Context
import android.util.AttributeSet
import androidx.recyclerview.widget.RecyclerView

class DynamicHeightRecyclerView(context: Context, attrs: AttributeSet) :
    RecyclerView(context, attrs) {

    override fun onMeasure(widthSpec: Int, heightSpec: Int) {
        super.onMeasure(
            widthSpec,
            MeasureSpec.makeMeasureSpec(Int.MAX_VALUE shr 2, MeasureSpec.AT_MOST)
        )
    }
}

Android Adapter调用notifyDataSetChanged()列表不更新的问题

目前发现一个Adapter调用notifyDataSetChanged()列表不更新的问题,代码如下:
mChannelList = channelListBean.getList();
huodaoInfoAdapter.notifyDataSetChanged();

原因:channelListBean.getList()和mChannelList是两个不同的变量,内存地址不同,赋值语句将新的内存地址赋给mChannelList,而Adapter中保留的list还是原来的内存地址。

解决方法:

if (!mChannelList.isEmpty()) {
  mChannelList.clear();
}
mChannelList.addAll(channelListBean.getList());

huodaoInfoAdapter.notifyDataSetChanged();

小米手机MIUI抓取蓝牙日志

1.打开开发者选项,打开蓝牙调试日志和蓝牙数据包日志开关

2.在拨号盘输入一次  *#*#5959#*#*  即开始抓蓝牙日志

3.操作APP,进行蓝牙通信

4.再拨号盘输入一次  *#*#5959#*#*

5.等待大概半分钟,在文件管理器中 /sdcard/MIUI/debug_log下会生成蓝牙日志文件

6.使用wireshark打开蓝牙日志文件

javax.net.ssl.SSLHandshakeException: com.android.org.bouncycastle.jce.exception.ExtCertPathValidatorException: Could not validate certificate

MqttException (0) – javax.net.ssl.SSLHandshakeException: com.android.org.bouncycastle.jce.exception.ExtCertPathValidatorException: Could not validate certificate: Certificate not valid until Fri Feb 28 09:25:13 GMT+08:00 2020 (compared to Mon Jan 21 17:46:42 GMT+08:00 2013)

SSL连接报错,原因是Android终端时间不对,和当前时间相差太大

记录一下Fragment连续跳转2次返回首页时第三个页面还显示的问题

有个需求是 “ A页面 ”跳转“ B页面 ”然后跳转“ C页面 ”。

在使用Fragment进行页面跳转的时候,第一次跳转(A->B)加.addToBackStack(null) ,第二次跳转(B->C)不加,然后点击返回按钮

期望状态是B、C消失,A出现

可是最终效果是返回到了A页面同时C页面还在,并没有被destroy,当进入另一个界面返回到A,这时候C会重新生成

原因:点击返回键只回滚了第一次跳转(A->B)加.addToBackStack(null) ,C页面没有影响,也不会重新生成。A重新跳转到其他页面变成(A、C->B) 加.addToBackStack(null) ,返回的时候会回滚A、C两个页面的状态

解决方案:B->C)加.addToBackStack(null)

一个Linux小白如何实现让进程在后台运行更可靠

我是一个Linux小白,最近在做一个微信小程序监控项目,使用的是海康威视的网络硬盘录像机加萤石云的解决方案。

由于每个摄像头需要显示缩略图,但是缩略图抓取又比较耗时(4秒才出一张图),所以打算服务器定时抓取每个摄像头的缩略图,然后缓存图片链接到Redis,在有人打开微信小程序的时候,直接返回内存里的图片。

这样既可以减少服务器的负担,同时使得抓图频率在可控的范围内。

注意:设备抓图能力有限,请勿频繁调用,频繁调用将会被拉入限制黑名单,建议调用的间隔为4s左右

以上是来自萤石官方的提醒,有点怕怕的。所以我就按照建议把抓图间隔设置为4秒,AccessToken也存放到了内存,防止重复生成。

我执行了下面的命令:

php /xxx/getCapture.php

文件在服务器里跑起来了,小程序开发工具里也看到抓图了,很不错,然后我就把shell界面关掉睡觉去了。

第二天,打开小程序一看,缩略图全黑了,怎么回事?检查发现抓图并没有一直执行,在我关闭shell界面就停了。百度了一下在命令后面添加“&”可以让命令在后台运行,像这样:

php /xxx/getCapture.php &

我心想,这下可以了吧,还专门提交了一个小程序体验版本,修改内容是:修复抓图不显示的问题。就这样提交给微信那边审核去了,版本号填的是:0.0.4。

提交审核第二天下午,审核结果出来了,审核未通过,理由是怀疑拿测试的版本/内容提交审核,我赶紧打开小程序看了一下,缩略图又黑了,不过我认为不通过的原因并不是这个,应该是版本号的问题。

我把版本号修改为1.0.0,其他没修改,包括看不到缩略图也没修复,重新提交审核。等到第二天,居然通过了!Yeah~~

但是缩略图还是看不到啊,所以又看了一下代码,也没啥问题,之前运行的php文件又消失了,难道是系统资源不足被杀了?

后来经过查询相关资料,了解到只在后面加一个 & 运行有个缺点:当前 session 断开时,会向其子进程发出 hang up 信号,导致挂起中断。解决方法是使用 nohup 配合 &,忽略挂起信号运行:

nohup php /xxx/getCapture.php &

还有其他方法,请参考文章:Linux 技巧:让进程在后台运行更可靠的几种方法

Tips:执行nohup的命令后会紧跟着一句话:

nohup: ignoring input and appending output to ‘nohup.out’

意思是忽略的信息会被写入到nohup.out文件中,打印信息多了,时间长了就会占用宝贵的服务器存储空间!前同事有一天找我,说服务器无法上传文件了,找不到原因,问我怎么办?文件夹权限也看过了没问题,那就是服务器存储空间满了。由于我们做的项目都不大,阿里云最低配服务器的40G硬盘通常是用不完的,最后发现是 nohup.out 这个文件捣的鬼,一个文件占了服务器99%的空间,导致无法上传。再看一下后台运行的程序,打印了好多调试信息、错误信息出来,最终都保存到了 nohup.out ,时间长了就满了,解决方法是把打印信息指向一个不存在的路径,就不会保存了

nohup php /xxx/getCapture.php >/dev/null 2>&1 &

解决websocket无法连接阿里云ECS的问题

在使用websocket连接阿里云ECS的时候总是提示连接超时,一开始想到是安全组没有打开对应端口

但是加上去之后,依然无法访问,然后尝试关闭
firewalld和 iptables 就可以了。

执行如下命令:

systemctl stop firewalld

systemctl stop iptables

如果提示 Failed to start firewalld.service: Unit is masked ,原因是
firewalld 服务被锁定了,执行如下命令可以取消 firewalld 服务的锁定。

systemctl unmask firewalld

然后再执行
systemctl stop firewalld

阿拉伯数字转中文汉字

在使用某些语音合成引擎的时候,遇到一些问题,比如三位数的数字110,语音合成播放是幺幺零,但是我想让她合成一百一十,怎么做呢?

所以我打算把阿拉伯数字转换成中文汉字就可以正常合成了,代码如下:

public class NumberHanFormat {
    public static final String ZERO = "零";
    public static final String NEGATIVE = "负";
    public static final String SPACE = " ";
    public static final String MILLION = "百万";
    public static final String THOUSAND = "千";
    public static final String HUNDRED = "百";
    public static final String[] INDNUM = {"零", "一", "二", "三", "四", "五", "六",
            "七", "八", "九", "十", "十一", "十二", "十三",
            "十四", "十五", "十六", "十七", "十八", "十九"};
    public static final String[] DECNUM = {"零","一十","二十", "三十", "四十", "五十", "六十",
            "七十", "八十", "九十"};

    //数字转换汉字
    public String format(int i) {

        StringBuilder sb = new StringBuilder();

        if(i == 0) {
            return ZERO;
        }

        if(i < 0) {
            sb.append(NEGATIVE);
            i *= -1;
        }


        if(i >= 1000000) {
            sb.append(numFormat(i / 1000000)).append(MILLION);
            i %= 1000000;

        }

        if(i >= 1000) {
            sb.append(numFormat(i / 1000)).append(THOUSAND);

            i %= 1000;
        }

        if(i < 1000){
            sb.append(numFormat(i));
        }

        return sb.toString();
    }

    // 3位数转汉字
    public String numFormat(int i) {

        StringBuilder sb = new StringBuilder();

        if(i >= 100) {
            sb.append(INDNUM[i / 100]).append(HUNDRED);
        }

        int j = i%100;

        if(j != 0) {
            if(j >= 20) {
                sb.append(DECNUM[j / 10]);
                if(j % 10 != 0) {
                    sb.append(INDNUM[j % 10]);
                }
            } else {
                if(i>100) {
                    if(j<10) {
                        sb.append(DECNUM[j / 10]).append(INDNUM[j % 10]);
                    } else {
                        sb.append(DECNUM[1]).append(INDNUM[j % 10]);
                    }
                } else {
                    sb.append(INDNUM[j]);
                }
            }
        }

        return sb.toString();
    }
}

调用方法:

public static NumberWordFormat mNumberWordFormat;
mNumberWordFormat = new NumberWordFormat();
mNumberWordFormat.format(power1)