前言
做安卓开发总是伴随着不同的设备适配和不同的设备性能,同一套代码可能在Android5.0上跑着相安无事,但是在Android7.0上就有各种各样的问题,再加上安卓厂商众多以及良萎不齐的设备性能,如果没有经验,很容易就会踩坑。如果是普遍的内存泄露问题,靠水磨工夫一点一点慢慢查还是能弄清楚的,但是总有些莫名其妙的问题,于是便有了本篇文章。
一、Android 中几种优雅的退出APP方式
参考链接:https://mp.weixin.qq.com/s/DAeD5YHS9oNYA-wzcHqJEg.
一开始是测试发现app有不能必现的闪退问题,通常是发生在关闭app重启过后,于是我从关闭的代码开始看起,发现app的关闭代码是直接杀死app的进程! 代码如下(示例):
android.os.Process.killProcess(android.os.Process.myPid());
System.exit(0);
Error: That port is already in use
这种方式我个人觉得很不好,于是在网上找到了同道中人后就开始根据自己项目选择了合适的退出方式,自测了一下发现问题并没有解决。
二、第三方库导致,程序异常,内存泄漏
测试反应问题没有出现的那么频繁了,但是还是有,而且会影响到再次开启,再次开启app时,就完全无响应了,重启才有用;看了日志发现app有的端口被占用了,类似这样的
报错如下(示例):
Error: That port is already in use
和
connect: already connected (cur=1 req=1)
因为项目在本地起了一个服务(不是Android的service)AndServer,然后在github上面看了下这个开源项目发现如果在关闭的时候没有关闭这个三方服务它就会占用端口导致重启app也起不起来,看了半天发现是有同事重启app时,没有调用关闭服务的方法,加上后,问题还是没有解决,我又化身为日志阅读器(bushi),在一个咔咔发现了内存泄露 报错如下(示例):
I/art: Background sticky concurrent mark sweep GC freed 115518(12MB) AllocSpace objects,
I/art: Background sticky concurrent mark sweep GC freed 83705(8MB) AllocSpace objects, 7
I/art: Background partial concurrent mark sweep GC freed 194941(15MB) AllocSpace objects
I/art: Background sticky concurrent mark sweep GC freed 125262(11MB) AllocSpace objects,
大概是这样的日志连续出现了很多次,我就开始考虑内存抖动的问题,这个就是老生常谈了,有各种各样的原因,出现这个问题,只能说自己平常写代码的时候不规范,查问题的时候两行泪;漫长的找问题后发现之前对接一个称重SdK的时候胡乱把厂家给的示例demo代码复制粘贴过去没问题就过了,它的入参使用了Handler作为参数,但是Handler会有内存泄漏问题,但是因为这个内存泄漏其实也只是暂时的,厂家的示例demo并没有做处理,所以写代码一定要细心再细心,5555.
修复这个问题呢,实际上就是让使用Handler的activity弱引用,这样就不会因为handleMassage中还有消息队列导致activity无法销毁导致crash了 参考这篇博文:Handler内存泄漏详解及其解决方案.
总结(实用小技巧)
实际上,内存泄露有各种各样的原因,大到activity,小到一个bitmap甚至一个没有置空GC的new对象,多多利用日志和现有开发工具抓取错误日志,多一点耐心,总能解决问题,拒绝开摆鸟 参考这篇博文分享一个抓取错误日志的工具类,参考:App crash原因以及解决办法.
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Environment;
import android.util.Log;
import android.widget.Toast;
import com.aw.ccpos.FullscreenApplication;
import com.aw.ccpos.client.Client;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.lang.reflect.Field;
import androidx.annotation.NonNull;
public class CrashHandler implements Thread.UncaughtExceptionHandler {
private FullscreenApplication softApp;
public static final String TAG="CrashHandler";
private Thread.UncaughtExceptionHandler mDefaultHandler;
private Context mContext;
private Map<String,String> infos=new HashMap<String,String>();
private DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
public CrashHandler(FullscreenApplication app){
softApp=app;
}
public void init(Context context){
mContext=context;
mDefaultHandler=Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler(this);;
}
@Override
public void uncaughtException(@NonNull Thread t, @NonNull Throwable e) {
Log.e(TAG, "error : ", e);
collectDeviceInfo(mContext);
saveCrashInfo2File(e);
Toast.makeText(Client.i().getActivity(),"app程序异常,即将关闭",Toast.LENGTH_LONG).show();
}
private String saveCrashInfo2File(Throwable ex) {
StringBuffer sb=new StringBuffer();
for(Map.Entry<String, String> entry : infos.entrySet()){
String key=entry.getKey();
String value=entry.getValue();
sb.append(key + "=" + value + "\n");
}
Writer writer=new StringWriter();
PrintWriter printWriter=new PrintWriter(writer);
ex.printStackTrace(printWriter);
Throwable cause = ex.getCause();
while(cause!=null){
cause.printStackTrace(printWriter);
cause=cause.getCause();
}
printWriter.close();
String result=writer.toString();
sb.append(result);
long timestamp=System.currentTimeMillis();
String time=formatter.format(new Date());
String fileName = "crash-" + time + "-" + timestamp + ".log";
if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){
String path="/sdcard/crash";
File dir=new File(path);
if(!dir.exists()){
dir.mkdirs();
}
try {
FileOutputStream fos=new FileOutputStream(path + fileName);
fos.write(sb.toString().getBytes());
} catch (FileNotFoundException e) {
Log.e(TAG, "an error occured while writing file...", e);
} catch (IOException e) {
Log.e(TAG, "an error occured while writing file...", e);
}
}
return null;
}
private void collectDeviceInfo(Context ctx) {
PackageManager pm=ctx.getPackageManager();
PackageInfo pi;
try {
pi = pm.getPackageInfo(ctx.getPackageCodePath(),PackageManager.GET_ACTIVITIES);
if(pi!=null){
String versionName=pi.versionName==null?"null":pi.versionName;
String versionCode=pi.versionCode+"";
infos.put("versionName", versionName);
infos.put("versionCode", versionCode);
}
} catch (PackageManager.NameNotFoundException e) {
Log.e(TAG, "an error occured when collect package info", e);
}
Field[] fields= Build.class.getDeclaredFields();
for(Field field:fields){
try {
field.setAccessible(true);
infos.put(field.getName(), field.get(null).toString());
Log.d(TAG, field.getName() + " : " + field.get(null));
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
Log.e(TAG, "an error occured when collect crash info", e);
}
}
}
}
抓取到的错误日志保存在/sdcard/crash目录下,有助于快速解决问题
其实碍于公司代码不方便粘贴出来,还有很多问题没有写出来,如果你们也遇到什么问题欢迎在评论区讨论,共勉
|