新增崩溃界面

pull/21/head
fengyuecanzhu 3 years ago
parent a465db092a
commit fda3600046
  1. 4
      .idea/assetWizardSettings.xml
  2. 5
      .idea/misc.xml
  3. 2
      app/src/main/AndroidManifest.xml
  4. 34
      app/src/main/java/xyz/fycz/myreader/application/CrashHandler.java
  5. 187
      app/src/main/java/xyz/fycz/myreader/ui/activity/CrashActivity.java
  6. 45
      app/src/main/java/xyz/fycz/myreader/ui/activity/RestartActivity.kt
  7. 4
      app/src/main/res/drawable/ic_exit.xml
  8. 12
      app/src/main/res/drawable/ic_info.xml
  9. 21
      app/src/main/res/drawable/ic_reboot.xml
  10. 121
      app/src/main/res/layout/activity_crash.xml

@ -14,8 +14,8 @@
<option name="values">
<map>
<entry key="assetSourceType" value="FILE" />
<entry key="outputName" value="ic_search_word" />
<entry key="sourceFile" value="F:\SVG图标\搜索_o.svg" />
<entry key="outputName" value="ic_info" />
<entry key="sourceFile" value="F:\SVG图标\info.svg" />
</map>
</option>
</PersistentState>

@ -3,15 +3,20 @@
<component name="DesignSurface">
<option name="filePathToZoomLevelMap">
<map>
<entry key="..\:/android/FYReader/app/src/main/res/drawable/ic_about.xml" value="0.20925925925925926" />
<entry key="..\:/android/FYReader/app/src/main/res/drawable/ic_delete.xml" value="0.18518518518518517" />
<entry key="..\:/android/FYReader/app/src/main/res/drawable/ic_exit.xml" value="0.20925925925925926" />
<entry key="..\:/android/FYReader/app/src/main/res/drawable/ic_online_syn.xml" value="0.19074074074074074" />
<entry key="..\:/android/FYReader/app/src/main/res/drawable/ic_re_seg.xml" value="0.1962962962962963" />
<entry key="..\:/android/FYReader/app/src/main/res/drawable/ic_restore.xml" value="0.19074074074074074" />
<entry key="..\:/android/FYReader/app/src/main/res/drawable/ic_search_word.xml" value="0.21944444444444444" />
<entry key="..\:/android/FYReader/app/src/main/res/drawable/info_ic.xml" value="0.20925925925925926" />
<entry key="..\:/android/FYReader/app/src/main/res/drawable/login_btn_selector.xml" value="0.17777777777777778" />
<entry key="..\:/android/FYReader/app/src/main/res/drawable/reboot_ic.xml" value="0.20925925925925926" />
<entry key="..\:/android/FYReader/app/src/main/res/drawable/selector_btn_add.xml" value="0.17777777777777778" />
<entry key="..\:/android/FYReader/app/src/main/res/layout/activity_about.xml" value="0.12132725430597771" />
<entry key="..\:/android/FYReader/app/src/main/res/layout/activity_auth_email.xml" value="0.1897644927536232" />
<entry key="..\:/android/FYReader/app/src/main/res/layout/activity_crash.xml" value="0.264" />
<entry key="..\:/android/FYReader/app/src/main/res/layout/activity_donate.xml" value="0.536" />
<entry key="..\:/android/FYReader/app/src/main/res/layout/activity_feedback.xml" value="0.12132725430597771" />
<entry key="..\:/android/FYReader/app/src/main/res/layout/activity_file_picker.xml" value="0.2318840579710145" />

@ -122,6 +122,8 @@
<activity android:name=".ui.activity.GroupManagerActivity" />
<activity android:name=".ui.activity.SearchWordActivity" />
<activity android:name=".ui.activity.AuthEmailActivity" />
<activity android:name=".ui.activity.RestartActivity" />
<activity android:name=".ui.activity.CrashActivity" />
<receiver android:name=".util.notification.NotificationClickReceiver" />
<receiver android:name=".ui.presenter.BookcasePresenter$cancelDownloadReceiver" />

