Android之崩溃日志管理
文章大纲
一、Android崩溃日志管理简介
二、崩溃日志管理实战
三、项目源码下载
一、Android崩溃日志管理简介
1. 什么是android崩溃日志管理
开发中有些地方未注意可能造成异常抛出未能caught到,然后弹出系统对话框强制退出。这种交互不好,而且开发者也不能及时获取到底哪里出问题。因此我们可以使用android的UncaughtExceptionHandler来处理这种异常。
2. 操作逻辑
用户端(出现崩溃)
我们会封装一个通用的jar包,该jar包包括日志打印、捕获异常信息逻辑、网络传输、设置Debug和Release模式、获取本机的相关信息等,当出现异常时,将异常信息以文件方式保存在用户手机中,并且发送到后台,当后台接收成功时,自动删除用户手机的崩溃信息文件,若接收失败,在下次发生崩溃时,将历史发送失败的崩溃一同发送。
接收端(后台)
我们会编写一个地址,用于接收异常的具体信息,并储存在本地文件中,以此作为日志进行管理。
二、崩溃日志管理实战
1. 后台端
在该实战中,我以简单的servlet进行讲解,实际项目中,可以以ssm或spring boot等框架进行操作。
/**
* 接收崩溃信息,并进行打印(实际项目中,需要以文件形式归档)
* @author wxc
*
*/
public class Test extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doPost(request, response);
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//获取客户端传送过来的信息流
BufferedReader in=new BufferedReader(new InputStreamReader(request.getInputStream()));
StringBuilder sb = new StringBuilder();
String line = null;
while ((line = in.readLine()) != null) {
//将信息流进行打印
System.out.println(line);
}
}
}
2. 客户端通用项目
网络请求相关的配置管理类:HttpManager.java
/**
*
* 网络请求相关的配置管理
*
* @author 吴晓畅
*
*/
public class HttpManager {
private static final int SET_CONNECTION_TIMEOUT = 5 * 1000;
private static final int SET_SOCKET_TIMEOUT = 20 * 1000;
private static final String BOUNDARY = getBoundry();// UUID.randomUUID().toString();
private static final String MP_BOUNDARY = "--" + BOUNDARY;
private static final String END_MP_BOUNDARY = "--" + BOUNDARY + "--";
private static final String LINEND = "\r\n";
private static final String CHARSET = "UTF-8";
public static String uploadFile(String url, HttpParameters params,
File logFile) throws IOException{
HttpClient client = getHttpClient();
HttpPost post = new HttpPost(url);
ByteArrayOutputStream bos = null;
FileInputStream logFileInputStream = null;
String result = null;
try {
bos = new ByteArrayOutputStream();
if(params != null){
String key = "";
for (int i = 0; i < params.size(); i++) {
key = params.getKey(i);
StringBuilder temp = new StringBuilder(10);
temp.setLength(0);
temp.append(MP_BOUNDARY).append(LINEND);
temp.append("content-disposition: form-data; name=\"").append(key)
.append("\"").append(LINEND + LINEND);
temp.append(params.getValue(key)).append(LINEND);
bos.write(temp.toString().getBytes());
}
}
StringBuilder temp = new StringBuilder();
temp.append(MP_BOUNDARY).append(LINEND);
temp.append(
"content-disposition: form-data; name=\"logfile\"; filename=\"")
.append(logFile.getName()).append("\"").append(LINEND);
temp.append("Content-Type: application/octet-stream; charset=utf-8").append(LINEND + LINEND);
bos.write(temp.toString().getBytes());
logFileInputStream = new FileInputStream(logFile);
byte[] buffer = new byte[1024*8];//8k
while(true){
int count = logFileInputStream.read(buffer);
if(count == -1){
break;
}
bos.write(buffer, 0, count);
}
bos.write((LINEND+LINEND).getBytes());
bos.write((END_MP_BOUNDARY+LINEND).getBytes());
ByteArrayEntity formEntity = new ByteArrayEntity(bos.toByteArray());
post.setEntity(formEntity);
HttpResponse response = client.execute(post);
StatusLine status = response.getStatusLine();
int statusCode = status.getStatusCode();
Log.i("HttpManager", "返回结果为"+statusCode);
if(statusCode == HttpStatus.SC_OK){
result = readHttpResponse(response);
}
} catch (IOException e) {
throw e;
}finally{
if(bos != null){
try {
bos.close();
} catch (IOException e) {
throw e;
}
}
if(logFileInputStream != null){
try {
logFileInputStream.close();
} catch (IOException e) {
throw e;
}
}
}
return result;
}
private static String readHttpResponse(HttpResponse response){
String result = null;
HttpEntity entity = response.getEntity();
InputStream inputStream;
try {
inputStream = entity.getContent();
ByteArrayOutputStream content = new ByteArrayOutputStream();
int readBytes = 0;
byte[] sBuffer = new byte[512];
while ((readBytes = inputStream.read(sBuffer)) != -1) {
content.write(sBuffer, 0, readBytes);
}
result = new String(content.toByteArray(), CHARSET);
return result;
} catch (IllegalStateException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return result;
}
private static HttpClient getHttpClient() {
try {
KeyStore trustStore = KeyStore.getInstance(KeyStore
.getDefaultType());
trustStore.load(null, null);
SSLSocketFactory sf = new MySSLSocketFactory(trustStore);
sf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
HttpParams params = new BasicHttpParams();
HttpConnectionParams.setConnectionTimeout(params, 10000);
HttpConnectionParams.setSoTimeout(params, 10000);
HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
HttpProtocolParams.setContentCharset(params, HTTP.UTF_8);
SchemeRegistry registry = new SchemeRegistry();
registry.register(new Scheme("http", PlainSocketFactory
.getSocketFactory(), 80));
registry.register(new Scheme("https", sf, 443));
ClientConnectionManager ccm = new ThreadSafeClientConnManager(
params, registry);
HttpConnectionParams.setConnectionTimeout(params,
SET_CONNECTION_TIMEOUT);
HttpConnectionParams.setSoTimeout(params, SET_SOCKET_TIMEOUT);
HttpClient client = new DefaultHttpClient(ccm, params);
return client;
} catch (Exception e) {
// e.printStackTrace();
return new DefaultHttpClient();
}
}
private static class MySSLSocketFactory extends SSLSocketFactory {
SSLContext sslContext = SSLContext.getInstance("TLS");
public MySSLSocketFactory(KeyStore truststore)
throws NoSuchAlgorithmException, KeyManagementException,
KeyStoreException, UnrecoverableKeyException {
super(truststore);
TrustManager tm = new X509TrustManager() {
@Override
public X509Certificate[] getAcceptedIssuers() {
// TODO Auto-generated method stub
return null;
}
@Override
public void checkServerTrusted(X509Certificate[] chain,
String authType) throws CertificateException {
// TODO Auto-generated method stub
}
@Override
public void checkClientTrusted(X509Certificate[] chain,
String authType) throws CertificateException {
// TODO Auto-generated method stub
}
};
sslContext.init(null, new TrustManager[] { tm }, null);
}
@Override
public Socket createSocket() throws IOException {
return sslContext.getSocketFactory().createSocket();
}
@Override
public Socket createSocket(Socket socket, String host, int port,
boolean autoClose) throws IOException, UnknownHostException {
return sslContext.getSocketFactory().createSocket(socket, host,
port, autoClose);
}
}
private static String getBoundry() {
StringBuffer _sb = new StringBuffer();
for (int t = 1; t < 12; t++) {
long time = System.currentTimeMillis() + t;
if (time % 3 == 0) {
_sb.append((char) time % 9);
} else if (time % 3 == 1) {
_sb.append((char) (65 + time % 26));
} else {
_sb.append((char) (97 + time % 26));
}
}
return _sb.toString();
}
}
文件上传相关类:UploadLogManager.java
package com.qihoo.linker.logcollector.upload;
import java.io.File;
import java.io.IOException;
import java.util.logging.Logger;
import com.qihoo.linker.logcollector.capture.LogFileStorage;
import android.content.Context;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
/**
*
* @author 吴晓畅
*
*/
public class UploadLogManager {
private static final String TAG = UploadLogManager.class.getName();
private static UploadLogManager sInstance;
private Context mContext;
private HandlerThread mHandlerThread;
private static volatile MyHandler mHandler;
private volatile Looper mLooper;
private volatile boolean isRunning = false;
private String url;
private HttpParameters params;
private UploadLogManager(Context c){
mContext = c.getApplicationContext();
mHandlerThread = new HandlerThread(TAG + ":HandlerThread");
mHandlerThread.start();
}
//初始化UploadLogManager类
public static synchronized UploadLogManager getInstance(Context c){
if(sInstance == null){
sInstance = new UploadLogManager(c);
}
return sInstance;
}
/**
* 执行文件上传具体操作
*
* @param url
* @param params
*/
public void uploadLogFile(String url , HttpParameters params){
this.url = url;
this.params = params;
mLooper = mHandlerThread.getLooper();
mHandler = new MyHandler(mLooper);
if(mHandlerThread == null){
return;
}
if(isRunning){
return;
}
mHandler.sendMessage(mHandler.obtainMessage());
isRunning = true;
}
//用于uploadLogFile方法调用的线程
private final class MyHandler extends Handler{
public MyHandler(Looper looper) {
super(looper);
// TODO Auto-generated constructor stub
}
@Override
public void handleMessage(Message msg) {
File logFile = LogFileStorage.getInstance(mContext).getUploadLogFile();
if(logFile == null){
isRunning = false;
return;
}
try {
String result = HttpManager.uploadFile(url, params, logFile);
Log.i("UpLoad", "服务端返回数据为"+result);
if(result != null){
Boolean isSuccess = LogFileStorage.getInstance(mContext).deleteUploadLogFile();
Log.i("UpLoad", "删除文件结果为"+isSuccess);
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
isRunning = false;
}
}
}
}
客户端崩溃日志文件的删除,保存等操作类:LogFileStorage.java
文件保存在Android/data/包名/Log/下
package com.qihoo.linker.logcollector.capture;
import java.io.File;
import java.io.FileOutputStream;
import com.qihoo.linker.logcollector.utils.LogCollectorUtility;
import com.qihoo.linker.logcollector.utils.LogHelper;
import android.content.Context;
import android.util.Log;
/**
*
* 客户端崩溃日志文件的删除,保存等操作
*
* @author 吴晓畅
*
*/
public class LogFileStorage {
private static final String TAG = LogFileStorage.class.getName();
public static final String LOG_SUFFIX = ".log";
private static final String CHARSET = "UTF-8";
private static LogFileStorage sInstance;
private Context mContext;
private LogFileStorage(Context ctx) {
mContext = ctx.getApplicationContext();
}
public static synchronized LogFileStorage getInstance(Context ctx) {
if (ctx == null) {
LogHelper.e(TAG, "Context is null");
return null;
}
if (sInstance == null) {
sInstance = new LogFileStorage(ctx);
}
return sInstance;
}
public File getUploadLogFile(){
File dir = mContext.getFilesDir();
File logFile = new File(dir, LogCollectorUtility.getMid(mContext)
+ LOG_SUFFIX);
if(logFile.exists()){
return logFile;
}else{
return null;
}
}
//删除客户端中崩溃日志文件
public boolean deleteUploadLogFile(){
File dir = mContext.getFilesDir();
File logFile = new File(dir, LogCollectorUtility.getMid(mContext)
+ LOG_SUFFIX);
Log.i("Log",
LogCollectorUtility.getMid(mContext)
+ LOG_SUFFIX);
return logFile.delete();
}
//保存文件
public boolean saveLogFile2Internal(String logString) {
try {
File dir = mContext.getFilesDir();
if (!dir.exists()) {
dir.mkdirs();
}
File logFile = new File(dir, LogCollectorUtility.getMid(mContext)
+ LOG_SUFFIX);
FileOutputStream fos = new FileOutputStream(logFile , true);
fos.write(logString.getBytes(CHARSET));
fos.close();
} catch (Exception e) {
e.printStackTrace();
LogHelper.e(TAG, "saveLogFile2Internal failed!");
return false;
}
return true;
}
public boolean saveLogFile2SDcard(String logString, boolean isAppend) {
if (!LogCollectorUtility.isSDcardExsit()) {
LogHelper.e(TAG, "sdcard not exist");
return false;
}
try {
File logDir = getExternalLogDir();
if (!logDir.exists()) {
logDir.mkdirs();
}
File logFile = new File(logDir, LogCollectorUtility.getMid(mContext)
+ LOG_SUFFIX);
/*if (!isAppend) {
if (logFile.exists() && !logFile.isFile())
logFile.delete();
}*/
LogHelper.d(TAG, logFile.getPath());
FileOutputStream fos = new FileOutputStream(logFile , isAppend);
fos.write(logString.getBytes(CHARSET));
fos.close();
} catch (Exception e) {
e.printStackTrace();
Log.e(TAG, "saveLogFile2SDcard failed!");
return false;
}
return true;
}
private File getExternalLogDir() {
File logDir = LogCollectorUtility.getExternalDir(mContext, "Log");
LogHelper.d(TAG, logDir.getPath());
return logDir;
}
}
UncaughtExceptionHandler实现类:CrashHandler.java
当出现异常时,会进入public void uncaughtException(Thread thread, Throwable ex) 方法中。
/**
*
* 如果需要捕获系统的未捕获异常(如系统抛出了未知错误,这种异常没有捕获,这将导致系统莫名奇妙的关闭,使得用户体验差),
* 可以通过UncaughtExceptionHandler来处理这种异常。
*
* @author 吴晓畅
*
*/
public class CrashHandler implements UncaughtExceptionHandler {
private static final String TAG = CrashHandler.class.getName();
private static final String CHARSET = "UTF-8";
private static CrashHandler sInstance;
private Context mContext;
private Thread.UncaughtExceptionHandler mDefaultCrashHandler;
String appVerName;
String appVerCode;
String OsVer;
String vendor;
String model;
String mid;
//初始化该类
private CrashHandler(Context c) {
mContext = c.getApplicationContext();
// mContext = c;
appVerName = "appVerName:" + LogCollectorUtility.getVerName(mContext);
appVerCode = "appVerCode:" + LogCollectorUtility.getVerCode(mContext);
OsVer = "OsVer:" + Build.VERSION.RELEASE;
vendor = "vendor:" + Build.MANUFACTURER;
model = "model:" + Build.MODEL;
mid = "mid:" + LogCollectorUtility.getMid(mContext);
}
//初始化该类
public static CrashHandler getInstance(Context c) {
if (c == null) {
LogHelper.e(TAG, "Context is null");
return null;
}
if (sInstance == null) {
sInstance = new CrashHandler(c);
}
return sInstance;
}
public void init() {
if (mContext == null) {
return;
}
boolean b = LogCollectorUtility.hasPermission(mContext);
if (!b) {
return;
}
mDefaultCrashHandler = Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler(this);
}
/**
* 发生异常时候进来这里
*/
@Override
public void uncaughtException(Thread thread, Throwable ex) {
//
handleException(ex);
//
ex.printStackTrace();
if (mDefaultCrashHandler != null) {
mDefaultCrashHandler.uncaughtException(thread, ex);
} else {
Process.killProcess(Process.myPid());
// System.exit(1);
}
}
//将异常信息保存成文件
private void handleException(Throwable ex) {
String s = fomatCrashInfo(ex);
// String bes = fomatCrashInfoEncode(ex);
LogHelper.d(TAG, s);
// LogHelper.d(TAG, bes);
//LogFileStorage.getInstance(mContext).saveLogFile2Internal(bes);
LogFileStorage.getInstance(mContext).saveLogFile2Internal(s);
if(Constants.DEBUG){
LogFileStorage.getInstance(mContext).saveLogFile2SDcard(s, true);
}
}
private String fomatCrashInfo(Throwable ex) {
/*
* String lineSeparator = System.getProperty("line.separator");
* if(TextUtils.isEmpty(lineSeparator)){ lineSeparator = "\n"; }
*/
String lineSeparator = "\r\n";
StringBuilder sb = new StringBuilder();
String logTime = "logTime:" + LogCollectorUtility.getCurrentTime();
String exception = "exception:" + ex.toString();
Writer info = new StringWriter();
PrintWriter printWriter = new PrintWriter(info);
ex.printStackTrace(printWriter);
String dump = info.toString();
String crashMD5 = "crashMD5:"
+ LogCollectorUtility.getMD5Str(dump);
String crashDump = "crashDump:" + "{" + dump + "}";
printWriter.close();
sb.append("&start---").append(lineSeparator);
sb.append(logTime).append(lineSeparator);
sb.append(appVerName).append(lineSeparator);
sb.append(appVerCode).append(lineSeparator);
sb.append(OsVer).append(lineSeparator);
sb.append(vendor).append(lineSeparator);
sb.append(model).append(lineSeparator);
sb.append(mid).append(lineSeparator);
sb.append(exception).append(lineSeparator);
sb.append(crashMD5).append(lineSeparator);
sb.append(crashDump).append(lineSeparator);
sb.append("&end---").append(lineSeparator).append(lineSeparator)
.append(lineSeparator);
return sb.toString();
}
private String fomatCrashInfoEncode(Throwable ex) {
/*
* String lineSeparator = System.getProperty("line.separator");
* if(TextUtils.isEmpty(lineSeparator)){ lineSeparator = "\n"; }
*/
String lineSeparator = "\r\n";
StringBuilder sb = new StringBuilder();
String logTime = "logTime:" + LogCollectorUtility.getCurrentTime();
String exception = "exception:" + ex.toString();
Writer info = new StringWriter();
PrintWriter printWriter = new PrintWriter(info);
ex.printStackTrace(printWriter);
String dump = info.toString();
String crashMD5 = "crashMD5:"
+ LogCollectorUtility.getMD5Str(dump);
try {
dump = URLEncoder.encode(dump, CHARSET);
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
String crashDump = "crashDump:" + "{" + dump + "}";
printWriter.close();
sb.append("&start---").append(lineSeparator);
sb.append(logTime).append(lineSeparator);
sb.append(appVerName).append(lineSeparator);
sb.append(appVerCode).append(lineSeparator);
sb.append(OsVer).append(lineSeparator);
sb.append(vendor).append(lineSeparator);
sb.append(model).append(lineSeparator);
sb.append(mid).append(lineSeparator);
sb.append(exception).append(lineSeparator);
sb.append(crashMD5).append(lineSeparator);
sb.append(crashDump).append(lineSeparator);
sb.append("&end---").append(lineSeparator).append(lineSeparator)
.append(lineSeparator);
String bes = Base64.encodeToString(sb.toString().getBytes(),
Base64.NO_WRAP);
return bes;
}
}
项目调用封装类:LogCollector.java
/**
*
* 执行文件上传相关的类
*
*
* @author 吴晓畅
*
*/
public class LogCollector {
private static final String TAG = LogCollector.class.getName();
private static String Upload_Url;
private static Context mContext;
private static boolean isInit = false;
private static HttpParameters mParams;
//初始化文件上传的url,数据等内容
public static void init(Context c , String upload_url , HttpParameters params){
if(c == null){
return;
}
if(isInit){
return;
}
Upload_Url = upload_url;
mContext = c;
mParams = params;
//初始化自己定义的异常处理
CrashHandler crashHandler = CrashHandler.getInstance(c);
crashHandler.init();
isInit = true;
}
/**
* 执行文件上传的网路请求
*
* if(isWifiOnly && !isWifiMode){
return;
}表示只在wifi状态下执行文件上传
*
* @param isWifiOnly
*/
public static void upload(boolean isWifiOnly){
if(mContext == null || Upload_Url == null){
Log.d(TAG, "please check if init() or not");
return;
}
if(!LogCollectorUtility.isNetworkConnected(mContext)){
return;
}
boolean isWifiMode = LogCollectorUtility.isWifiConnected(mContext);
if(isWifiOnly && !isWifiMode){
return;
}
UploadLogManager.getInstance(mContext).uploadLogFile(Upload_Url, mParams);
}
/**
* 用于设置是否为测试状态
*
* @param isDebug true为是,false为否 如果是,能看到LOG日志,同时能够在将文件夹看到崩溃日志
*/
public static void setDebugMode(boolean isDebug){
Constants.DEBUG = isDebug;
LogHelper.enableDefaultLog = isDebug;
}
}
3. 客户端接入使用
为通用项目设置is Library模式
实际android项目使用
添加Library
在Application子类中进行初始化
public class MyApplication extends Application {
//后台地址地址
private static final String UPLOAD_URL = "http://192.168.3.153:8080/bengkuitest/servlet/Test";
@Override
public void onCreate() {
super.onCreate();
boolean isDebug = true;
//设置是否为测试模式,如果是,同时能够在将文件夹看到崩溃日志
LogCollector.setDebugMode(isDebug);
//params的数据可以为空 初始化LogCollector的相关数据,用于文件上传到服务器
LogCollector.init(getApplicationContext(), UPLOAD_URL, null);
}
}
编写异常并上传异常
public class MainActivity extends Activity implements OnClickListener {
private Button btn_crash;
private Button btn_upload;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn_crash = (Button) findViewById(R.id.button1);
btn_upload = (Button) findViewById(R.id.button2);
btn_crash.setOnClickListener(this);
btn_upload.setOnClickListener(this);
}
//产生异常
private void causeCrash(){
String s = null;
s.split("1");
}
//上传文件
private void uploadLogFile(){
//设置为只在wifi下上传文件
boolean isWifiOnly = true;//only wifi mode can upload
//执行文件上传服务器
LogCollector.upload(isWifiOnly);//upload at the right time
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.button1:
causeCrash();
break;
case R.id.button2:
//上传文件
uploadLogFile();
break;
default:
break;
}
}
}
运行结果如下图所示
--No1Qr4Tu7Wx
content-disposition: form-data; name="logfile"; filename="c5c63fec3651fdebdd411582793fa40c.log"
Content-Type: application/octet-stream; charset=utf-8
&start---
logTime:2019-04-07 10:54:47
appVerName:1.0
appVerCode:1
OsVer:5.1.1
vendor:samsung
model:SM-G955F
mid:c5c63fec3651fdebdd411582793fa40c
exception:java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.String[] java.lang.String.split(java.lang.String)' on a null object reference
crashMD5:74861b8fb97ef57b82a87a826ab6b08f
crashDump:{java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.String[] java.lang.String.split(java.lang.String)' on a null object reference
at com.jiabin.logcollectorexample.MainActivity.causeCrash(MainActivity.java:32)
at com.jiabin.logcollectorexample.MainActivity.onClick(MainActivity.java:45)
at android.view.View.performClick(View.java:4780)
at android.view.View$PerformClick.run(View.java:19866)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:135)
at android.app.ActivityThread.main(ActivityThread.java:5293)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:903)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:698)
}
&end---
--No1Qr4Tu7Wx--
三、项目源码下载
Android之崩溃日志管理的更多相关文章
- 捕android程序崩溃日志
主要类别: package com.example.callstatus; import java.io.File; import java.io.FileOutputStream; import j ...
- android app崩溃日志收集以及上传
源代码获取请到github:https://github.com/DrJia/AndroidLogCollector 已经做成sdk的形式,源代码已公开,源代码看不懂的请自行google. 假设想定制 ...
- Android 简易崩溃日志保存
仅仅做了简单的保存到了本地而已: 根据需要可以继续增加功能: 下一次启动上传到服务器: 增加应用版本,机型系统版本信息等: public class CrashSaver { public stati ...
- 保存android程序崩溃日志到SD卡
private boolean writeToSDCard(Throwable ex) { boolean isDealing = false; if (Environment.getExternal ...
- iOS崩溃日志记录工具--CrashlyTics
http://try.crashlytics.com Crashlytics优势: 1.Crashlytics基本不会漏掉任何应用崩溃的信息 2.Crashlytics对崩溃日志管理很人性化,会根据崩 ...
- 【Android应用开发】 Android 崩溃日志 本地存储 与 远程保存
示例代码下载 : http://download.csdn.net/detail/han1202012/8638801; 一. 崩溃日志本地存储 1. 保存原理解析 崩溃信息本地保存步骤 : -- 1 ...
- android开发之应用Crash自动抓取Log_自动保存崩溃日志到本地
http://blog.csdn.net/jason0539/article/details/45602655 应用发生crash之后要查看log,判断问题出在什么地方,可是一旦应用发布出去,就要想办 ...
- android的Log日志打印管理工具类(一)
android的Log日志的打印管理工具类: package com.gzcivil.utils; import android.util.Log; /** * 日志打印管理 * * @author ...
- android 程序崩溃crash日志的捕捉
android 程序崩溃crash日志的捕捉 之前在项目开发过程中,一直会遇到程序崩溃了,但是测试組的哥哥们又没及时的导出日志.... 后来在诳群的时候听别人说起,腾讯有那么一个叫bugly的东西 将 ...
随机推荐
- react 高阶组件的 理解和应用
高阶组件是什么东西 简单的理解是:一个包装了另一个基础组件的组件.(相对高阶组件来说,我习惯把被包装的组件称为基础组件) 注意:这里说的是包装,可以理解成包裹和组装: 具体的是高阶组件的两种形式吧: ...
- Vue.js——60分钟组件快速入门
一.组件简介 组件系统是Vue.js其中一个重要的概念,它提供了一种抽象,让我们可以使用独立可复用的小组件来构建大型应用,任意类型的应用界面都可以抽象为一个组件树: 那么什么是组件呢?组件可以扩展HT ...
- PAT1064: Compelte Binary Search Tree
1064. Complete Binary Search Tree (30) 时间限制 100 ms 内存限制 65536 kB 代码长度限制 16000 B 判题程序 Standard 作者 CHE ...
- 刨根问底HTTP和WebSocket协议
HTML5的新成员:WebSocket 上篇介绍了HTTP1.1协议的基本内容,这篇文章将继续分析WebSocket协议,然后对这两个进行简单的比较. WebSocket WebSocket协议还很年 ...
- NIO之FileChannel类的理解和使用
文章链接:http://blog.csdn.net/qq_16628781/article/details/70532307 知识点: FileChannel类及方法理解: 普通输入输出流复制文件: ...
- maven入门 (二)_私服安装与上传下载
本篇文章主要介绍maven的私服安装和 jar包的上传与下载.毕竟大家还是在公司需要上传jar包到自己公司私服的. 1.安装私服 下载链接: https://pan.baidu.com/s/17dbQ ...
- Java开源生鲜电商平台-订单抽成模块的设计与架构(源码可下载)
Java开源生鲜电商平台-订单抽成模块的设计与架构(源码可下载) 说明:订单抽成指的是向卖家收取相应的信息服务费.(目前市场上有两种抽成方式,一种是按照总额的抽成比率,另外一种是按照订单明细的抽成比率 ...
- 基于E-R模型的关系型数据库设计方法
摘要 在管理信息系统开发中,数据库设计的目标是建立DBMS能识别的关系数据模型.而关系数据模型建立的基础是首先建立E-R模型,通过E-R模型才能转换为关系数据模型.如何建立E-R模型以及如何将E-R模 ...
- CAPTCHA---验证码 ---Security code
BotDetect Java CAPTCHA Generator 3. Add BotDetect Java CAPTCHA Library Dependency Here is how to add ...
- Mybatis通过注解方式实现批量插入数据库 及 常见的坑
原文地址:http://f0rb.iteye.com/blog/1207384 MyBatis中通过xml文件配置数据库批量操作的文章很多,比如这篇http://www.cnblogs.com/xcc ...