文章大纲

一、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. [CVPR2017] Deep Self-Taught Learning for Weakly Supervised Object Localization 论文笔记

    http://openaccess.thecvf.com/content_cvpr_2017/papers/Jie_Deep_Self-Taught_Learning_CVPR_2017_paper. ...

  2. Spring 的IOC和AOP总结

    Spring 的IOC和AOP IOC 1.IOC 许多应用都是通过彼此间的相互合作来实现业务逻辑的,如类A要调用类B的方法,以前我们都是在类A中,通过自身new一个类B,然后在调用类B的方法,现在我 ...

  3. 渐进式Web应用(PWA)入门教程(上)

    最近关于渐进式Web应用有好多讨论,有一些人还在质疑渐进式Web应用是否就是移动端未来. 但在这篇文章中我并不会将渐进式APP和原生的APP进行比较,但有一点是可以肯定的,这两种APP的目标都是使用户 ...

  4. Java EE

  5. Open-Source Service Discovery

    Service discovery is a key component of most distributed systems and service oriented architectures. ...

  6. SSH概念及常用操作汇总

    工作有一段时间了,经常用SSH登录远程机器,但对原理一直不是很了解,所以查阅了一些资料,写个小结. 一. SSH是什么? SSH的全称是Secure Shell, 是一种“用来在不安全的网络上安全地运 ...

  7. Using variables inside Postman and Collection Runner

    Variables are among the most powerful features in Postman. Using variables in your Postman requests, ...

  8. CSDN Android客户端的制作 导航帖

    弄个导航贴,把相关知识来个汇总. CSDN Android的客户端的效果图: 分别通过以下博客进行详细的讲解: 1.Android 使用Fragment,ViewPagerIndicator 制作cs ...

  9. 十九. 想快速开发app,需要找外包吗?

    健生干货分享:第19篇 摘要:最近和两位准备开发app的创业者聊天,他们之前没有移动互联网的相关经验,有的是想法和资金.他们在纠结:想快速开发app,需要找外包吗? 最近和两位想开发app的创业者聊天 ...

  10. Centos7 修改硬件时间和系统时间

    查看系统时间 [root@localhost ~]# date Tue Jun 13 10:20:13 CST 2017 查看硬件时间 [root@localhost ~]# hwclock --sh ...