@ -36,6 +36,8 @@ import io.reactivex.ObservableSource;
import io.reactivex.functions.Function;
import xyz.fycz.myreader.common.APPCONST;
import xyz.fycz.myreader.common.URLCONST;
import xyz.fycz.myreader.ui.activity.CrashActivity;
import xyz.fycz.myreader.ui.activity.RestartActivity;
import xyz.fycz.myreader.ui.activity.SplashActivity;
import xyz.fycz.myreader.util.utils.FileUtils;
import xyz.fycz.myreader.util.utils.OkHttpUtils;
@ -46,7 +48,15 @@ import xyz.fycz.myreader.util.utils.OkHttpUtils;
*/
public final class CrashHandler implements Thread.UncaughtExceptionHandler {
private DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
private final DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd-HH:mm:ss", Locale.getDefault());
/**
* Crash 文件名
*/
private static final String CRASH_FILE_NAME = "crash_file";
/**
* Crash 时间记录
*/
private static final String KEY_CRASH_TIME = "key_crash_time";
/**
* 注册 Crash 监听
@ -70,7 +80,24 @@ public final class CrashHandler implements Thread.UncaughtExceptionHandler {
@SuppressLint("ApplySharedPref")
@Override
public void uncaughtException(@NonNull Thread thread, @NonNull Throwable throwable) {
saveCrashLog(throwable);
SharedPreferences sharedPreferences = mApplication.getSharedPreferences(CRASH_FILE_NAME, Context.MODE_PRIVATE);
long currentCrashTime = System.currentTimeMillis();
long lastCrashTime = sharedPreferences.getLong(KEY_CRASH_TIME, 0);
// 记录当前崩溃的时间,以便下次崩溃时进行比对
sharedPreferences.edit().putLong(KEY_CRASH_TIME, currentCrashTime).commit();
String logFilePath = saveCrashLog(throwable);
// 致命异常标记:如果上次崩溃的时间距离当前崩溃小于 5 分钟,那么判定为致命异常
boolean deadlyCrash = currentCrashTime - lastCrashTime < 1000 * 60 * 5;
// 如果是致命的异常,或者是调试模式下
if (deadlyCrash || App.isDebug()) {
CrashActivity.start(mApplication, throwable, logFilePath);
} else {
RestartActivity.Companion.start(mApplication);
}
// 不去触发系统的崩溃处理(com.android.internal.os.RuntimeInit$KillApplicationHandler)
if (mNextHandler != null && !mNextHandler.getClass().getName().startsWith("com.android.internal.os")) {
mNextHandler.uncaughtException(thread, throwable);
@ -80,7 +107,7 @@ public final class CrashHandler implements Thread.UncaughtExceptionHandler {
System.exit(10);
}
private void saveCrashLog(Throwable throwable) {
private String saveCrashLog(Throwable throwable) {
StringWriter stringWriter = new StringWriter();
PrintWriter printWriter = new PrintWriter(stringWriter);
throwable.printStackTrace(printWriter);
@ -142,6 +169,7 @@ public final class CrashHandler implements Thread.UncaughtExceptionHandler {
String path = APPCONST.LOG_DIR + fileName;
File file = FileUtils.getFile(path);
FileUtils.writeText(mPhoneInfo + "\n\n" + mStackTrace, file);
return file.getAbsolutePath();
}
/**

@ -0,0 +1,187 @@
package xyz.fycz.myreader.ui.activity;
import android.app.Application;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Build;
import android.os.Bundle;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.style.ForegroundColorSpan;
import android.text.style.UnderlineSpan;
import android.util.DisplayMetrics;
import androidx.appcompat.widget.Toolbar;
import androidx.core.view.GravityCompat;
import com.gyf.immersionbar.ImmersionBar;
import java.io.File;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import xyz.fycz.myreader.R;
import xyz.fycz.myreader.application.App;
import xyz.fycz.myreader.base.BaseActivity;
import xyz.fycz.myreader.databinding.ActivityCrashBinding;
import xyz.fycz.myreader.util.ShareUtils;
/**
* @author fengyue
* @date 2022/1/22 9:15
*/
public class CrashActivity extends BaseActivity {
private ActivityCrashBinding binding;
/**
* 报错代码行数正则表达式
*/
private static final Pattern CODE_REGEX = Pattern.compile("\\(\\w+\\.\\w+:\\d+\\)");
public static final String INTENT_CRASH_KEY = "crash_exception";
public static final String INTENT_LOG_KEY = "log_file_path";
private String mStackTrace;
private String logFilePath;
public static void start(Application application, Throwable throwable, String logFilePath) {
if (throwable == null) {
return;
}
Intent intent = new Intent(application, CrashActivity.class);
intent.putExtra(INTENT_CRASH_KEY, throwable);
intent.putExtra(INTENT_LOG_KEY, logFilePath);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
application.startActivity(intent);
}
@Override
protected void bindView() {
binding = ActivityCrashBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
}
@Override
protected void setUpToolbar(Toolbar toolbar) {
// 设置状态栏沉浸
ImmersionBar.setTitleBar(this, binding.llCrashBar);
}
@Override
protected boolean initSwipeBackEnable() {
return false;
}
@Override
protected void initData(Bundle savedInstanceState) {
Throwable throwable = (Throwable) getIntent().getSerializableExtra(INTENT_CRASH_KEY);
logFilePath = getIntent().getStringExtra(INTENT_LOG_KEY);
if (throwable == null) {
return;
}
binding.tvCrashTitle.setText(throwable.getClass().getSimpleName());
StringWriter stringWriter = new StringWriter();
PrintWriter printWriter = new PrintWriter(stringWriter);
throwable.printStackTrace(printWriter);
Throwable cause = throwable.getCause();
if (cause != null) {
cause.printStackTrace(printWriter);
}
mStackTrace = stringWriter.toString();
Matcher matcher = CODE_REGEX.matcher(mStackTrace);
SpannableStringBuilder spannable = new SpannableStringBuilder(mStackTrace);
if (spannable.length() > 0) {
for (int index = 0; matcher.find(); index++) {
// 不包含左括号(
int start = matcher.start() + "(".length();
// 不包含右括号 )
int end = matcher.end() - ")".length();
// 设置前景
spannable.setSpan(new ForegroundColorSpan(index < 3 ? 0xFF287BDE : 0xFF999999), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
// 设置下划线
spannable.setSpan(new UnderlineSpan(), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
binding.tvCrashMessage.setText(spannable);
}
Resources res = getResources();
DisplayMetrics displayMetrics = res.getDisplayMetrics();
int screenWidth = displayMetrics.widthPixels;
int screenHeight = displayMetrics.heightPixels;
String targetResource;
if (displayMetrics.densityDpi > 480) {
targetResource = "xxxhdpi";
} else if (displayMetrics.densityDpi > 320) {
targetResource = "xxhdpi";
} else if (displayMetrics.densityDpi > 240) {
targetResource = "xhdpi";
} else if (displayMetrics.densityDpi > 160) {
targetResource = "hdpi";
} else if (displayMetrics.densityDpi > 120) {
targetResource = "mdpi";
} else {
targetResource = "ldpi";
}
StringBuilder builder = new StringBuilder();
builder.append("设备品牌:\t").append(Build.BRAND)
.append("\n设备型号:\t").append(Build.MODEL)
.append("\n设备类型:\t").append(isTablet() ? "平板" : "手机");
builder.append("\n屏幕宽高:\t").append(screenWidth).append(" x ").append(screenHeight)
.append("\n屏幕密度:\t").append(displayMetrics.densityDpi)
.append("\n目标资源:\t").append(targetResource);
builder.append("\n安卓版本:\t").append(Build.VERSION.RELEASE)
.append("\nAPI 版本:\t").append(Build.VERSION.SDK_INT)
.append("\nCPU 架构:\t").append(Build.SUPPORTED_ABIS[0]);
builder.append("\n应用版本:\t").append(App.getStrVersionName())
.append("\n版本代码:\t").append(App.getVersionCode());
try {
SimpleDateFormat dateFormat = new SimpleDateFormat("MM-dd HH:mm", Locale.getDefault());
PackageInfo packageInfo = getPackageManager().getPackageInfo(getPackageName(), PackageManager.GET_PERMISSIONS);
builder.append("\n首次安装:\t").append(dateFormat.format(new Date(packageInfo.firstInstallTime)))
.append("\n最近安装:\t").append(dateFormat.format(new Date(packageInfo.lastUpdateTime)))
.append("\n崩溃时间:\t").append(dateFormat.format(new Date()));
} catch (PackageManager.NameNotFoundException ignored) {
}
binding.tvCrashInfo.setText(builder);
}
@Override
protected void initClick() {
binding.ivCrashInfo.setOnClickListener(v -> binding.dlCrashDrawer.openDrawer(GravityCompat.START));
binding.ivCrashShare.setOnClickListener(v -> {
File logFile;
if (logFilePath != null && (logFile = new File(logFilePath)).exists()) {
ShareUtils.share(this, logFile, "崩溃日志", "text/plain");
} else {
ShareUtils.share(this, mStackTrace);
}
});
binding.ivCrashRestart.setOnClickListener(v -> {
// 重启应用
RestartActivity.Companion.restart(this);
finish();
});
}
/**
* 判断当前设备是否是平板
*/
public boolean isTablet() {
return (getResources().getConfiguration().screenLayout
& Configuration.SCREENLAYOUT_SIZE_MASK)
>= Configuration.SCREENLAYOUT_SIZE_LARGE;
}
}

@ -0,0 +1,45 @@
package xyz.fycz.myreader.ui.activity
import android.os.Bundle
import xyz.fycz.myreader.base.BaseActivity
import android.content.Intent
import android.app.Activity
import android.content.Context
import xyz.fycz.myreader.util.ToastUtils
/**
* @author fengyue
* @date 2022/1/22 8:57
*/
class RestartActivity : BaseActivity() {
override fun bindView() {}
companion object {
fun start(context: Context) {
val intent = Intent(context, RestartActivity::class.java)
if (context !is Activity) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}
context.startActivity(intent)
}
fun restart(context: Context) {
val intent = Intent(context, MainActivity::class.java)
if (context !is Activity) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}
context.startActivity(intent)
}
}
override fun initData(savedInstanceState: Bundle?) {
restart(this)
finish()
ToastUtils.showInfo("正在重启应用")
}
}

@ -5,8 +5,8 @@
android:viewportHeight="1024">
<path
android:pathData="M546.13,128a42.67,42.67 0,0 0,-42.67 42.67v341.33a42.67,42.67 0,0 0,85.33 0V170.67a42.67,42.67 0,0 0,-42.67 -42.67z"
android:fillColor="#666666"/>
android:fillColor="#FFFFFF"/>
<path
android:pathData="M631.47,180.48v87.89a298.67,298.67 0,1 1,-170.67 0V180.48a384,384 0,0 0,-298.67 374.19,388.69 388.69,0 0,0 372.91,384A384,384 0,0 0,631.47 180.48z"
android:fillColor="#666666"/>
android:fillColor="#FFFFFF"/>
</vector>

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:pathData="M512,822c171.21,0 310,-138.79 310,-310s-138.79,-310 -310,-310 -310,138.79 -310,310 138.79,310 310,310zM512,912c-220.91,0 -400,-179.09 -400,-400S291.09,112 512,112s400,179.09 400,400 -179.09,400 -400,400z"
android:fillColor="#ffffff"/>
<path
android:pathData="M468,505c0,-24.85 20.15,-45 45,-45s45,20.15 45,45v150c0,24.85 -20.15,45 -45,45S468,679.85 468,655v-150zM468,373c0,-24.85 20.15,-45 45,-45s45,20.15 45,45v1c0,24.85 -20.15,45 -45,45S468,398.85 468,374v-1z"
android:fillColor="#ffffff"/>
</vector>

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:fillColor="#FFFFFF"
android:pathData="M510.887,350.362 A25.536554,25.536554,0,0,0,536.424,324.825 L536.424,25.5366
A25.536554,25.536554,0,0,0,485.351,25.5366 L485.351,324.825
A25.536554,25.536554,0,0,0,510.888,350.362 Z" />
<path
android:fillColor="#FFFFFF"
android:pathData="M782.085,80.4401 A25.536554,25.536554,0,0,0,755.017,123.852
A459.65798,459.65798,0,1,1,51.2291,512.263
A455.827497,455.827497,0,0,1,266.758,123.852
A25.536554,25.536554,0,1,0,239.689,80.4399
A510.731089,510.731089,0,1,0,1021.62,512.263
A506.389874,506.389874,0,0,0,782.085,80.44 Z" />
</vector>

@ -0,0 +1,121 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/dl_crash_drawer"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white"
android:orientation="vertical"
android:paddingHorizontal="20dp"
android:paddingVertical="10dp"
tools:context=".ui.activity.CrashActivity">
<LinearLayout
android:id="@+id/ll_crash_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/iv_crash_info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="10dp"
app:srcCompat="@drawable/ic_info"
app:tint="@color/textPrimary"/>
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/tv_crash_title"
android:layout_width="0px"
android:layout_height="wrap_content"
android:layout_marginStart="30dp"
android:layout_marginEnd="10dp"
android:layout_weight="1"
android:gravity="center_horizontal"
android:lineSpacingExtra="5dp"
android:text="应用程序发生崩溃"
android:textSize="@dimen/text_medium_size"
android:textStyle="bold"
app:tint="@color/textPrimary"/>
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/iv_crash_share"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="10dp"
app:srcCompat="@drawable/ic_share"
app:tint="@color/textPrimary"/>
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/iv_crash_restart"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="10dp"
app:srcCompat="@drawable/ic_exit"
app:tint="@color/textPrimary"/>
</LinearLayout>
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginHorizontal="10dp"
android:layout_marginTop="15dp">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/tv_crash_message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:lineSpacingExtra="5dp"
android:textColor="#FF4444"
android:textSize="16sp"
tools:text="我是错误信息" />
</androidx.core.widget.NestedScrollView>
</LinearLayout>
<androidx.core.widget.NestedScrollView
android:layout_width="240dp"
android:layout_height="match_parent"
android:layout_gravity="start"
android:background="@color/white">
<LinearLayout
android:id="@+id/ll_crash_info"
android:layout_width="240dp"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="10dp"
android:text="详细信息"
android:textColor="@color/black80"
android:textSize="16sp"
android:textStyle="bold" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/tv_crash_info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginVertical="15dp"
android:lineSpacingExtra="5dp"
android:textColor="@color/black60"
android:textSize="14sp" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</androidx.drawerlayout.widget.DrawerLayout>
Loading…
Cancel
Save