文章大纲

一、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--

三、项目源码下载

链接:https://pan.baidu.com/s/1kEGfJ3PSoDnsyulCAoimjg
密码:xy0l

 

Android之崩溃日志管理的更多相关文章

  1. 捕android程序崩溃日志

    主要类别: package com.example.callstatus; import java.io.File; import java.io.FileOutputStream; import j ...

  2. android app崩溃日志收集以及上传

    源代码获取请到github:https://github.com/DrJia/AndroidLogCollector 已经做成sdk的形式,源代码已公开,源代码看不懂的请自行google. 假设想定制 ...

  3. Android 简易崩溃日志保存

    仅仅做了简单的保存到了本地而已: 根据需要可以继续增加功能: 下一次启动上传到服务器: 增加应用版本,机型系统版本信息等: public class CrashSaver { public stati ...

  4. 保存android程序崩溃日志到SD卡

    private boolean writeToSDCard(Throwable ex) { boolean isDealing = false; if (Environment.getExternal ...

  5. iOS崩溃日志记录工具--CrashlyTics

    http://try.crashlytics.com Crashlytics优势: 1.Crashlytics基本不会漏掉任何应用崩溃的信息 2.Crashlytics对崩溃日志管理很人性化,会根据崩 ...

  6. 【Android应用开发】 Android 崩溃日志 本地存储 与 远程保存

    示例代码下载 : http://download.csdn.net/detail/han1202012/8638801; 一. 崩溃日志本地存储 1. 保存原理解析 崩溃信息本地保存步骤 : -- 1 ...

  7. android开发之应用Crash自动抓取Log_自动保存崩溃日志到本地

    http://blog.csdn.net/jason0539/article/details/45602655 应用发生crash之后要查看log,判断问题出在什么地方,可是一旦应用发布出去,就要想办 ...

  8. android的Log日志打印管理工具类(一)

    android的Log日志的打印管理工具类: package com.gzcivil.utils; import android.util.Log; /** * 日志打印管理 * * @author ...

  9. android 程序崩溃crash日志的捕捉

    android 程序崩溃crash日志的捕捉 之前在项目开发过程中,一直会遇到程序崩溃了,但是测试組的哥哥们又没及时的导出日志.... 后来在诳群的时候听别人说起,腾讯有那么一个叫bugly的东西 将 ...

随机推荐

  1. python3 爬取qq音乐作者所有单曲 并且下载歌曲

    1 import requests import re import json import os # 便于存放作者的姓名 zuozhe = [] headers = {'User-Agent': ' ...

  2. cw2vec理论及其实现

    导读 本文对AAAI 2018(Association for the Advancement of Artificial Intelligence 2018)高分录用的一篇中文词向量论文(cw2ve ...

  3. SQLServer 导入大脚本文件

    1.cmd 你懂的 2.这里呢得引入一下OSQL,先看看帮助文档:osql -? 3.osql -E -i C:\Users\DNT\Desktop\BigValues.sql-E 表示使用 Wind ...

  4. PAT1079 :Total Sales of Supply Chain

    1079. Total Sales of Supply Chain (25) 时间限制 250 ms 内存限制 65536 kB 代码长度限制 16000 B 判题程序 Standard 作者 CHE ...

  5. 通过VirtualBox安装Linux系统(CentOS7)

    本文目的:创建虚拟系统.在windows系统中通过虚拟工具VirtualBox创建一个虚拟系统CentOS. 备注:(1)版本如下:VirtualBox-5.2.12-122591-Win 和Cent ...

  6. Undoing Merges

    I would like to start writing more here about general Git tips, tricks and upcoming features. There ...

  7. cassandra 在window上的demo

    Cassandra   window使用 1.        下载:http://cassandra.apache.org/download/. 2.        解压后,bin目录下,cassan ...

  8. 后端传Long类型至前端js会出现精度丢失问题

    今天开发遇到个问题,Java后端的Long类型数据,传到前端会出现精度丢失,如:164379764419858435,前端会变成164379764419858430.在浏览器中做测试可知,这就是一个精 ...

  9. 监督学习——logistic进行二分类(python)

    线性回归及sgd/bgd的介绍: 监督学习--随机梯度下降算法(sgd)和批梯度下降算法(bgd) 训练数据形式:          (第一列代表x1,第二列代表 x2,第三列代表 数据标签 用 0/ ...

  10. Azure Devops/Tfs 编译的时候自动修改版本号

    看到阿迪王那边出品了一个基于Azure Devops自增版本号  链接 http://edi.wang/post/2019/3/1/incremental-build-number-for-net-c ...