白屏

设备一段时间使用后,白屏,需要重启,经线上日志和以下相关代码初步分析是AMS 窗口显示问题。检查相关业务代码,发现Activity有可能被finish多次,导致失败,还有多次startActivity

if (isFinishing()) {
  return;
}
finish();

startActivity  //要防止重复启动,在垃圾机型上会出问题

黑屏

经过排查,发现软件不稳定时,捕到崩溃时,调用了下面杀掉进程方法,在某些机器上,表现很奇怪,主进程无法启动,导致无响应!

Process.killProcess(Process.myPid()); //能不用则不用
怎么解决?我加了很多措施。
1、因为rk 系统有root功能,am force-stop 这个停止命令十分好用,因为as 安装apk时也会执行这个命令

2、添加一个插件apk,崩溃时,如果安装了则跳到一个crash页,过几秒又跳回崩溃app

3、退出进程处理优化, System.exit(1); //我不确定这个退出是否可靠,但是我看见腾讯捕获SDK有这个

4、mDefaultExceptionHandler.uncaughtException(thread, throwable); //系统处理崩溃, 点击弹窗后,进程死了, 最后再移交给默认处理器,应该就是ThreadGroup。

加了上面的措施,客户不再反馈黑屏问题了。我也尝试深究why,黑屏,分析了一下日志不敢断定。

发生黑屏时日志,lowmemorykiller, 有去kill 了应用应用进程。

Process: Sending signal. PID: 1937 SIG: 10
Process: Sending signal. PID: 1937 SIG: 9

也有模拟信号去复现场景也确实会出现,进一步你可以了解,oom_adj,lowmemorykiller 这些好玩的东西

cat /proc/[PID]/oom_adj # 命令会直接显示出对应进程号的adj值

我的常用命令备忘录

理解杀进程的实现原理

解读Android进程优先级ADJ算法

我了解上面信息,就猜测是:崩溃时,进程die,adj降低, 回收进程时, 系统误杀了新启动的应用进程

另外我们的app是个系统桌面,系统persistent进程级别。

Android 4.4假性黑屏

Android 4.4黑屏日志(闪退日志)

adb shell bugreport  

<4>[10905.332851] send sigkill to 993 (d.process.acore), adj 9, size 4564
<4>[10905.432279] select 30723 (ng.vscreen.base), adj 0, size 161149, to kill
<4>[10905.432355] select 822 (ericenceService), adj 5, size 4125, to kill
<4>[10905.432422] select 1008 (d.process.acore), adj 9, size 4616, to kill
<4>[10905.432462] send sigkill to 1008 (d.process.acore), adj 9, size 4616
<4>[10905.503747] select 30723 (ng.vscreen.base), adj 0, size 161149, to kill


观察内存
2022-01-10 21:44:07.324 23017-23258/cn.mashang.vscreen.base D/MemoryInfo: 1.00G,537.68M,96.00M,false; JavaHeapInfo: 23/384mb,ratio:6.13%
2022-01-10 21:44:15.844 23017-23258/cn.mashang.vscreen.base D/MemoryInfo: 1.00G,362.04M,96.00M,false; JavaHeapInfo: 28/384mb,ratio:7.38%
2022-01-10 21:44:24.854 23017-23258/cn.mashang.vscreen.base D/MemoryInfo: 1.00G,112.67M,96.00M,false; JavaHeapInfo: 18/384mb,ratio:4.74%

单看上面日志,简单得出内存增长到高点被系统kill,导致黑屏

Android 7.1.2错误信息,图文表现为白屏

logcat错误信息:

skia: libjpeg error 54 <Insufficient memory (case 4)> from output_message
skia: setjmp: Error from libjpeg
skia: --- codec->getAndroidPixels() failed.

Image can't be decoded  ##ImageLoader

libjpeg 源码分析

/external/libjpeg-turbo/*

#include "jerror.h"             /* get library error codes too */    //错误定义头文件

116JMESSAGE(JERR_OUT_OF_MEMORY, "Insufficient memory (case %d)")    //错误提示位置


//分配内存时

METHODDEF(void *)
alloc_large (j_common_ptr cinfo, int pool_id, size_t sizeofobject)
/* Allocate a "large" object */
{
...
if (hdr_ptr == NULL)
    out_of_memory(cinfo, 4);    /* jpeg_get_large failed */
..
}

检查业务代码,加载图文这块,由于客户上传了很大的图片。再来看看业务:轮播页采用了ImageLoader库,BaseImageDecoder 是默认解码器。遇上这复杂问题,先看看源码报错信息,结合知识分析应该是安卓4.4加载大图bitmap,内存疯狂飙升导致被系统杀死进程,安卓7.12虽然不会崩溃但是解码失败了,按照3-5mb图片测试,确实解码失败之后,ImageLoader重试加载直到成功。复杂情况,依然是简单demo 分析。bitmap-demo, 如何构建demo准确分析问题呢?根据底层思维,就模拟底层发生的动作

  • BitmapFactory.decodeStream, 防止大尺寸图OOM,decodingOptions是必备的,测试3-5MB大小图片(分辨率是19202、19203x 10802、10803)

