白屏
设备一段时间使用后,白屏,需要重启,经线上日志和以下相关代码初步分析是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");