嘿,今天的你过的还好吗
这项目完事了.总有你不知道为啥就崩溃的情况.那你崩溃了你得知道情况吧.给测试看.给你自己看.你是不是得知道因为啥崩溃了.所以就需要崩溃采集.思路是.你崩溃了采集一下log就ok了
那么思路有了.
需要一个文件读写的权限.如果没有的话就没有存储呗.那就得把这个报错显示出来,权限然后需要服务.当知道崩溃了需要知道你什么时候崩溃了.第三个就是崩溃了把最近的崩溃日志记录下来.如此结束
用了两个Server来保证能抓到崩溃
package com.example.kotlinbasemodel.logutil;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
import android.os.RemoteException;
import androidx.core.app.NotificationCompat;
import com.blankj.utilcode.util.AppUtils;
import com.example.kotlinbasemodel.R;
public class Service1 extends Service {
private final static int NOTIFY_ID = 101;
Notification notification;
NotificationManager manager;
@Override
public void onCreate() {
super.onCreate();
// LogUtil.leoricLog("Service1 onCreate");
// saveRuntimeLog("Service1 onCreate");
// showForegroundNotification(this,LAUNCHER_NAME);
// startService(new Intent(Service1.this,Service2.class));
// bindService(new Intent(Service1.this,Service2.class),connection, Context.BIND_ABOVE_CLIENT);
//
}
@Override
public IBinder onBind(Intent intent) {
// saveRuntimeLog("Service1 onBind");
return new Binder();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// LogUtil.leoricLog("Service1 onStartCommand");
// saveRuntimeLog("Service1 onStartCommand");
// showNotification();
// startForeground(NOTIFY_ID, notification);
// MyApplication.startSocketService(this);
// return super.onStartCommand(intent, flags, startId);
return Service.START_NOT_STICKY;
}
private final ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
// saveRuntimeLog("Service1与Service2断连");
startService(new Intent(Service1.this, Service2.class));
bindService(new Intent(Service1.this, Service2.class), connection, Context.BIND_ABOVE_CLIENT);
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
// saveRuntimeLog("Service1与Service2连接");
try {
service.linkToDeath(mDeathRecipient, 0);
} catch (RemoteException e) {
e.printStackTrace();
}
}
};
private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
// saveRuntimeLog("Service1 binderDied");
}
};
@Override
public void onDestroy() {
super.onDestroy();
// LogUtil.leoricLog("Service1 onDestroy");
// saveRuntimeLog("Service1 onDestroy");
}
private void showNotification() {
manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
String packageName = AppUtils.getAppPackageName();
String className = "com.zwl.moduletest.activity.TestBaseActivity";
ComponentName component = new ComponentName(packageName, className);
Intent explicitIntent = new Intent();
explicitIntent.setComponent(component);
// startActivity(explicitIntent);
// Intent hangIntent = new Intent(this, MainActivity.class);
PendingIntent hangPendingIntent = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
hangPendingIntent = PendingIntent.getActivity(this, 1002, explicitIntent, PendingIntent.FLAG_UPDATE_CURRENT|PendingIntent.FLAG_IMMUTABLE);
}else {
hangPendingIntent = PendingIntent.getActivity(this, 1002, explicitIntent, PendingIntent.FLAG_UPDATE_CURRENT);
}
String CHANNEL_ID = "your_custom_id";//应用频道Id唯一值, 长度若太长可能会被截断,
String CHANNEL_NAME = "your_custom_name";//最长40个字符,太长会被截断
notification = new NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("appService1通知Title")
.setContentText("appService1通知Content")
.setSmallIcon(R.mipmap.ic_launcher)
.setContentIntent(hangPendingIntent)
.setAutoCancel(true)
.build();
//Android 8.0 以上需包添加渠道
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel notificationChannel = new NotificationChannel(CHANNEL_ID,
CHANNEL_NAME, NotificationManager.IMPORTANCE_HIGH);
manager.createNotificationChannel(notificationChannel);
}
manager.notify(NOTIFY_ID, notification);
}
}
package com.example.kotlinbasemodel.logutil;
//package com.example.kotlinbasemodel.logutil;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
import android.os.RemoteException;
import androidx.core.app.NotificationCompat;
import com.blankj.utilcode.util.AppUtils;
import com.example.kotlinbasemodel.R;
import com.example.kotlinbasemodel.logutil.Service1;
//import me.weishu.leoric.LogUtil;
public class Service2 extends Service{
private final static int NOTIFY_ID = 100;
Notification notification;
NotificationManager manager;
@Override
public void onCreate() {
super.onCreate();
// LogUtil.leoricLog("appService2 onCreate");
// saveRuntimeLog("appService2 onCreate");
// showForegroundNotification(this,LAUNCHER_NAME);
// startService(new Intent(Service2.this,Service1.class));
// bindService(new Intent(Service2.this,Service1.class),connection, Context.BIND_ABOVE_CLIENT);
// MyApplication.startSocketService(this);
}
@Override
public IBinder onBind(Intent intent) {
// saveRuntimeLog("Service2 onBind");
//此处不返回不会调用onServiceConnected
return new Binder();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// LogUtil.leoricLog("Service2 onStartCommand");
// saveRuntimeLog("Service2 onStartCommand");
// showNotification();
// startForeground(NOTIFY_ID,notification);
return Service.START_NOT_STICKY;
// return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
// LogUtil.leoricLog("Service2 onDestroy");
// saveRuntimeLog("Service2 onDestroy");
}
private final ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
// saveRuntimeLog("Service2与Service1断连");
startService(new Intent(Service2.this, Service1.class));
bindService(new Intent(Service2.this,Service1.class),connection, Context.BIND_ABOVE_CLIENT);
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
// saveRuntimeLog("Service2与Service1连接");
try {
service.linkToDeath(mDeathRecipient,0);
} catch (RemoteException e) {
e.printStackTrace();
}
}
};
private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
// saveRuntimeLog("Service2 binderDied");
}
};
private void showNotification() {
manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
String className = "com.zwl.moduletest.activity.TestBaseActivity";
String packageName = AppUtils.getAppPackageName();
ComponentName component = new ComponentName(packageName, className);
Intent explicitIntent =new Intent();
explicitIntent.setComponent(component);
// Intent hangIntent = new Intent(this, MainActivity.class);
PendingIntent hangPendingIntent = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
hangPendingIntent = PendingIntent.getActivity(this, 1001, explicitIntent, PendingIntent.FLAG_UPDATE_CURRENT|PendingIntent.FLAG_IMMUTABLE);
}else {
hangPendingIntent = PendingIntent.getActivity(this, 1001, explicitIntent, PendingIntent.FLAG_UPDATE_CURRENT);
}
String CHANNEL_ID = "your_custom_id";//应用频道Id唯一值, 长度若太长可能会被截断,
String CHANNEL_NAME = "your_custom_name";//最长40个字符,太长会被截断
notification = new NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("appService2通知Title")
.setContentText("appService2通知Content")
.setSmallIcon(R.mipmap.ic_launcher)
.setContentIntent(hangPendingIntent)
.setAutoCancel(true)
.build();
//Android 8.0 以上需包添加渠道
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel notificationChannel = new NotificationChannel(CHANNEL_ID,
CHANNEL_NAME, NotificationManager.IMPORTANCE_HIGH);
manager.createNotificationChannel(notificationChannel);
}
manager.notify(NOTIFY_ID, notification);
}
}
然后去抓包.收集错误日志.显示出来
package com.example.kotlinbasemodel.logutil
import android.app.Activity
import android.app.Application
import android.app.Application.ActivityLifecycleCallbacks
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import com.example.kotlinbasemodel.logutil.AppTool.Companion.getAppName
import com.example.kotlinbasemodel.logutil.AppTool.Companion.getDeviceModelName
import com.example.kotlinbasemodel.logutil.AppTool.Companion.getPackageName
import com.example.kotlinbasemodel.logutil.AppTool.Companion.getVersionName
import com.example.kotlinbasemodel.logutil.AppTool.Companion.killCurrentProcess
import java.io.PrintWriter
import java.io.StringWriter
import java.lang.ref.WeakReference
import java.text.DateFormat
import java.text.SimpleDateFormat
import java.util.*
import java.util.zip.ZipFile
class Anomalous {
companion object {
private const val INTENT_ACTION_RESTART_ACTIVITY = "RESTART";
private const val EXTRA_STACK_TRACE =
"EXTRA_STACK_TRACE"
private const val EXTRA_ACTIVITY_LOG =
"EXTRA_ACTIVITY_LOG"
private const val MAX_STACK_TRACE_SIZE = 131071 //128 KB - 1
private const val MIN_SHOW_CRASH_INTERVAL = 3000
private const val MAX_ACTIVITIES_IN_LOG = 50
private const val INTENT_ACTION_ERROR_ACTIVITY = "com.zwl.anomalous.ERROR"
//Shared preferences
private const val SHARED_PREFERENCES_FILE = "TCrashTool"
private const val SHARED_PREFERENCES_FIELD_TIMESTAMP = "last_crash_timestamp"
private val activityLog =
LimitArrayDeque<String>(
MAX_ACTIVITIES_IN_LOG
)
private var lastActivityCreated = WeakReference<Activity?>(null)
@JvmStatic
fun install(context: Context) {
val oldHandler = Thread.getDefaultUncaughtExceptionHandler()
Thread.setDefaultUncaughtExceptionHandler { t, e ->
if (oldHandler != null && !oldHandler.javaClass.name.startsWith(javaClass.name)) {
// if (!hasCrashedInTheLastSeconds(context.applicationContext)) {
// setLastCrashTimestamp(
// context.applicationContext,
// Date().time
// )
var errorActivityClass = guessErrorActivityClass(context.applicationContext)
val intent = Intent(context.applicationContext, errorActivityClass)
val sw = StringWriter()
val pw = PrintWriter(sw)
e.printStackTrace(pw)
var stackTraceString = sw.toString()
//Reduce data to 128KB so we don't get a TransactionTooLargeException when sending the intent.
//The limit is 1MB on Android but some devices seem to have it lower.
//See: http://developer.android.com/reference/android/os/TransactionTooLargeException.html
//And: http://stackoverflow.com/questions/11451393/what-to-do-on-transactiontoolargeexception#comment46697371_12809171
if (stackTraceString.length > MAX_STACK_TRACE_SIZE) {
val disclaimer = " [stack trace too large]"
stackTraceString = stackTraceString.substring(
0,
MAX_STACK_TRACE_SIZE - disclaimer.length
) + disclaimer
}
intent.putExtra(
EXTRA_STACK_TRACE,
stackTraceString
)
val activityLogStringBuilder = StringBuilder()
while (!activityLog.isEmpty()) {
activityLogStringBuilder.append(activityLog.poll())
}
intent.putExtra(
EXTRA_ACTIVITY_LOG,
activityLogStringBuilder.toString()
)
getAllErrorDetailsFromIntent(
context.applicationContext,
intent
)?.let {
}
intent.flags =
Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
context.startActivity(intent)
val lastActivity: Activity? = lastActivityCreated.get()
if (lastActivity != null) {
//We finish the activity, this solves a bug which causes infinite recursion.
//See: https://github.com/ACRA/acra/issues/42
lastActivity.finish()
lastActivityCreated.clear()
}
//com.android.internal.os.RuntimeInit$UncaughtHandler
if (!oldHandler.javaClass.name.startsWith("com.android.internal.os.RuntimeInit")) {
oldHandler.uncaughtException(t, e)
} else {
// e.printStackTrace()
killCurrentProcess()
}
// } else {
// oldHandler.uncaughtException(t, e)
// }
} else {
oldHandler?.uncaughtException(t, e)
}
}
(context.applicationContext as Application).registerActivityLifecycleCallbacks(
object : ActivityLifecycleCallbacks {
val dateFormat: DateFormat =
SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.CHINA)
var currentlyStartedActivities = 0
override fun onActivityCreated(
activity: Activity,
savedInstanceState: Bundle?
) {
if (activity.javaClass != getErrorActivityClassWithIntentFilter(context.applicationContext)) {
// Copied from ACRA:
// Ignore activityClass because we want the last
// application Activity that was started so that we can
// explicitly kill it off.
lastActivityCreated = WeakReference(activity)
}
activityLog.add(
"""${dateFormat.format(Date())}: ${activity.javaClass.simpleName} created
"""
)
}
override fun onActivityStarted(activity: Activity) {
currentlyStartedActivities++
}
override fun onActivityResumed(activity: Activity) {
activityLog.add(
"""${dateFormat.format(Date())}: ${activity.javaClass.simpleName} resumed
"""
)
}
override fun onActivityPaused(activity: Activity) {
activityLog.add(
"""${dateFormat.format(Date())}: ${activity.javaClass.simpleName} paused
"""
)
}
override fun onActivityStopped(activity: Activity) {
//Do nothing
currentlyStartedActivities--
}
override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {
//Do nothing
}
override fun onActivityDestroyed(activity: Activity) {
activityLog.add(
"""${dateFormat.format(Date())}: ${activity.javaClass.simpleName} destroyed
"""
)
}
})
}
private fun hasCrashedInTheLastSeconds(context: Context): Boolean {
val lastTimestamp: Long = getLastCrashTimestamp(context)
val currentTimestamp = Date().time
return lastTimestamp <= currentTimestamp && currentTimestamp - lastTimestamp < MIN_SHOW_CRASH_INTERVAL
}
private fun getLastCrashTimestamp(context: Context): Long {
return context.getSharedPreferences(
SHARED_PREFERENCES_FILE,
Context.MODE_PRIVATE
).getLong(SHARED_PREFERENCES_FIELD_TIMESTAMP, -1)
}
private fun setLastCrashTimestamp(context: Context, timestamp: Long) {
context.getSharedPreferences(SHARED_PREFERENCES_FILE, Context.MODE_PRIVATE)
.edit()
.putLong(SHARED_PREFERENCES_FIELD_TIMESTAMP, timestamp)
.commit()
}
private fun guessErrorActivityClass(context: Context): Class<out Activity?> {
var resolvedActivityClass: Class<out Activity?>?
//If action is defined, use that
resolvedActivityClass = getErrorActivityClassWithIntentFilter(context)
//Else, get the default error activity
if (resolvedActivityClass == null) {
resolvedActivityClass = AnomalousActivity::class.java
}
return resolvedActivityClass
}
private fun getErrorActivityClassWithIntentFilter(context: Context): Class<out Activity?>? {
val searchedIntent =
Intent().setAction(INTENT_ACTION_ERROR_ACTIVITY)
.setPackage(context.packageName)
val resolveInfos = context.packageManager.queryIntentActivities(
searchedIntent,
PackageManager.GET_RESOLVED_FILTER
)
if (resolveInfos.size > 0) {
val resolveInfo = resolveInfos[0]
try {
return Class.forName(resolveInfo.activityInfo.name) as Class<out Activity?>
} catch (e: ClassNotFoundException) {
e.printStackTrace()
}
}
return null
}
fun guessRestartActivityClass(context: Context): Class<out Activity?>? {
var resolvedActivityClass: Class<out Activity?>?
//If action is defined, use that
resolvedActivityClass = getRestartActivityClassWithIntentFilter(context)
//Else, get the default launcher activity
if (resolvedActivityClass == null) {
resolvedActivityClass = getLauncherActivity(context)
}
return resolvedActivityClass
}
private fun getRestartActivityClassWithIntentFilter(context: Context): Class<out Activity?>? {
val searchedIntent =
Intent().setAction(INTENT_ACTION_RESTART_ACTIVITY)
.setPackage(context.packageName)
val resolveInfos = context.packageManager.queryIntentActivities(
searchedIntent,
PackageManager.GET_RESOLVED_FILTER
)
if (resolveInfos.size > 0) {
val resolveInfo = resolveInfos[0]
try {
return Class.forName(resolveInfo.activityInfo.name) as Class<out Activity?>
} catch (e: ClassNotFoundException) {
e.printStackTrace()
}
}
return null
}
@JvmStatic
fun getLauncherActivity(context: Context): Class<out Activity?>? {
val intent = context.packageManager.getLaunchIntentForPackage(context.packageName)
if (intent != null && intent.component != null) {
try {
return Class.forName(intent.component!!.className) as Class<out Activity?>
} catch (e: ClassNotFoundException) {
e.printStackTrace()
}
}
return null
}
fun getAllErrorDetailsFromIntent(context: Context, intent: Intent): String? {
//I don't think that this needs localization because it's a development string...
val currentDate = Date()
val dateFormat: DateFormat = SimpleDateFormat("yyyy年MM月dd日 HH点mm分ss秒", Locale.CHINA)
//Get build date
val buildDateAsString: String? = getBuildDateAsString(context, dateFormat)
//Get app version
val versionName: String? = getVersionName(context)
val appName: String? = getAppName(context)
val packageName: String? = getPackageName(context)
var errorDetails = ""
errorDetails += "Build App Name : $appName \n"
errorDetails += "Build version : $versionName \n"
errorDetails += "Build Package Name : $packageName \n"
if (buildDateAsString != null) {
errorDetails += "Build date : $buildDateAsString \n"
}
errorDetails += """Current date : ${dateFormat.format(currentDate)}
"""
//Added a space between line feeds to fix #18.
//Ideally, we should not use this method at all... It is only formatted this way because of coupling with the default error activity.
//We should move it to a method that returns a bean, and let anyone format it as they wish.
errorDetails += """Device : ${getDeviceModelName()}
"""
errorDetails += """OS version : Android ${Build.VERSION.RELEASE} (SDK ${Build.VERSION.SDK_INT})
"""
errorDetails += "Stack trace : \n"
errorDetails += getStackTraceFromIntent(intent)
val activityLog: String? = getActivityLogFromIntent(intent)
if (activityLog != null) {
errorDetails += "\nUser actions : \n"
errorDetails += activityLog
}
return errorDetails
}
private fun getBuildDateAsString(context: Context, dateFormat: DateFormat): String? {
var buildDate: Long
try {
val ai = context.packageManager.getApplicationInfo(context.packageName, 0)
val zf = ZipFile(ai.sourceDir)
//If this failed, try with the old zip method
val ze = zf.getEntry("classes.dex")
buildDate = ze.time
zf.close()
} catch (e: Exception) {
buildDate = 0
}
return if (buildDate > 312764400000L) {
dateFormat.format(Date(buildDate))
} else {
null
}
}
fun getStackTraceFromIntent(intent: Intent): String? {
return intent.getStringExtra(EXTRA_STACK_TRACE)
}
fun getActivityLogFromIntent(intent: Intent): String? {
return intent.getStringExtra(EXTRA_ACTIVITY_LOG)
}
}
}
至此架构基本完事.剩下的就是看项目需求了