Android 性能优化12 --- crash优化02(cra
2022-06-28 本文已影响0人
沪漂意哥哥
一. 崩溃数据采集方案
1.崩溃需要采集哪些信息?
- 基本信息
进程(前台进程还是后台进程),线程(是否是UI线程),崩溃堆栈,崩溃堆栈类型 - 系统信息
机型,系统,厂商,CPU,LINUX版本 - 内存信息
系统剩余内存,虚拟内存,应用使用内存,线程数 - 应用信息
崩溃场景,关键操作,其它自定义信息路径,APP版本。
2.如何采集?
- ProcessUtils
/**
* 进程工具类
*/
public final class ProcessUtils {
private static String currentProcessName;
/**
* 获取当前进程名
* 我们优先通过 Application.getProcessName() 方法获取进程名。
* 如果获取失败,我们再反射ActivityThread.currentProcessName()获取进程名
* 如果失败,我们才通过常规方法ActivityManager来获取进程名
* @return 当前进程名
*/
@Nullable
public static String getCurrentProcessName(@NonNull Context context) {
if (!TextUtils.isEmpty(currentProcessName)) {
return currentProcessName;
}
//1)通过Application的API获取当前进程名
currentProcessName = getCurrentProcessNameByApplication();
if (!TextUtils.isEmpty(currentProcessName)) {
return currentProcessName;
}
//2)通过反射ActivityThread获取当前进程名
currentProcessName = getCurrentProcessNameByActivityThread();
if (!TextUtils.isEmpty(currentProcessName)) {
return currentProcessName;
}
//3)通过ActivityManager获取当前进程名
currentProcessName = getCurrentProcessNameByActivityManager(context);
return currentProcessName;
}
/**
* 通过Application新的API获取进程名,无需反射,无需IPC,效率最高。
*/
public static String getCurrentProcessNameByApplication() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
//Application.getProcessName()方法直接返回当前进程名。
//这个方法只有在android9【也就是aip28】之后的系统才能调用
return Application.getProcessName();
}
return null;
}
/**
* 通过反射ActivityThread获取进程名,避免了ipc
*/
public static String getCurrentProcessNameByActivityThread() {
String processName = null;
try {
//ActivityThread.currentProcessName()方法居然是public static的
@SuppressLint("PrivateApi")
final Method declaredMethod = Class.forName("android.app.ActivityThread",
false, Application.class.getClassLoader())
.getDeclaredMethod("currentProcessName", (Class<?>[]) new Class[0]);
declaredMethod.setAccessible(true);
final Object invoke = declaredMethod.invoke(null, new Object[0]);
if (invoke instanceof String) {
processName = (String) invoke;
}
} catch (Throwable e) {
e.printStackTrace();
}
return processName;
}
/**
* 通过ActivityManager 获取进程名,需要IPC通信
* 1。ActivityManager.getRunningAppProcesses() 方法需要跨进程通信,效率不高。
* 需要 和 系统进程的 ActivityManagerService 通信。必然会导致该方法调用耗时。
* 2。拿到RunningAppProcessInfo的列表之后,还需要遍历一遍找到与当前进程的信息。
* 3。ActivityManager.getRunningAppProcesses() 有可能调用失败,返回null,也可能 AIDL 调用失败。调用失败是极低的概率。
*/
public static String getCurrentProcessNameByActivityManager(@NonNull Context context) {
if (context == null) {
return null;
}
//指的是Process的id。每个进程都有一个独立的id,可以通过pid来区分不同的进程。
int pid = android.os.Process.myPid( );
ActivityManager am = (ActivityManager) context.getApplicationContext()
.getSystemService(Context.ACTIVITY_SERVICE);
if (am != null) {
//获取当前正在运行的进程
List<ActivityManager.RunningAppProcessInfo> runningAppList = am.getRunningAppProcesses();
if (runningAppList != null) {
for (ActivityManager.RunningAppProcessInfo processInfo : runningAppList) {
//相应的RunningServiceInfo的pid
if (processInfo.pid == pid) {
return processInfo.processName;
}
}
}
}
return null;
}
/**
* 获取前台线程包名
* <p>当不是查看当前 App,且 SDK 大于 21 时,
* 需添加权限
* {@code <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />}</p>
*
* @return 前台应用包名
*/
public static String getForegroundProcessName(Context context) {
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
//noinspection ConstantConditions
List<ActivityManager.RunningAppProcessInfo> pInfo = am.getRunningAppProcesses();
if (pInfo != null && pInfo.size() > 0) {
for (ActivityManager.RunningAppProcessInfo aInfo : pInfo) {
if (aInfo.importance
== ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
return aInfo.processName;
}
}
}
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) {
PackageManager pm = context.getPackageManager();
Intent intent = new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS);
List<ResolveInfo> list =
pm.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
Log.i("ProcessUtils", list.toString());
if (list.size() <= 0) {
Log.i("ProcessUtils",
"getForegroundProcessName: noun of access to usage information.");
return "";
}
try {// Access to usage information.
ApplicationInfo info =
pm.getApplicationInfo(context.getPackageName(), 0);
AppOpsManager aom =
(AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
if (aom.checkOpNoThrow(AppOpsManager.OPSTR_GET_USAGE_STATS,
info.uid,
info.packageName) != AppOpsManager.MODE_ALLOWED) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
if (aom.checkOpNoThrow(AppOpsManager.OPSTR_GET_USAGE_STATS,
info.uid,
info.packageName) != AppOpsManager.MODE_ALLOWED) {
Log.i("ProcessUtils",
"getForegroundProcessName: refuse to device usage stats.");
return "";
}
UsageStatsManager usageStatsManager = (UsageStatsManager) context
.getSystemService(Context.USAGE_STATS_SERVICE);
List<UsageStats> usageStatsList = null;
if (usageStatsManager != null) {
long endTime = System.currentTimeMillis();
long beginTime = endTime - 86400000 * 7;
usageStatsList = usageStatsManager
.queryUsageStats(UsageStatsManager.INTERVAL_BEST,
beginTime, endTime);
}
if (usageStatsList == null || usageStatsList.isEmpty()) return "";
UsageStats recentStats = null;
for (UsageStats usageStats : usageStatsList) {
if (recentStats == null
|| usageStats.getLastTimeUsed() > recentStats.getLastTimeUsed()) {
recentStats = usageStats;
}
}
return recentStats == null ? null : recentStats.getPackageName();
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
}
return "";
}
/**
* 获取后台服务进程
* <p>需添加权限
* {@code <uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES" />}</p>
*
* @return 后台服务进程
*/
@RequiresPermission(KILL_BACKGROUND_PROCESSES)
public static Set<String> getAllBackgroundProcesses(Context context) {
ActivityManager am =
(ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningAppProcessInfo> info = am.getRunningAppProcesses();
Set<String> set = new HashSet<>();
if (info != null) {
for (ActivityManager.RunningAppProcessInfo aInfo : info) {
Collections.addAll(set, aInfo.pkgList);
}
}
return set;
}
/**
* 杀死所有的后台服务进程
* <p>需添加权限
* {@code <uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES" />}</p>
*
* @return 被暂时杀死的服务集合
*/
@RequiresPermission(KILL_BACKGROUND_PROCESSES)
public static Set<String> killAllBackgroundProcesses(Context context) {
ActivityManager am =
(ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningAppProcessInfo> info = am.getRunningAppProcesses();
Set<String> set = new HashSet<>();
if (info == null) return set;
for (ActivityManager.RunningAppProcessInfo aInfo : info) {
for (String pkg : aInfo.pkgList) {
am.killBackgroundProcesses(pkg);
set.add(pkg);
}
}
info = am.getRunningAppProcesses();
for (ActivityManager.RunningAppProcessInfo aInfo : info) {
for (String pkg : aInfo.pkgList) {
set.remove(pkg);
}
}
return set;
}
/**
* 杀死后台服务进程
* <p>需添加权限
* {@code <uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES" />}</p>
*
* @param packageName The name of the package.
* @return {@code true}: 杀死成功<br>{@code false}: 杀死失败
*/
@SuppressLint("MissingPermission")
public static boolean killBackgroundProcesses(Context context,@NonNull final String packageName) {
ActivityManager am =
(ActivityManager) context.getApplicationContext().getSystemService(Context.ACTIVITY_SERVICE);
if (am == null) return false;
List<ActivityManager.RunningAppProcessInfo> info = am.getRunningAppProcesses();
if (info == null || info.size() == 0) return true;
for (ActivityManager.RunningAppProcessInfo aInfo : info) {
if (Arrays.asList(aInfo.pkgList).contains(packageName)) {
am.killBackgroundProcesses(packageName);
}
}
info = am.getRunningAppProcesses();
if (info == null || info.size() == 0) return true;
for (ActivityManager.RunningAppProcessInfo aInfo : info) {
if (Arrays.asList(aInfo.pkgList).contains(packageName)) {
return false;
}
}
return true;
}
/**
* Return whether app running in the main process.
*
* @return {@code true}: yes<br>{@code false}: no
*/
public static boolean isMainProcess(Context context) {
return context.getPackageName().equals(getCurrentProcessName(context));
}
public static boolean isRunningInForeground(Context context) {
return Build.VERSION.SDK_INT >= 21 ? LollipopRunningProcessCompat.isRunningInForeground(context) : RunningProcessCompat.isRunningInForeground(context);
}
private static final class LollipopRunningProcessCompat extends RunningProcessCompat {
private LollipopRunningProcessCompat() {
}
public static boolean isRunningInForeground(Context context) {
try {
Field processStateField = ActivityManager.RunningAppProcessInfo.class.getDeclaredField("processState");
ActivityManager am = (ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningAppProcessInfo> processInfos = am.getRunningAppProcesses();
if (null == processInfos || processInfos.isEmpty()) {
return false;
}
String packageName = context.getPackageName();
Iterator var5 = processInfos.iterator();
while(var5.hasNext()) {
ActivityManager.RunningAppProcessInfo rapi = (ActivityManager.RunningAppProcessInfo)var5.next();
if (rapi.importance == 100 && rapi.importanceReasonCode == 0) {
try {
Integer processState = processStateField.getInt(rapi);
if (processState != null && processState == 2 && rapi.pkgList != null && rapi.pkgList.length > 0) {
return rapi.pkgList[0].equals(packageName);
}
} catch (Exception var8) {
}
}
}
} catch (Exception var9) {
}
return false;
}
}
private static class RunningProcessCompat {
private RunningProcessCompat() {
}
public static boolean isRunningInForeground(Context context) {
try {
ActivityManager am = (ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningTaskInfo> tasks = am.getRunningTasks(1);
return null != tasks && !tasks.isEmpty() ?
((ActivityManager.RunningTaskInfo)tasks.get(0)).topActivity.getPackageName().equals(context.getPackageName()) : false;
} catch (Exception var3) {
return false;
}
}
}
}
- MemoryUtils
/**
* 内存工具类
*/
public final class MemoryUtils {
/**
* 获取当前应用进程的pid
*/
public static int getCurrentPid() {
return android.os.Process.myPid();
}
/**
* 获取总体内存使用情况
*/
public static void getMemoryInfo(final Context context, final OnGetMemoryInfoCallback onGetMemoryInfoCallback) {
new Thread(new Runnable() {
@Override
public void run() {
final String pkgName = context.getPackageName();
final int pid = android.os.Process.myPid();
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
final ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo();
am.getMemoryInfo(mi);
//1. ram
final RamMemoryInfo ramMemoryInfo = new RamMemoryInfo();
ramMemoryInfo.availMem = mi.availMem;
ramMemoryInfo.isLowMemory = mi.lowMemory;
ramMemoryInfo.lowMemThreshold = mi.threshold ;
ramMemoryInfo.totalMem = MemoryUtils.getRamTotalMemSync(context);
//2. pss
final PssInfo pssInfo = MemoryUtils.getAppPssInfo(context, pid);
//3. dalvik heap
final DalvikHeapMem dalvikHeapMem = MemoryUtils.getAppDalvikHeapMem();
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
onGetMemoryInfoCallback.onGetMemoryInfo(pkgName, pid, ramMemoryInfo, pssInfo, dalvikHeapMem);
}
});
}
}).start();
}
/**
* 获取手机RAM的存储情况
*/
public static void getSystemRam(final Context context, final OnGetRamMemoryInfoCallback onGetRamMemoryInfoCallback) {
getRamTotalMem(context, new OnGetRamTotalMemCallback() {
@Override
public void onGetRamTotalMem(long totalMem) {
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
final ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo();
am.getMemoryInfo(mi);
RamMemoryInfo ramMemoryInfo = new RamMemoryInfo();
ramMemoryInfo.availMem = mi.availMem;
ramMemoryInfo.isLowMemory = mi.lowMemory;
ramMemoryInfo.lowMemThreshold = mi.threshold;
ramMemoryInfo.totalMem = totalMem;
onGetRamMemoryInfoCallback.onGetRamMemoryInfo(ramMemoryInfo);
}
});
}
/**
* 获取应用实际占用内存
*
* @return 应用pss信息KB
*/
public static PssInfo getAppPssInfo(Context context, int pid) {
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
Debug.MemoryInfo memoryInfo = am.getProcessMemoryInfo(new int[]{pid})[0];
PssInfo pssInfo = new PssInfo();
//返回总的PSS内存使用量(以kB为单位)
pssInfo.totalPss = memoryInfo.getTotalPss();
//dalvik堆的比例设置大小
pssInfo.dalvikPss = memoryInfo.dalvikPss;
//本机堆的比例设置大小
pssInfo.nativePss = memoryInfo.nativePss;
//比例设置大小为其他所有
pssInfo.otherPss = memoryInfo.otherPss;
return pssInfo;
}
/**
* 获取应用dalvik内存信息
*
* @return dalvik堆内存KB
*/
public static DalvikHeapMem getAppDalvikHeapMem() {
Runtime runtime = Runtime.getRuntime();
DalvikHeapMem dalvikHeapMem = new DalvikHeapMem();
//空闲内存
dalvikHeapMem.freeMem = runtime.freeMemory();
//最大内存
dalvikHeapMem.maxMem = Runtime.getRuntime().maxMemory();
//已用内存
dalvikHeapMem.allocated = (Runtime.getRuntime().totalMemory() - runtime.freeMemory());
return dalvikHeapMem;
}
/**
* 获取应用能够获取的max dalvik堆内存大小
* 和Runtime.getRuntime().maxMemory()一样
*
* @return 单位M
*/
public static long getAppTotalDalvikHeapSize(Context context) {
ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
return manager.getMemoryClass();
}
/**
* Dalvik堆内存,只要App用到的内存都算(包括共享内存)
*/
public static class DalvikHeapMem {
public long freeMem;
public long maxMem;
public long allocated;
}
/**
* 应用实际占用内存(共享按比例分配)
*/
public static class PssInfo {
public int totalPss;
public int dalvikPss;
public int nativePss;
public int otherPss;
}
/**
* 手机RAM内存信息
* 物理内存信息
*/
public static class RamMemoryInfo {
//可用RAM
public long availMem;
//手机总RAM
public long totalMem;
//内存占用满的阀值,超过即认为低内存运行状态,可能会Kill process
public long lowMemThreshold;
//是否低内存状态运行
public boolean isLowMemory;
}
/**
* 内存相关的所有数据
*/
public interface OnGetMemoryInfoCallback {
void onGetMemoryInfo(String pkgName, int pid, RamMemoryInfo ramMemoryInfo, PssInfo pssInfo, DalvikHeapMem dalvikHeapMem);
}
public interface OnGetRamMemoryInfoCallback {
void onGetRamMemoryInfo(RamMemoryInfo ramMemoryInfo);
}
private interface OnGetRamTotalMemCallback {
//手机总RAM容量/KB
void onGetRamTotalMem(long totalMem);
}
/**
* 获取手机RAM容量/手机实际内存
* 单位
*/
private static void getRamTotalMem(final Context context, final OnGetRamTotalMemCallback onGetRamTotalMemCallback) {
new Thread(new Runnable() {
@Override
public void run() {
final long totalRam = getRamTotalMemSync(context);
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
onGetRamTotalMemCallback.onGetRamTotalMem(totalRam);
}
});
}
}).start();
}
/**
* 同步获取系统的总ram大小
*/
private static long getRamTotalMemSync(Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo();
am.getMemoryInfo(mi);
return mi.totalMem;
} else if (sTotalMem.get() > 0L) {//如果已经从文件获取过值,则不需要再次获取
return sTotalMem.get();
} else {
final long tm = getRamTotalMemByFile();
sTotalMem.set(tm);
return tm;
}
}
private static AtomicLong sTotalMem = new AtomicLong(0L);
/**
* 获取手机的RAM容量,其实和activityManager.getMemoryInfo(mi).totalMem效果一样,
* 也就是说,在API16以上使用系统API获取,低版本采用这个文件读取方式
*
* @return 容量KB
*/
private static long getRamTotalMemByFile() {
final String dir = "/proc/meminfo";
try {
FileReader fr = new FileReader(dir);
BufferedReader br = new BufferedReader(fr, 2048);
String memoryLine = br.readLine();
String subMemoryLine = memoryLine.substring(memoryLine.indexOf("MemTotal:"));
br.close();
long totalMemorySize = Integer.parseInt(subMemoryLine.replaceAll("\\D+", ""));
return totalMemorySize;
} catch (IOException e) {
e.printStackTrace();
}
return 0L;
}
/**
* 格式化单位
* @param size
* @return
*/
public static String getFormatSize(double size) {
double kiloByte = size / 1024;
if (kiloByte < 1) {
return size + "Byte";
}
double megaByte = kiloByte / 1024;
if (megaByte < 1) {
BigDecimal result1 = new BigDecimal(Double.toString(kiloByte));
return result1.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "KB";
}
double gigaByte = megaByte / 1024;
if (gigaByte < 1) {
BigDecimal result2 = new BigDecimal(Double.toString(megaByte));
return result2.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "MB";
}
double teraBytes = gigaByte / 1024;
if (teraBytes < 1) {
BigDecimal result3 = new BigDecimal(Double.toString(gigaByte));
return result3.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "GB";
}
BigDecimal result4 = new BigDecimal(teraBytes);
return result4.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "TB";
}
}
- NetDeviceUtils
/**
* 设备信息采集工具
*/
public final class NetDeviceUtils {
private static final String LINE_SEP = System.getProperty("line.separator");
private NetDeviceUtils() {
throw new UnsupportedOperationException("u can't instantiate me...");
}
/**
* 判断设备是否 root
*
* @return the boolean{@code true}: 是<br>{@code false}: 否
*/
public static boolean isDeviceRooted() {
try {
String su = "su";
String[] locations = {"/system/bin/", "/system/xbin/", "/sbin/", "/system/sd/xbin/",
"/system/bin/failsafe/", "/data/local/xbin/", "/data/local/bin/", "/data/local/"};
for (String location : locations) {
if (new File(location + su).exists()) {
return true;
}
}
return false;
} catch (Exception e){
return false;
}
}
/**
* 获取设备系统版本号
*
* @return 设备系统版本号
*/
public static String getSDKVersionName() {
return Build.VERSION.RELEASE;
}
/**
* 获取设备系统版本码
*
* @return 设备系统版本码
*/
public static int getSDKVersionCode() {
return Build.VERSION.SDK_INT;
}
/**
* 获取设备 AndroidID
*
* @return AndroidID
*/
@SuppressLint("HardwareIds")
public static String getAndroidID(Context context) {
return Settings.Secure.getString(
context.getApplicationContext().getContentResolver(),
Settings.Secure.ANDROID_ID
);
}
/**
* 获取设备厂商
* <p>如 Xiaomi</p>
*
* @return 设备厂商
*/
public static String getManufacturer() {
return Build.MANUFACTURER;
}
/**
* 获取设备的品牌
* <p>如 Xiaomi</p>
*
* @return 设备的品牌
*/
public static String getBrand() {
return Build.BRAND;
}
/**
* 获取设备版本号
*
* @return 设备版本号
*/
public static String getId() {
return Build.ID;
}
/**
* 获取CPU的类型
*
* @return CPU的类型
*/
public static String getCpuType() {
return Build.CPU_ABI;
}
/**
* 获取设备型号
* <p>如 MI2SC</p>
*
* @return 设备型号
*/
public static String getModel() {
String model = Build.MODEL;
if (model != null) {
model = model.trim().replaceAll("\\s*", "");
} else {
model = "";
}
return model;
}
/**
* 获取wifi的强弱
* @param context 上下文
* @return
*/
public static String getWifiState(Context context){
if (isWifiConnect(context)) {
WifiManager mWifiManager = (WifiManager) context.getApplicationContext()
.getSystemService(Context.WIFI_SERVICE);
WifiInfo mWifiInfo = null;
if (mWifiManager != null) {
mWifiInfo = mWifiManager.getConnectionInfo();
int wifi = mWifiInfo.getRssi();//获取wifi信号强度
if (wifi > -50 && wifi < 0) {//最强
return "最强";
} else if (wifi > -70 && wifi < -50) {//较强
return "较强";
} else if (wifi > -80 && wifi < -70) {//较弱
return "较弱";
} else if (wifi > -100 && wifi < -80) {//微弱
return "微弱";
} else {
return "微弱";
}
}
}
return "无wifi连接";
}
public static boolean isWifiConnect(Context context) {
ConnectivityManager connManager = (ConnectivityManager)
context.getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo mWifiInfo = null;
if (connManager != null) {
mWifiInfo = connManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
return mWifiInfo.isConnected();
}
return false;
}
/**
* 通过域名获取真实的ip地址 (此方法需要在线程中调用)
* @param domain host
* @return
*/
public static String getHostIP(String domain) {
String ipAddress = "";
InetAddress iAddress = null;
try {
iAddress = InetAddress.getByName(domain);
} catch (UnknownHostException e) {
e.printStackTrace();
}
if (iAddress == null)
NetLogUtils.i("xxx", "iAddress ==null");
else {
ipAddress = iAddress.getHostAddress();
}
return ipAddress;
}
/**
* 通过域名获取真实的ip地址 (此方法需要在线程中调用)
* @param domain host
* @return
*/
public static String getHostName(String domain) {
String ipAddress = "";
InetAddress iAddress = null;
try {
iAddress = InetAddress.getByName(domain);
} catch (UnknownHostException e) {
e.printStackTrace();
}
if (iAddress == null)
NetLogUtils.i("xxx", "iAddress ==null");
else {
ipAddress = iAddress.getHostName();
}
return ipAddress;
}
/**
* 获取wifi的名称
* @param context 上下文
* @return
*/
public static String getWifiName(Context context){
WifiManager wifiManager = (WifiManager) context.getApplicationContext()
.getSystemService(Context.WIFI_SERVICE);
WifiInfo wifiInfo = null;
if (wifiManager != null) {
wifiInfo = wifiManager.getConnectionInfo();
NetLogUtils.i("getWifiName--------",wifiInfo.toString());
NetLogUtils.i("getWifiName--------",wifiInfo.getBSSID());
String ssid = wifiInfo.getSSID();
return ssid;
}
return "无网络";
}
/**
* 获取wifi的ip
* @param context 上下文
* @return
*/
public static int getWifiIp(Context context){
WifiManager wifiManager = (WifiManager) context.getApplicationContext()
.getSystemService(Context.WIFI_SERVICE);
WifiInfo wifiInfo = null;
if (wifiManager != null) {
wifiInfo = wifiManager.getConnectionInfo();
NetLogUtils.i("getWifiIp--------",wifiInfo.toString());
NetLogUtils.i("getWifiIp--------",wifiInfo.getBSSID());
int ipAddress = wifiInfo.getIpAddress();
return ipAddress;
}
return -1;
}
/**
* 获取wifi的信息
* @param context 上下文
* @return
*/
public static WifiInfo getWifiInfo(Context context){
WifiManager wifiManager = (WifiManager) context.getApplicationContext()
.getSystemService(Context.WIFI_SERVICE);
WifiInfo wifiInfo = null;
if (wifiManager != null) {
wifiInfo = wifiManager.getConnectionInfo();
return wifiInfo;
}
return null;
}
/**
* 获取dhcp信息
* @param context 上下文
* @return
*/
public static DhcpInfo getDhcpInfo(Context context){
WifiManager wifiManager = (WifiManager) context.getApplicationContext()
.getSystemService(Context.WIFI_SERVICE);
DhcpInfo dhcpInfo = null;
if (wifiManager != null) {
dhcpInfo = wifiManager.getDhcpInfo();
return dhcpInfo;
}
return null;
}
public static String intToIp(int paramInt) {
return (paramInt & 0xFF) + "." + (0xFF & paramInt >> 8) + "." + (0xFF & paramInt >> 16) + "."
+ (0xFF & paramInt >> 24);
}
public static String getSDCardSpace(Context context) {
try {
String free = getSDAvailableSize(context);
String total = getSDTotalSize(context);
return free + "/" + total;
} catch (Exception e) {
return "-/-";
}
}
/**
* 获得SD卡总大小
*
* @return
*/
private static String getSDTotalSize(Context context) {
File path = Environment.getExternalStorageDirectory();
StatFs stat = new StatFs(path.getPath());
long blockSize = stat.getBlockSize();
long totalBlocks = stat.getBlockCount();
return Formatter.formatFileSize(context, blockSize * totalBlocks);
}
/**
* 获得sd卡剩余容量,即可用大小
*
* @return
*/
private static String getSDAvailableSize(Context context) {
File path = Environment.getExternalStorageDirectory();
StatFs stat = new StatFs(path.getPath());
long blockSize = stat.getBlockSize();
long availableBlocks = stat.getAvailableBlocks();
return Formatter.formatFileSize(context, blockSize * availableBlocks);
}
public static String getRomSpace(Context context) {
try {
String free = getRomAvailableSize(context);
String total = getRomTotalSize(context);
return free + "/" + total;
} catch (Exception e) {
return "-/-";
}
}
/**
* 获得机身可用内存
*
* @return
*/
private static String getRomAvailableSize(Context context) {
File path = Environment.getDataDirectory();
StatFs stat = new StatFs(path.getPath());
long blockSize = stat.getBlockSize();
long availableBlocks = stat.getAvailableBlocks();
return Formatter.formatFileSize(context, blockSize * availableBlocks);
}
/**
* 获得机身内存总大小
*
* @return
*/
private static String getRomTotalSize(Context context) {
File path = Environment.getDataDirectory();
StatFs stat = new StatFs(path.getPath());
long blockSize = stat.getBlockSize();
long totalBlocks = stat.getBlockCount();
return Formatter.formatFileSize(context, blockSize * totalBlocks);
}
/**
* 手机总内存
* @param context
* @return 手机总内存(兆)
*/
public static long getTotalMemory(Context context) {
String str1 = "/proc/meminfo";// 系统内存信息文件
String str2;
String[] arrayOfString;
long initial_memory = 0;
try {
FileReader localFileReader = new FileReader(str1);
BufferedReader localBufferedReader = new BufferedReader(
localFileReader, 8192);
str2 = localBufferedReader.readLine();// 读取meminfo第一行,系统总内存大小
if (!TextUtils.isEmpty(str2)) {
arrayOfString = str2.split("\\s+");
// 获得系统总内存,单位是KB,乘以1024转换为Byte
initial_memory = Integer.valueOf(arrayOfString[1]).intValue() / 1024;
}
localBufferedReader.close();
} catch (IOException e) {
e.printStackTrace();
}
return initial_memory;// Byte转换为KB或者MB,内存大小规格化
}
/**
* 手机当前可用内存
* @param context
* @return 手机当前可用内存(兆)
*/
public static long getAvailMemory(Context context) {// 获取android当前可用内存大小
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo();
if (am != null) {
am.getMemoryInfo(mi);
}
return mi.availMem / 1024 / 1024;
}
public static int getWidthPixels(Context context) {
DisplayMetrics metrics = new DisplayMetrics();
WindowManager windowManager = (WindowManager) context.getApplicationContext()
.getSystemService(Context.WINDOW_SERVICE);
if (windowManager == null) {
return 0;
}
windowManager.getDefaultDisplay().getMetrics(metrics);
return metrics.widthPixels;
}
public static int getRealHeightPixels(Context context) {
WindowManager windowManager = (WindowManager) context.getApplicationContext()
.getSystemService(Context.WINDOW_SERVICE);
int height = 0;
Display display = null;
if (windowManager != null) {
display = windowManager.getDefaultDisplay();
}
DisplayMetrics dm = new DisplayMetrics();
Class c;
try {
c = Class.forName("android.view.Display");
Method method = c.getMethod("getRealMetrics", DisplayMetrics.class);
method.invoke(display, dm);
height = dm.heightPixels;
} catch (Exception e) {
NetLogUtils.d(e.toString());
}
return height;
}
/**
* 获取屏幕尺寸
* @param context
* @return
*/
public static double getScreenInch(Activity context) {
double inch = 0;
try {
int realWidth = 0, realHeight = 0;
Display display = context.getWindowManager().getDefaultDisplay();
DisplayMetrics metrics = new DisplayMetrics();
display.getMetrics(metrics);
if (Build.VERSION.SDK_INT >= 17) {
Point size = new Point();
display.getRealSize(size);
realWidth = size.x;
realHeight = size.y;
} else if (Build.VERSION.SDK_INT < 17
&& Build.VERSION.SDK_INT >= 14) {
Method mGetRawH = Display.class.getMethod("getRawHeight");
Method mGetRawW = Display.class.getMethod("getRawWidth");
realWidth = (Integer) mGetRawW.invoke(display);
realHeight = (Integer) mGetRawH.invoke(display);
} else {
realWidth = metrics.widthPixels;
realHeight = metrics.heightPixels;
}
inch = formatDouble(Math.sqrt((realWidth / metrics.xdpi) * (realWidth / metrics.xdpi)
+ (realHeight / metrics.ydpi) * (realHeight / metrics.ydpi)), 1);
} catch (Exception e) {
e.printStackTrace();
}
return inch;
}
/**
* Double类型保留指定位数的小数,返回double类型(四舍五入)
* newScale 为指定的位数
*/
private static double formatDouble(double d, int newScale) {
BigDecimal bd = new BigDecimal(d);
return bd.setScale(newScale, BigDecimal.ROUND_HALF_UP).doubleValue();
}
/**
* 获取设备 MAC 地址
* <p>需添加权限 {@code <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />}</p>
* <p>需添加权限 {@code <uses-permission android:name="android.permission.INTERNET" />}</p>
*
* @return MAC 地址
*/
public static String getMacAddress(Context context) {
String macAddress = getMacAddressByWifiInfo(context);
if (!"02:00:00:00:00:00".equals(macAddress)) {
return macAddress;
}
macAddress = getMacAddressByNetworkInterface();
if (!"02:00:00:00:00:00".equals(macAddress)) {
return macAddress;
}
macAddress = getMacAddressByFile();
if (!"02:00:00:00:00:00".equals(macAddress)) {
return macAddress;
}
return "please open wifi";
}
/**
* 获取设备 MAC 地址
* <p>需添加权限 {@code <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />}</p>
*
* @return MAC 地址
*/
@SuppressLint({"HardwareIds", "MissingPermission"})
private static String getMacAddressByWifiInfo(Context context) {
try {
@SuppressLint("WifiManagerLeak")
WifiManager wifi = (WifiManager) context.getApplicationContext().getSystemService(Context.WIFI_SERVICE);
if (wifi != null) {
WifiInfo info = wifi.getConnectionInfo();
if (info != null) return info.getMacAddress();
}
} catch (Exception e) {
e.printStackTrace();
}
return "02:00:00:00:00:00";
}
/**
* 获取设备 MAC 地址
* <p>需添加权限 {@code <uses-permission android:name="android.permission.INTERNET" />}</p>
*
* @return MAC 地址
*/
private static String getMacAddressByNetworkInterface() {
try {
List<NetworkInterface> nis = Collections.list(NetworkInterface.getNetworkInterfaces());
for (NetworkInterface ni : nis) {
if (!ni.getName().equalsIgnoreCase("wlan0")) continue;
byte[] macBytes = ni.getHardwareAddress();
if (macBytes != null && macBytes.length > 0) {
StringBuilder res1 = new StringBuilder();
for (byte b : macBytes) {
res1.append(String.format("%02x:", b));
}
return res1.deleteCharAt(res1.length() - 1).toString();
}
}
} catch (Exception e) {
e.printStackTrace();
}
return "02:00:00:00:00:00";
}
/**
* 获取设备 MAC 地址
*
* @return MAC 地址
*/
private static String getMacAddressByFile() {
CommandResult result = execCmd(new String[]{"getprop wifi.interface"}, false,true);
if (result.result == 0) {
String name = result.successMsg;
if (name != null) {
result = execCmd(new String[]{"cat /sys/class/net/" + name + "/address"}, false,true);
if (result.result == 0) {
if (result.successMsg != null) {
return result.successMsg;
}
}
}
}
return "02:00:00:00:00:00";
}
/**
* 是否是在 root 下执行命令
*
* @param commands 命令数组
* @param isRoot 是否需要 root 权限执行
* @param isNeedResultMsg 是否需要结果消息
* @return CommandResult
*/
public static CommandResult execCmd(final String[] commands,
final boolean isRoot,
final boolean isNeedResultMsg) {
int result = -1;
if (commands == null || commands.length == 0) {
return new CommandResult(result, null, null);
}
Process process = null;
BufferedReader successResult = null;
BufferedReader errorResult = null;
StringBuilder successMsg = null;
StringBuilder errorMsg = null;
DataOutputStream os = null;
try {
process = Runtime.getRuntime().exec(isRoot ? "su" : "sh");
os = new DataOutputStream(process.getOutputStream());
for (String command : commands) {
if (command == null) continue;
os.write(command.getBytes());
os.writeBytes(LINE_SEP);
os.flush();
}
os.writeBytes("exit" + LINE_SEP);
os.flush();
result = process.waitFor();
if (isNeedResultMsg) {
successMsg = new StringBuilder();
errorMsg = new StringBuilder();
successResult = new BufferedReader(new InputStreamReader(process.getInputStream(),
"UTF-8"));
errorResult = new BufferedReader(new InputStreamReader(process.getErrorStream(),
"UTF-8"));
String line;
if ((line = successResult.readLine()) != null) {
successMsg.append(line);
while ((line = successResult.readLine()) != null) {
successMsg.append(LINE_SEP).append(line);
}
}
if ((line = errorResult.readLine()) != null) {
errorMsg.append(line);
while ((line = errorResult.readLine()) != null) {
errorMsg.append(LINE_SEP).append(line);
}
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
closeIO(os, successResult, errorResult);
if (process != null) {
process.destroy();
}
}
return new CommandResult(
result,
successMsg == null ? null : successMsg.toString(),
errorMsg == null ? null : errorMsg.toString()
);
}
/**
* 关闭 IO
*
* @param closeables closeables
*/
private static void closeIO(final Closeable... closeables) {
if (closeables == null) return;
for (Closeable closeable : closeables) {
if (closeable != null) {
try {
closeable.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 返回的命令结果
*/
public static class CommandResult {
/**
* 结果码
**/
public int result;
/**
* 成功信息
**/
public String successMsg;
/**
* 错误信息
**/
public String errorMsg;
public CommandResult(final int result, final String successMsg, final String errorMsg) {
this.result = result;
this.successMsg = successMsg;
this.errorMsg = errorMsg;
}
}
}
二.异常栈获取
设置自己的UncaughtExceptionHandler。
4.使用Looper可以拉拦截崩溃和ANR
触发异常捕捉流程,触发kill昂起进程被kill掉 image.png如果主线程Looper结束,其本质上来将就一定会停止。
思考:如何让上面的循环不进行退出,意味发生的下让其规避掉被kill掉的命运?
三. 代码介绍
1.CrashHandler
自定义Thread.uncaughtExceptionHandler,用当前这个Handler嫁接系统的Handler进行处理。
/**
* 异常处理嫁接
*/
public class CrashHandler implements Thread.UncaughtExceptionHandler {
public static final String TAG = "CrashHandler";
/**
* 系统默认的UncaughtException处理类
*/
private Thread.UncaughtExceptionHandler mDefaultHandler;
/**
* 程序的Context对象
*/
private Context mContext;
/**
* CrashHandler实例
*/
private static CrashHandler INSTANCE;
/**
* 监听
*/
private CrashListener listener;
/**
* 是否写崩溃日志到file文件夹,默认开启
*/
private boolean isWriteLog = true;
/**
* 点击按钮异常后设置处理崩溃而是关闭当前activity
*/
private boolean isFinishActivity = true;
/**
* 保证只有一个CrashHandler实例
*/
private CrashHandler() {
}
/**
* 获取CrashHandler实例 ,单例模式
*/
public static CrashHandler getInstance() {
if (INSTANCE == null) {
synchronized (CrashHandler.class) {
if (INSTANCE == null) {
INSTANCE = new CrashHandler();
}
}
}
return INSTANCE;
}
public CrashHandler setWriteLog(boolean writeLog) {
isWriteLog = writeLog;
return this;
}
public CrashHandler setFinishActivity(boolean finishActivity) {
isFinishActivity = finishActivity;
return this;
}
/**
* 初始化,注册Context对象,
* 获取系统默认的UncaughtException处理器,
* 设置该CrashHandler为程序的默认处理器
*
* @param ctx 上下文
*/
public CrashHandler init(Application ctx , CrashListener listener) {
LifecycleCallback.getInstance().init(ctx);
if (isFinishActivity){
CrashHelper.getInstance().install(ctx);
}
mContext = ctx;
this.listener = listener;
//获取系统默认的UncaughtExceptionHandler
mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
ToolLogUtils.d(TAG, "init mDefaultHandler : " + mDefaultHandler);
//打印:init mDefaultHandler : com.android.internal.os.RuntimeInit$KillApplicationHandler@7b5887e
//将当前实例设为系统默认的异常处理器
//设置一个处理者当一个线程突然因为一个未捕获的异常而终止时将自动被调用。
//未捕获的异常处理的控制第一个被当前线程处理,如果该线程没有捕获并处理该异常,其将被线程的ThreadGroup对象处理,最后被默认的未捕获异常处理器处理。
Thread.setDefaultUncaughtExceptionHandler(this);
ToolLogUtils.d(TAG, "init mDefaultHandler : " + Thread.getDefaultUncaughtExceptionHandler());
//打印:init mDefaultHandler : com.yc.toollib.crash.CrashHandler@755b1df
return this;
}
/**
* 当UncaughtException发生时会转入该函数来处理
* 该方法来实现对运行时线程进行异常处理
*/
@Override
public void uncaughtException(@NonNull Thread thread, @NonNull Throwable ex) {
boolean isHandle = handleException(ex);
ToolLogUtils.d(TAG, "uncaughtException--- handleException----"+isHandle);
initCustomBug(ex);
if (mDefaultHandler != null && !isHandle) {
//收集完信息后,交给系统自己处理崩溃
//uncaughtException (Thread t, Throwable e) 是一个抽象方法
//当给定的线程因为发生了未捕获的异常而导致终止时将通过该方法将线程对象和异常对象传递进来。
ToolLogUtils.d(TAG, "uncaughtException--- ex----");
mDefaultHandler.uncaughtException(thread, ex);
} else {
//如果已经崩溃,那么当前没有默认处理器, 自己进行处理,也就是重启
if (mContext instanceof Application){
ToolLogUtils.w(TAG, "handleException--- ex----重启activity-");
if (listener!=null){
listener.againStartApp();
}
}
}
if (isFinishActivity){
CrashHelper.getInstance().setSafe(thread,ex);
}
}
/**
* 初始化
* @param ex
*/
private void initCustomBug(Throwable ex) {
//自定义上传crash,支持开发者上传自己捕获的crash数据
//StatService.recordException(mContext, ex);
if (listener!=null){
//捕获监听中异常,防止外部开发者使用方代码抛出异常时导致的反复调用
try {
listener.recordException(ex);
} catch (Throwable e){
e.printStackTrace();
}
}
}
/**
* 自定义错误处理,收集错误信息
* 发送错误报告等操作均在此完成.
* 开发者可以根据自己的情况来自定义异常处理逻辑
*/
private boolean handleException(Throwable ex) {
if (ex == null) {
ToolLogUtils.w(TAG, "handleException--- ex==null");
return false;
}
//收集crash信息
String msg = ex.getLocalizedMessage();
if (msg == null) {
return false;
}
ToolLogUtils.w(TAG, "handleException--- ex-----"+msg);
ex.printStackTrace();
//收集设备信息
//保存错误报告文件
if (isWriteLog){
CrashFileUtils.saveCrashInfoInFile(mContext,ex);
}
return true;
}
}
2.CrashFileUtils
异常处理保存文件类,主要是拿到异常信息,获取崩溃的内容写入文件。
/**
* 异常处理文件持久化
*/
public final class CrashFileUtils {
/**
* 错误报告文件的扩展名
*/
private static final String CRASH_REPORTER_EXTENSION = ".txt";
/**
* 额外信息写入
*/
private static String headContent;
/**
* 时间转换
*/
private static final SimpleDateFormat dataFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.CHINA);
private static String crashTime;
private static String crashHead;
private static String crashMem;
private static String crashThread;
private static String versionName;
private static String versionCode;
public static void setHeadContent(String headContent) {
CrashFileUtils.headContent = headContent;
}
/**
* 保存错误信息到文件中
* 一个崩溃保存到一个txt文本文件中
* 后期需求:1.过了7天自动清除日志;2.针对文件的大小限制;3.文件写入
* @param context
* @param ex
*/
public static void saveCrashInfoInFile(Context context , Throwable ex){
initCrashHead(context);
initPhoneHead(context);
initThreadHead(context,ex);
dumpExceptionToFile(context,ex);
//saveCrashInfoToFile(context,ex);
}
private static void initCrashHead(Context context) {
//崩溃时间
crashTime = dataFormat.format(new Date(System.currentTimeMillis()));
//版本信息
try {
PackageManager pm = context.getPackageManager();
PackageInfo pi = pm.getPackageInfo(context.getPackageName(),
PackageManager.GET_CONFIGURATIONS);
if (pi != null) {
versionName = pi.versionName;
versionCode = String.valueOf(pi.versionCode);
}
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
//组合Android相关信息
StringBuilder sb = new StringBuilder();
sb.append("\n软件App的Id:").append(BuildConfig.APPLICATION_ID);
sb.append("\n是否是DEBUG版本:").append(BuildConfig.BUILD_TYPE);
sb.append("\n崩溃的时间:").append(crashTime);
sb.append("\n是否root:").append(NetDeviceUtils.isDeviceRooted());
sb.append("\n系统硬件商:").append(NetDeviceUtils.getManufacturer());
sb.append("\n设备的品牌:").append(NetDeviceUtils.getBrand());
sb.append("\n手机的型号:").append(NetDeviceUtils.getModel());
sb.append("\n设备版本号:").append(NetDeviceUtils.getId());
sb.append("\nCPU的类型:").append(NetDeviceUtils.getCpuType());
sb.append("\n系统的版本:").append(NetDeviceUtils.getSDKVersionName());
sb.append("\n系统版本值:").append(NetDeviceUtils.getSDKVersionCode());
sb.append("\n当前的版本:").append(versionName).append("—").append(versionCode);
sb.append("\n\n");
crashHead = sb.toString();
}
private static void initPhoneHead(Context context) {
StringBuilder sb = new StringBuilder();
sb.append("手机内存分析:");
final int pid = MemoryUtils.getCurrentPid();
MemoryUtils.PssInfo pssInfo = MemoryUtils.getAppPssInfo(context, pid);
sb.append("\ndalvik堆大小:").append(MemoryUtils.getFormatSize(pssInfo.dalvikPss));
sb.append("\n手机堆大小:").append(MemoryUtils.getFormatSize(pssInfo.nativePss));
sb.append("\nPSS内存使用量:").append(MemoryUtils.getFormatSize(pssInfo.totalPss));
sb.append("\n其他比例大小:").append(MemoryUtils.getFormatSize(pssInfo.otherPss));
final MemoryUtils.DalvikHeapMem dalvikHeapMem = MemoryUtils.getAppDalvikHeapMem();
sb.append("\n已用内存:").append(MemoryUtils.getFormatSize(dalvikHeapMem.allocated));
sb.append("\n最大内存:").append(MemoryUtils.getFormatSize(dalvikHeapMem.maxMem));
sb.append("\n空闲内存:").append(MemoryUtils.getFormatSize(dalvikHeapMem.freeMem));
long appTotalDalvikHeapSize = MemoryUtils.getAppTotalDalvikHeapSize(context);
sb.append("\n应用占用内存:").append(MemoryUtils.getFormatSize(appTotalDalvikHeapSize));
sb.append("\n\n");
crashMem = sb.toString();
}
private static void initThreadHead(Context context, Throwable ex) {
StringBuilder sb = new StringBuilder();
sb.append("该App信息:");
String currentProcessName = ProcessUtils.getCurrentProcessName(context);
if (currentProcessName!=null){
sb.append("\nApp进程名称:").append(currentProcessName);
}
sb.append("\n进程号:").append(android.os.Process.myPid());
sb.append("\n当前线程号:").append(android.os.Process.myTid());
sb.append("\n当前调用该进程的用户号:").append(android.os.Process.myUid());
sb.append("\n当前线程ID:").append(Thread.currentThread().getId());
sb.append("\n当前线程名称:").append(Thread.currentThread().getName());
sb.append("\n主线程ID:").append(context.getMainLooper().getThread().getId());
sb.append("\n主线程名称:").append(context.getMainLooper().getThread().getName());
sb.append("\n主线程优先级:").append(context.getMainLooper().getThread().getPriority());
Activity activity = ToolAppManager.getAppManager().currentActivity();
if (activity!=null){
sb.append("\n当前Activity名称:").append(activity.getComponentName().getClassName());
sb.append("\n当前Activity所在栈的ID:").append(activity.getTaskId());
}
sb.append("\n\n");
crashThread = sb.toString();
}
private static void dumpExceptionToFile(Context context , Throwable ex) {
File file = null;
PrintWriter pw = null;
try {
//Log保存路径
// SDCard/Android/data/<application package>/cache
// data/data/<application package>/cache
File dir = new File(ToolFileUtils.getCrashLogPath(context));
if (!dir.exists()) {
boolean ok = dir.mkdirs();
if (!ok) {
return;
}
}
//Log文件的名字
String fileName = "V" + versionName + "_" + crashTime + CRASH_REPORTER_EXTENSION;
file = new File(dir, fileName);
if (!file.exists()) {
boolean createNewFileOk = file.createNewFile();
if (!createNewFileOk) {
return;
}
}
ToolLogUtils.i(CrashHandler.TAG, "保存异常的log文件名称:" + fileName);
ToolLogUtils.i(CrashHandler.TAG, "保存异常的log文件file:" + file);
//开始写日志
pw = new PrintWriter(new BufferedWriter(new FileWriter(file)));
//判断有没有额外信息需要写入
if (!TextUtils.isEmpty(headContent)) {
pw.println(headContent);
}
//print(ex);
//写入设备信息
pw.println(crashHead);
pw.println(crashMem);
pw.println(crashThread);
//导出异常的调用栈信息
ex.printStackTrace(pw);
//异常信息
Throwable cause = ex.getCause();
while (cause != null) {
cause.printStackTrace(pw);
cause = cause.getCause();
}
//重新命名文件
String string = ex.toString();
String splitEx;
if (string.contains(":")){
splitEx = ex.toString().split(":")[0];
} else {
splitEx = "java.lang.Exception";
}
String newName = "V" + versionName + "_" + crashTime + "_" + splitEx + CRASH_REPORTER_EXTENSION;
File newFile = new File(dir, newName);
//重命名文件
ToolFileUtils.renameFile(file.getPath(), newFile.getPath());
//路径:/storage/emulated/0/Android/data/包名/cache/crashLogs
//file V1.0_2020-09-02_09:05:01.txt
//newFile V1.0_2020-09-02_09:05:01_java.lang.NullPointerException.txt
ToolLogUtils.i(CrashHandler.TAG, "保存异常的log文件路径:" + file.getPath() + "----新路径---"+newFile.getPath());
} catch (Exception e) {
ToolLogUtils.e(CrashHandler.TAG, "保存日志失败:" + e.toString());
} finally {
if (pw != null) {
pw.close();
}
}
}
public static void print(Throwable thr) {
StackTraceElement[] stackTraces = thr.getStackTrace();
for (StackTraceElement stackTrace : stackTraces) {
String clazzName = stackTrace.getClassName();
String fileName = stackTrace.getFileName();
int lineNumber = stackTrace.getLineNumber();
String methodName = stackTrace.getMethodName();
ToolLogUtils.i("printThrowable------"+clazzName+"----"
+fileName+"------"+lineNumber+"----"+methodName);
}
}
private static StackTraceElement parseThrowable(Throwable ex , Context context) {
if (ex == null || ex.getStackTrace() == null || ex.getStackTrace().length == 0) {
return null;
}
if (context == null){
return null;
}
StackTraceElement[] stackTrace = ex.getStackTrace();
StackTraceElement element;
String packageName = context.getPackageName();
for (StackTraceElement ele : stackTrace) {
if (ele.getClassName().contains(packageName)) {
element = ele;
String clazzName = element.getClassName();
String fileName = element.getFileName();
int lineNumber = element.getLineNumber();
String methodName = element.getMethodName();
ToolLogUtils.i("printThrowable----1--"+clazzName+"----"
+fileName+"------"+lineNumber+"----"+methodName);
return element;
}
}
element = stackTrace[0];
String clazzName = element.getClassName();
String fileName = element.getFileName();
int lineNumber = element.getLineNumber();
String methodName = element.getMethodName();
ToolLogUtils.i("printThrowable----2--"+clazzName+"----"
+fileName+"------"+lineNumber+"----"+methodName);
return element;
}
/**
* 获取错误报告文件路径
*
* @param ctx
* @return
*/
@Deprecated
public static String[] getCrashReportFiles(Context ctx) {
File filesDir = new File(getCrashFilePath(ctx));
String[] fileNames = filesDir.list();
int length = fileNames.length;
String[] filePaths = new String[length];
for (int i = 0; i < length; i++) {
filePaths[i] = getCrashFilePath(ctx) + fileNames[i];
}
return filePaths;
}
/**
* 保存错误信息到文件中
* @param ex
* @return
*/
@Deprecated
public static void saveCrashInfoToFile(Context context ,Throwable ex) {
Writer info = new StringWriter();
PrintWriter printWriter = new PrintWriter(info);
ex.printStackTrace(printWriter);
Throwable cause = ex.getCause();
while (cause != null) {
cause.printStackTrace(printWriter);
cause = cause.getCause();
}
String result = info.toString();
printWriter.close();
StringBuilder sb = new StringBuilder();
@SuppressLint("SimpleDateFormat")
String now = dataFormat.format(new Date());
sb.append("TIME:").append(now);//崩溃时间
//程序信息
sb.append("\nAPPLICATION_ID:").append(BuildConfig.APPLICATION_ID);//软件APPLICATION_ID
sb.append("\nVERSION_CODE:").append(BuildConfig.VERSION_CODE);//软件版本号
sb.append("\nVERSION_NAME:").append(BuildConfig.VERSION_NAME);//VERSION_NAME
sb.append("\nBUILD_TYPE:").append(BuildConfig.BUILD_TYPE);//是否是DEBUG版本
//设备信息
sb.append("\nMODEL:").append(Build.MODEL);
sb.append("\nRELEASE:").append(Build.VERSION.RELEASE);
sb.append("\nSDK:").append(Build.VERSION.SDK_INT);
sb.append("\nEXCEPTION:").append(ex.getLocalizedMessage());
sb.append("\nSTACK_TRACE:").append(result);
String crashFilePath = getCrashFilePath(context);
if (crashFilePath!=null && crashFilePath.length()>0){
try {
ToolLogUtils.w(CrashHandler.TAG, "handleException---输出路径-----"+crashFilePath);
FileWriter writer = new FileWriter( crashFilePath+ now + CRASH_REPORTER_EXTENSION);
writer.write(sb.toString());
writer.flush();
writer.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 获取文件夹路径
*
* @param context
* @return
*/
@Deprecated
private static String getCrashFilePath(Context context) {
String path = null;
try {
path = Environment.getExternalStorageDirectory().getCanonicalPath()
+ "/" + context.getResources().getString(R.string.app_name) + "/Crash/";
File file = new File(path);
if (!file.exists()) {
file.mkdirs();
}
} catch (IOException e) {
e.printStackTrace();
}
return path;
}
}
3.CrashToolUtils
崩溃重启操作,提供三种重启方案进行使用。
public final class CrashToolUtils {
private static final String TAG = "CrashToolUtils";
/**
* 可扩展功能
* 1.如何保存任务栈
* 2.activity重启后数据恢复[自动恢复Activity Stack和数据]
* 3.崩溃信息的保存显示,以及是否添加过期清除
* 4.开闭原则,支持拓展性,后期上报数据到自己服务器【待定】
* 5.是清空缓存处理还是重启app
*/
/**
* 退出app操作
*/
private static void exitApp(){
finishActivity();
killCurrentProcess(true);
}
/**
* 杀死进程操作,默认为异常退出
* System.exit(0)是正常退出程序,而System.exit(1)或者说非0表示非正常退出程序
* System.exit(1)一般放在catch块中,当捕获到异常,需要停止程序。这个status=1是用来表示这个程序是非正常退出。
*
* 为何要杀死进程:如果不主动退出进程,重启后会一直黑屏,所以加入主动杀掉进程
* @param isThrow 是否是异常退出
*/
public static void killCurrentProcess(boolean isThrow) {
//需要杀掉原进程,否则崩溃的app处于黑屏,卡死状态
try{
android.os.Process.killProcess(android.os.Process.myPid());
if (isThrow){
System.exit(10);
} else {
System.exit(0);
}
}catch (Exception e){
Log.e(TAG, "killCurrentProcess: "+e.getMessage());
}
}
private static void finishActivity() {
Activity activity = ToolAppManager.getAppManager().currentActivity();
if (activity!=null && !activity.isFinishing()){
//可将activity 退到后台,注意不是finish()退出。
//判断Activity是否是task根
if (activity.isTaskRoot()){
//参数为false——代表只有当前activity是task根,指应用启动的第一个activity时,才有效;
activity.moveTaskToBack(false);
} else {
//参数为true——则忽略这个限制,任何activity都可以有效。
//使用此方法,便不会执行Activity的onDestroy()方法
activity.moveTaskToBack(true);
}
//使用moveTaskToBack是为了让app退出时,不闪屏,退出柔和一些
}
}
private static void finishAllActivity(){
ToolAppManager.getAppManager().finishAllActivity();
}
/**
* 开启一个新的服务,用来重启本APP【使用handler延迟】
* 软件重启,不清临时数据。
* 重启整个APP
* @param context 上下文
* @param Delayed 延迟多少毫秒
*/
public static void reStartApp1(Context context, long Delayed){
//finishActivity();
Intent intent = new Intent(context, KillSelfService.class);
intent.putExtra("PackageName",context.getPackageName());
intent.putExtra("Delayed",Delayed);
context.startService(intent);
ToolLogUtils.w(CrashHandler.TAG, "reStartApp--- 用来重启本APP--1---");
// exitApp();
killCurrentProcess(true);
}
/**
* 用来重启本APP[使用闹钟,整体重启,临时数据清空(推荐)]
* 重启整个APP
* @param context 上下文
* @param Delayed 延迟多少毫秒
*/
public static void reStartApp2(Context context , long Delayed , Class clazz){
//finishActivity();
//Intent intent = context.getPackageManager().getLaunchIntentForPackage(context.getPackageName());
Intent intent = new Intent(context.getApplicationContext(), clazz);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
//intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
/*intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_CLEAR_TASK |
Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);*/
if (intent.getComponent() != null) {
//如果类名已经设置,我们强制它模拟启动器启动。
//如果我们不这样做,如果你从错误活动重启,然后按home,
//然后从启动器启动活动,主活动将在backstack上出现两次。
//这很可能不会有任何有害的影响,因为如果你设置了Intent组件,
//if将始终启动,而不考虑此处指定的操作。
intent.setAction(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_LAUNCHER);
}
//为何用PendingIntent,不能Intent
PendingIntent restartIntent = PendingIntent.getActivity(
context.getApplicationContext(), 0, intent,PendingIntent.FLAG_ONE_SHOT);
//退出程序
AlarmManager mgr = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
mgr.set(AlarmManager.RTC, System.currentTimeMillis() + Delayed,restartIntent);
ToolLogUtils.w(CrashHandler.TAG, "reStartApp--- 用来重启本APP--2---"+clazz);
//exitApp();
killCurrentProcess(true);
}
public static void reStartApp3(Context context) {
String packageName = context.getPackageName();
Activity activity = ToolAppManager.getAppManager().currentActivity();
Class<? extends Activity> clazz = guessRestartActivityClass(activity);
ToolLogUtils.w(CrashHandler.TAG, "reStartApp--- 用来重启本APP--3-"+packageName + "--"+clazz);
Intent intent = new Intent(activity, clazz);
restartApplicationWithIntent(activity, intent);
}
public static void reStartApp4(Context context) {
relaunchApp(context,false);
}
/**
* 通过包名打开app
* @param packageName 包名
*/
public static void reStartApp5(Context context,final String packageName) {
if (packageName==null || packageName.length()==0){
return;
}
if (context==null){
return;
}
Intent launchAppIntent = getLaunchAppIntent(context,packageName);
if (launchAppIntent == null) {
Log.e("AppUtils", "Didn't exist launcher activity.");
return;
}
context.startActivity(launchAppIntent);
}
private static void restartApplicationWithIntent(@NonNull Activity activity, @NonNull Intent intent) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_CLEAR_TASK |
Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
if (intent.getComponent() != null) {
//如果类名已经设置,我们强制它模拟启动器启动。
//如果我们不这样做,如果你从错误活动重启,然后按home,
//然后从启动器启动活动,主活动将在backstack上出现两次。
//这很可能不会有任何有害的影响,因为如果你设置了Intent组件,
//if将始终启动,而不考虑此处指定的操作。
intent.setAction(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_LAUNCHER);
}
activity.startActivity(intent);
activity.finish();
killCurrentProcess(true);
}
@Nullable
private static Class<? extends Activity> guessRestartActivityClass(@NonNull Context context) {
Class<? extends Activity> resolvedActivityClass;
resolvedActivityClass = getRestartActivityClassWithIntentFilter(context);
if (resolvedActivityClass == null) {
resolvedActivityClass = getLauncherActivity(context);
}
return resolvedActivityClass;
}
@SuppressWarnings("unchecked")
@Nullable
private static Class<? extends Activity> getRestartActivityClassWithIntentFilter(@NonNull Context context) {
Intent searchedIntent = new Intent().setPackage(context.getPackageName());
//检索可以为给定意图执行的所有活动
List<ResolveInfo> resolveInfo = context.getPackageManager().queryIntentActivities(searchedIntent,
PackageManager.GET_RESOLVED_FILTER);
if (resolveInfo.size() > 0) {
ResolveInfo info = resolveInfo.get(0);
try {
return (Class<? extends Activity>) Class.forName(info.activityInfo.name);
} catch (ClassNotFoundException e) {
ToolLogUtils.e(CrashHandler.TAG+e.getMessage());
}
}
return null;
}
@SuppressWarnings("unchecked")
@Nullable
private static Class<? extends Activity> getLauncherActivity(@NonNull Context context) {
Intent intent = context.getPackageManager().getLaunchIntentForPackage(context.getPackageName());
if (intent != null && intent.getComponent() != null) {
try {
return (Class<? extends Activity>) Class.forName(intent.getComponent().getClassName());
} catch (ClassNotFoundException e) {
ToolLogUtils.e(CrashHandler.TAG+e.getLocalizedMessage());
}
}
return null;
}
/**
* Relaunch the application.
*
* @param context
* @param isKillProcess True to kill the process, false otherwise.
*/
public static void relaunchApp(Context context, final boolean isKillProcess) {
Intent intent = getLaunchAppIntent(context,context.getApplicationContext().getPackageName(), true);
if (intent == null) {
Log.e("AppUtils", "Didn't exist launcher activity.");
return;
}
intent.addFlags(
Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_CLEAR_TASK
);
context.startActivity(intent);
if (!isKillProcess) return;
android.os.Process.killProcess(android.os.Process.myPid());
System.exit(0);
}
private static Intent getLaunchAppIntent(Context context, final String packageName) {
return getLaunchAppIntent(context,packageName, false);
}
private static Intent getLaunchAppIntent(Context context, final String packageName, final boolean isNewTask) {
String launcherActivity = getLauncherActivity(context,packageName);
if (!launcherActivity.isEmpty()) {
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_LAUNCHER);
ComponentName cn = new ComponentName(packageName, launcherActivity);
intent.setComponent(cn);
return isNewTask ? intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) : intent;
}
return null;
}
private static String getLauncherActivity(Context context, @NonNull final String pkg) {
Intent intent = new Intent(Intent.ACTION_MAIN, null);
intent.addCategory(Intent.CATEGORY_LAUNCHER);
intent.setPackage(pkg);
PackageManager pm = context.getApplicationContext().getPackageManager();
List<ResolveInfo> info = pm.queryIntentActivities(intent, 0);
int size = info.size();
if (size == 0) return "";
for (int i = 0; i < size; i++) {
ResolveInfo ri = info.get(i);
if (ri.activityInfo.processName.equals(pkg)) {
return ri.activityInfo.name;
}
}
return info.get(0).activityInfo.name;
}
}
4.ToolFileUtils
文件操作,提供日志信息的文件持久化。
/**
* 日志文件保存
*/
public class ToolFileUtils {
/**
* 目录地址
* 崩溃日志记录地址
* SDCard/Android/data/<application package>/cache
* data/data/<application package>/cache
*/
public static String getCrashLogPath(Context context) {
String path = getCachePath(context) + File.separator + "crashLogs";
return path;
}
/**
* 目录地址
* 崩溃截图记录地址
* SDCard/Android/data/<application package>/cache
* data/data/<application package>/cache
*/
public static String getCrashPicPath(Context context) {
String path = getCachePath(context) + File.separator + "crashPics";
File file = new File(path);
if (!file.exists()) {
file.mkdirs();
}
return path;
}
/**
* 获取崩溃分享路径地址
* @return 路径
*/
public static String getCrashSharePath() {
String path = Environment.getExternalStorageDirectory() + "";
File file = new File(path);
if (!file.exists()) {
file.mkdirs();
}
return path;
}
/**
* 获取app缓存路径
* SDCard/Android/data/<application package>/cache
* data/data/<application package>/cache
*
* @param context 上下文
* @return
*/
private static String getCachePath(Context context) {
String cachePath;
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())
|| !Environment.isExternalStorageRemovable()) {
//外部存储可用
if (context.getExternalCacheDir()!=null){
cachePath = context.getExternalCacheDir().getAbsolutePath();
} else {
cachePath = context.getCacheDir().getAbsolutePath();
}
} else {
//外部存储不可用
cachePath = context.getCacheDir().getAbsolutePath();
}
return cachePath;
}
/**
* 获取崩溃crash的list集合
* @param context 上下文
* @return 集合
*/
public static List<File> getCrashFileList(Context context) {
File file = new File(ToolFileUtils.getCrashLogPath(context));
List<File> mFileList = new ArrayList<>();
File[] fileArray = file.listFiles();
if (fileArray == null || fileArray.length <= 0) {
return mFileList;
}
for (File f : fileArray) {
if (f.isFile()) {
mFileList.add(f);
}
}
return mFileList;
}
/**
* 删除单个文件
*
* @param fileName 要删除的文件的文件名
* @return 单个文件删除成功返回true,否则返回false
*/
public static boolean deleteFile(String fileName) {
File file = new File(fileName);
// 如果文件路径所对应的文件存在,并且是一个文件,则直接删除
if (file.exists() && file.isFile()) {
if (file.delete()) {
return true;
} else {
return false;
}
} else {
return false;
}
}
/**
* 删除所有的文件
* @param root root目录
*/
public static void deleteAllFiles(File root) {
File files[] = root.listFiles();
if (files != null)
for (File f : files) {
if (f.isDirectory()) { // 判断是否为文件夹
deleteAllFiles(f);
try {
f.delete();
} catch (Exception e) {
}
} else {
if (f.exists()) { // 判断是否存在
deleteAllFiles(f);
try {
f.delete();
} catch (Exception e) {
}
}
}
}
}
/**
* 读取file文件,转化成字符串
* @param fileName 文件名称
* @return
*/
public static String readFile2String(String fileName) {
String res = "";
try {
FileInputStream inputStream = new FileInputStream(fileName);
InputStreamReader inputStreamReader = null;
try {
inputStreamReader = new InputStreamReader(inputStream, "utf-8");
} catch (UnsupportedEncodingException e1) {
e1.printStackTrace();
}
BufferedReader reader = new BufferedReader(inputStreamReader);
StringBuilder sb = new StringBuilder("");
String line;
try {
while ((line = reader.readLine()) != null) {
sb.append(line);
sb.append("\n");
}
} catch (IOException e) {
e.printStackTrace();
}
res = sb.toString();
} catch (Exception e) {
e.printStackTrace();
}
return res;
}
/**
* 重命名文件
*
* @param oldPath 原来的文件地址
* @param newPath 新的文件地址
*/
public static void renameFile(String oldPath, String newPath) {
File oleFile = new File(oldPath);
File newFile = new File(newPath);
//执行重命名
oleFile.renameTo(newFile);
}
/**
* 根据文件路径拷贝文件
*
* @param src 源文件
* @param dest 目标文件
* @return boolean 成功true、失败false
*/
public static boolean copyFile(File src, File dest) {
boolean result = false;
if ((src == null) || (dest == null)) {
return result;
}
if (dest.exists()) {
dest.delete(); // delete file
}
if (!createOrExistsDir(dest.getParentFile())) {
return false;
}
try {
dest.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
FileChannel srcChannel = null;
FileChannel dstChannel = null;
try {
srcChannel = new FileInputStream(src).getChannel();
dstChannel = new FileOutputStream(dest).getChannel();
srcChannel.transferTo(0, srcChannel.size(), dstChannel);
result = true;
} catch (FileNotFoundException e) {
e.printStackTrace();
return result;
} catch (IOException e) {
e.printStackTrace();
return result;
}
try {
srcChannel.close();
dstChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
return result;
}
/**
* 删除文件
* @param file file文件
* @return
*/
public static boolean deleteFile(final File file) {
return file != null && (!file.exists() || file.isFile() && file.delete());
}
/**
* 判断文件是否创建,如果没有创建,则新建
* @param file file文件
* @return
*/
public static boolean createOrExistsDir(final File file) {
return file != null && (file.exists() ? file.isDirectory() : file.mkdirs());
}
}
四. 代码地址
https://gitee.com/luisliuyi/android-optimize-crash.git