IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Android app crash的问题排查思路与反思 -> 正文阅读

[移动开发]Android app crash的问题排查思路与反思

前言

做安卓开发总是伴随着不同的设备适配和不同的设备性能,同一套代码可能在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;


/**
 * @ProjectName : ....
 * @Author : yifeng_zeng
 * @Time : 2022/2/28 10:27
 * @Description : app异常捕获
 */
public class CrashHandler implements Thread.UncaughtExceptionHandler {
    private FullscreenApplication softApp;
    public static final String TAG="CrashHandler";
    //系统默认的UncaughtException处理类
    private Thread.UncaughtExceptionHandler mDefaultHandler;
    //程序的Context对象
    private Context mContext;
    //用来存储设备信息和异常信息
    private Map<String,String> infos=new HashMap<String,String>();
    //用于格式化日期,作为日志文件名的一部分
    private DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");

    /**保证只有一个CrashHandler实例**/
    public  CrashHandler(FullscreenApplication app){
        softApp=app;
    }

    /**
     * 初始化
     */
    public void init(Context context){
        mContext=context;
        //获取系统默认的UnCaughtException处理器
        mDefaultHandler=Thread.getDefaultUncaughtExceptionHandler();
        //设置该CrashHandler为程序默认处理器
        Thread.setDefaultUncaughtExceptionHandler(this);;
    }


    @Override
    public void uncaughtException(@NonNull Thread t, @NonNull Throwable e) {
        Log.e(TAG, "error : ", e);
        //收集设备参数信息
        collectDeviceInfo(mContext);
        //保存日志文件
        saveCrashInfo2File(e);
        //关闭app
        Toast.makeText(Client.i().getActivity(),"app程序异常,即将关闭",Toast.LENGTH_LONG).show();
        
        //这里重启或关闭app
    }

    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) {
                // TODO Auto-generated catch block
                Log.e(TAG, "an error occured while writing file...", e);
            } catch (IOException e) {
                // TODO Auto-generated catch block
                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) {
            // TODO Auto-generated catch block
            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) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (IllegalArgumentException e) {
                // TODO Auto-generated catch block
                Log.e(TAG, "an error occured when collect crash info", e);
            }
        }
    }




}

抓取到的错误日志保存在/sdcard/crash目录下,有助于快速解决问题

其实碍于公司代码不方便粘贴出来,还有很多问题没有写出来,如果你们也遇到什么问题欢迎在评论区讨论,共勉

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2022-03-03 16:26:11  更:2022-03-03 16:29:47 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 16:21:37-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码