经过图片大小测试,APP Demo是能正常加载图片的,4.4解码十分慢而已,但是问题不大。那新的问题又来了,为什么会解码失败呢?

结合skia: libjpeg 的错误信息,应该是内存申请失败导致,为什么会失败呢?为什么呢?其它地方同时申请了大内存,不太可能,因为bitmap 才是内存杀手,那就还是bitmap身上。这时需要打破思维限制,bitmap异步加载情况。

demo 走起:

new Thread(){
Bitmap bm1 = BitmapFactory.decodeFile("/sdcard/6ap9wtakywn4r3ngfdn7cyhec", opts);
}.start();

new Thread(){
Bitmap bm2 = BitmapFactory.decodeFile("/sdcard/3tocy6obm5jhw9f7e4efhd1lj", opts);
}.start();

这时,bm1 有可能解码失败,bm2也可能解码识别,安卓4.4直接崩溃了,mmp。这怎么优化呢?根本解决办法就是图片不能加载这么大,需要做限制,还有吗?控制并发解码,这里只是解码,因为ImageLoader 的threadPoolSize=3 //默认处理线程数,App更是定义了4, 因为IO是较慢的,线程数是必须的,但是解码大图时必须要加锁

简单demo:

long nativeMax = Debug.getNativeHeapSize() / 1024;
long nativeAllocated = Debug.getNativeHeapAllocatedSize() / 1024;
long nativeFree = Debug.getNativeHeapFreeSize() / 1024;

Log.d("xxa", String.format("nativeMax: %s nativeAllocated: %s nativeFree: %s", nativeMax / 1024f, nativeAllocated / 1024f, nativeFree / 1024f));

BitmapFactory.Options opts = new BitmapFactory.Options();
        //opts.inMutable =true;
        opts.inSampleSize = 9;
        Object lock = new Object();
        new Thread(() -> {
            //imageView.post(() -> imageView.setImageBitmap(bm));
            synchronized (lock) {
                Bitmap bm2 = BitmapFactory.decodeFile("/sdcard/6ap9wtakywn4r3ngfdn7cyhec", opts);
                System.out.println(bm2);
                imageView.post(() -> imageView.setImageBitmap(bm2));
            }
        }).start();
        new Thread(() -> {
            synchronized (lock) {
                Bitmap bm1 = BitmapFactory.decodeFile("/sdcard/3tocy6obm5jhw9f7e4efhd1lj", opts);
                System.out.println(bm1);
                imageView.post(() -> imageView.setImageBitmap(bm1));
            }

        }).start();

加了锁后,自测Demo一切正常。具体的app策略无非更完善写,加载文件时判断,是否大于3MB且分辨率是屏幕的3倍,是的话就加lock,主进程解码就不作处理了,以免卡UI。

瑞芯微等板卡,网卡网络断开恢复后,不能上网问题

瑞芯微等板卡,网卡网络断开恢复后,不能上网问题, PC电脑是no problem,

EthernetMonitor, 以太网监听,要是网络一段时间内断网,加上一个良好机制重启网卡。

public static boolean restartNetworkCard() {
        try {
            OutputStream out = null;
            Process suProcess = createSuProcess();
            out = suProcess.getOutputStream();
            //out.write("ifconfig eth0 down up\n".getBytes());
            out.write("ifconfig eth0 down\n".getBytes());
            out.flush();
            ThreadSleeper.sleep(100);
            out.write("ifconfig eth0 up\n".getBytes());
            out.write("exit\n".getBytes());
            out.flush();
            out.close();
            return true;
        } catch (Exception e) {
            Log.error(TAG, "restartNetworkCard error!");
        }
        return false;
    }

sdcard挂载失败

我们的设备挺多比较奇葩的,经常会遇到sdcard找不到的情况。

buggly:
java.lang.NullPointerException:Attempt to invoke virtual method 'java.lang.String java.io.File.getPath()' on a null object reference
k6.x.<clinit>(VideoDownloader.java:1)

真实代码:
private static final String OLD_SAVE_DIR = VSApp.getUserDataDir().getPath(); //getUserDataDir 获取了,Environment.getExternalStorageDirectory();

如果研究vdc的源码会发现安卓的变迁史。一把眼泪。问题可能是挂载失败,如果是系统服务应用(如桌面)则可能是应用启动早于sdcard, sd挂载晚了。

获取sdcard目录时需要,安全判断

/**
     * SD卡是否可用
     *
     * @return SD卡是否可用
     */
    public static boolean isSDCardReady() {
        return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
    }

加上广播监听

IntentFilter iFilter = new IntentFilter();
iFilter.addAction(Intent.ACTION_MEDIA_MOUNTED);
iFilter.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
iFilter.addDataScheme("file");