咱們寫程序的時候都但願能寫出一個沒有任何Bug的程序,指望在任何狀況下都不會發生程序崩潰。不過理想是豐滿的,現實是骨感的。沒有一個程序員能保證本身寫的程序絕對不會出現異常崩潰。特別是針對用戶數達到幾十萬幾百萬的程序,當你用戶數達到必定數量級後,就算你的程序出現個別異常崩潰狀況也不用驚訝。此時及時收集用戶的日誌成了解決問題的關鍵。本文從兩種方式分析查看日誌的方式:java
一、在自測階段或者交給測試部測試階段出現了:android
1,程序異常退出 , uncaused exception
2,程序強制關閉 ,Force Closed (簡稱FC)
3,程序無響應 , Application No Response (簡稱ANR) , 順便,通常主線程超過5秒麼有處理就會ANR程序員
將終端的/data/anr/traces.txt文件取出(命令如:adb pull /data/anr/traces.txt C:\)拿到日誌文件打開後一看"哇"好長,可是不要懼怕log日誌雖長,但其實它由三大塊兒組成:數組
一、系統基本信息 ,包括 內存,CPU ,進程隊列 ,虛擬內存 , 垃圾回收等信息 。
Heap: 4% free, 66MB/69MB; 164920 objects
Dumping cumulative Gc timings
Total number of allocations 617049
Total bytes allocated 86MB
Free memory 3MB
Free memory until GC 3MB
Free memory until OOME 189MB
Total memory 69MB
Max memory 256MB
Total mutator paused time: 0
Total time waiting for GC to complete: 598.247us服務器
二、時間信息 , 也是咱們主要分析的信息 。
----- pid 17845 at 2016-10-19 10:59:32 -----網絡
三、虛擬機信息 , 包括進程的,線程的跟蹤信息,這是用來跟蹤進程和線程具體點的好地方 。app
----- pid 17845 at 2016-10-19 10:59:32 -----
Cmd line: com.XX.XXide
DALVIK THREADS (19):
"main" prio=5 tid=1 WaitingForDebuggerSend
| group="main" sCount=2 dsCount=1 obj=0x87800ed0 self=0x7f85ca6000
| sysTid=17845 nice=0 cgrp=default sched=0/0 handle=0x7f89a63e30
| state=S schedstat=( 0 0 0 ) utm=54 stm=14 core=0 HZ=100
| stack=0x7fc2029000-0x7fc202b000 stackSize=8MB
| held mutexes=
kernel: futex_wait_queue_me+0xd4/0x12c
kernel: futex_wait+0xd8/0x1cc
kernel: do_futex+0xc8/0x8d0
kernel: SyS_futex+0xf8/0x174
kernel: __sys_trace+0x30/0x34
native: #00 pc 00019d94 /system/lib64/libc.so (syscall+28)
native: #01 pc 000d7c00 /system/lib64/libart.so (art::ConditionVariable::Wait(art::Thread*)+140)
native: #02 pc 0032ea88 /system/lib64/libart.so (art::ThreadList::SuspendSelfForDebugger()+268)
native: #03 pc 002311a4 /system/lib64/libart.so (art::JDWP::JdwpState::SuspendByPolicy(art::JDWP::JdwpSuspendPolicy, unsigned long)+144)
native: #04 pc 002319d0 /system/lib64/libart.so (art::JDWP::JdwpState::SendRequestAndPossiblySuspend(art::JDWP::ExpandBuf*, art::JDWP::JdwpSuspendPolicy, unsigned long)+632)
native: #05 pc 00236360 /system/lib64/libart.so (art::JDWP::JdwpState::PostClassPrepare(art::mirror::Class*)+912)
native: #06 pc 0011c218 /system/lib64/libart.so (art::ClassLinker::DefineClass(art::Thread*, char const*, unsigned long, art::Handle<art::mirror::ClassLoader>, art::DexFile const&, art::DexFile::ClassDef const&) (.part.430)+1000)
native: #07 pc 0011c8dc /system/lib64/libart.so (art::ClassLinker::FindClassInPathClassLoader(art::ScopedObjectAccessAlreadyRunnable&, art::Thread*, char const*, unsigned long, art::Handle<art::mirror::ClassLoader>)+748)
native: #08 pc 0011ce4c /system/lib64/libart.so (art::ClassLinker::FindClass(art::Thread*, char const*, art::Handle<art::mirror::ClassLoader>) (.part.431)+884)
native: #09 pc 00121180 /system/lib64/libart.so (art::ClassLinker::ResolveType(art::DexFile const&, unsigned short, art::Handle<art::mirror::DexCache>, art::Handle<art::mirror::ClassLoader>)+244)
native: #10 pc 003a7148 /system/lib64/libart.so (artInitializeTypeFromCode+348)
native: #11 pc 000c9a6c /system/lib64/libart.so (art_quick_initialize_type+60)
native: #12 pc 002e5560 /data/dalvik-cache/arm64/data@app@com.XX.XX-2@base.apk@classes.dex (Java_com_tima_www_driver_stationquery_StationQueryActivity_000241_onItemClick__Landroid_widget_AdapterView_2Landroid_view_View_2IJ+196)
at com.XX.XX.stationquery.StationQueryActivity$1.onItemClick(StationQueryActivity.java:73)
at android.widget.AdapterView.performItemClick(AdapterView.java:339)
at android.widget.AbsListView.performItemClick(AbsListView.java:1544)
at android.widget.AbsListView$PerformClick.run(AbsListView.java:3721)
at android.widget.AbsListView$3.run(AbsListView.java:5660)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:145)
at android.app.ActivityThread.main(ActivityThread.java:6837)
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:1404)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1199)
從這麼多大堆棧裏如何分析出關鍵問題呢:oop
1,若是是ANR問題 , 則搜索「ANR」關鍵詞 。 快速定位到關鍵事件信息 。
2,若是是ForceClosed 和其它異常退出信息,則搜索"Fatal" 關鍵詞, 快速定位到關鍵事件信息 。
3,定位到關鍵事件信息後 , 若是信息不夠明確的,再去搜索應用程序包的虛擬機信息 ,查看具體的進程和線程跟蹤的日誌,來定位到代碼測試
該方式適合可以及時拿到終端的狀況下進行日誌分析,那若是是用戶的終端該如何及時分析bug日誌呢,採用第二種方法以下
二、應用程序出現問題時採用HTTP的方式將日誌及時上報到後臺進行分析:
看了網上大部分是採用日誌收集的第三方jar包來完成的,還有一種是自定義一個本身的CrashHandler實現UncaughtExceptionHandler接口來捕獲閃退信息而後上傳到本身的服務器。這樣的實現方式總以爲若是用戶誤刪了閃退的日誌文件那麼就會致使沒法及時上報閃退日誌,也就無從分析隱藏的Bug了,因而採用另外一種實現思路,即:在程序發生異常時提醒用戶發生了什麼樣的異常,同時把本次捕獲的Exception的字段寫入到本身定義的log文件中,而後上報異常字段到本身的異常服務器上,從手機端或者後臺均可以看到發生的異常堆棧。日誌記錄系統不借助與任何第三方jar包。說幹就幹,
項目目錄以下:
關鍵代碼以下:
一、定義IExceptionHandler接口
<pre name="code" class="java">/**
*異常處理類
*
*/
public interface IExceptionHandler {
/**
* 異常處理類.
* @param t
* @param db
*/
public String handlerException(Throwable t, SQLiteDatabase db);
/**
* 異常處理類.
* @param t
*/
public String handlerException(Throwable t);
/**
* 異常處理類.
* @param t
*/
public void sendException(Throwable t);
/**
* 給用戶提示
* @param errorMsg
*/
public void showTipMessgae(String errorMsg);
}
二、定義ExceptionHandler實現自IExceptionHandler
/**
* 異常處理類
*/
public class ExceptionHandler implements IExceptionHandler{
private Context context;
private ExceptionSender exceptionSender;
public ExceptionHandler(Context context)
{
this.context = context;
exceptionSender = new ExceptionSender(context);
}
@Override
public String handlerException(Throwable t) {
return handlerException(t, null);
}
@Override
public String handlerException(Throwable exception, SQLiteDatabase db) {
String message = "";
if (exception instanceof ConnectException)
{
message = "網絡鏈接有問題!";
}
else if (exception instanceof SocketTimeoutException)
{
message = "鏈接超時!";
}
else if (exception instanceof NullPointerException)
{
message = "程序出錯,空指針錯誤!";
}
else if (exception instanceof IllegalArgumentException)
{
message = "程序出錯,錯誤的請求參數!";
}
else if (exception instanceof ArithmeticException)
{
message = "程序出錯,數值計算出錯!";
}
else if (exception instanceof NetworkOnMainThreadException)
{
message = "程序出錯,網絡請求放在主線程中運行!";
}
else if (exception instanceof IllegalStateException)
{
message = "程序出錯,網絡請求要放在主線程中運行!";
}
else if (exception instanceof IndexOutOfBoundsException)
{
message = "數組越界異常!";
}
else if (exception instanceof IOException)
{
message = "程序出錯,IO錯誤!";
}
else
{
message = exception.getMessage();
}
showTipMessgae(message);
exceptionSender.send(exception);
return message;
}
@Override
public void sendException(Throwable t) {
exceptionSender.send(t);
}
@Override
public void showTipMessgae(final String msg)
{
if (context != null)
{
Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();
}
}
}
三、在AndroidManifest.xml裏面配置本身的Application:IApplication,並在裏面初始化app日誌文件目錄
public class IApplication extends Application{
/**
* 應用根目錄
*/
private File rootFile;
/**
* log目錄
*/
private File logFile;
private static final SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
private static IApplication INSTANCE = null;
public static IApplication getInstance()
{
return INSTANCE;
}
private FileLog fileLog;
@Override
public void onCreate() {
super.onCreate();
INSTANCE = this;
initDataRoot();
File log = new File(logFile, df.format(new Date()) + ".log");
PrintWriter pw = null;
try {
pw = new PrintWriter(new FileOutputStream(log, true), true);
} catch (FileNotFoundException e1) {
}
fileLog = new FileLog(pw);
fileLog.setLevel(FileLog.LEVEL_INFO);//設置日誌系統的等級
CrashHandler crashHandler = CrashHandler.getInstance();
crashHandler.init(this);
}
/**
* 初始化log目錄
*/
private void initDataRoot() {
String state = android.os.Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state)) {
rootFile =new File(Environment.getExternalStorageDirectory(),"11");
if (!rootFile.exists()) {
rootFile.mkdirs();
}
}else {
rootFile = getDatabasePath("11");
}
logFile = new File(rootFile, "logs");
if (!logFile.exists()) {
logFile.mkdirs();
}
}
public FileLog getFileLog() {
return fileLog;
}
}
四、採用HTTP方式將實時catch的異常發送到異常服務器,並將異常的堆棧信息寫入到SDcard中
<pre name="code" class="java">/**
* 異常發送類
*/
public class ExceptionSender {
private Context context;
private String url="你的收集異常信息的服務器的地址";
//版本名稱
private String app_name;
//版本號
private String app_version;
//設備名稱
private String device_name;
//操做系統
private String os_name;
//操做系統版本號
private String os_version;
private ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
private FileLog fileLog;
public ExceptionSender(Context context)
{
init(context);
this.context = context;
this.fileLog = IApplication.getInstance().getFileLog();
}
private void init(Context context)
{
PackageInfo packInfo;
try
{
packInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
if (packInfo != null)
{
app_name = packInfo.packageName;
app_version = packInfo.versionName;
}
device_name = android.os.Build.MODEL;
os_name = "Android";
os_version = android.os.Build.VERSION.RELEASE;
}
catch (PackageManager.NameNotFoundException e)
{
}
}
/**
* 發送錯誤信息到錯誤收集中心.
*
* @param ex
*/
public void send(final Throwable ex)
{
fileLog.e(ex.getClass().getSimpleName(), ex);
cachedThreadPool.execute(new Runnable() {
@Override
public void run() {
try
{
DefaultHttpClient httpClient = new DefaultHttpClient();
HttpPost httpPost = new HttpPost(url);
ByteArrayOutputStream out = new ByteArrayOutputStream();
PrintStream ps = new PrintStream(out);
ex.printStackTrace(ps);
ps.flush();
String err_msg = new String(out.toByteArray(), "UTF-8");
Map<String, String> params = new HashMap<String, String>();
List<NameValuePair> parameters = new ArrayList<NameValuePair>();
addParameter(params, parameters, "class_name", ex.getClass().getSimpleName());
addParameter(params, parameters, "app_name", app_name);
addParameter(params, parameters, "app_version", app_version);
addParameter(params, parameters, "device_name", device_name);
addParameter(params, parameters, "os_name", os_name);
addParameter(params, parameters, "os_version", os_version);
parameters.add(new BasicNameValuePair("err_msg", err_msg));
parameters.add(new BasicNameValuePair("stack_msg", ""));
addParameter(params, parameters, "error_time", Long.toString(System.currentTimeMillis()));
httpPost.setEntity(new UrlEncodedFormEntity(parameters, HTTP.UTF_8));
httpClient.execute(httpPost);
}
catch (Throwable e)
{
}
}
});
}
private void addParameter(Map<String, String> params, List<NameValuePair> parameters, String key, String value)
{
value = value == null ? "" : value;
params.put(key, value);
parameters.add(new BasicNameValuePair(key, value));
}
}
四、使用方式:在程序裏面採用try catch捕獲可能會出現的異常的代碼塊(界面給出提示,程序不閃退),另外一種是沒有進行try catch應用程序直接閃退
<pre name="code" class="java">public class MainActivity extends AppCompatActivity implements View.OnClickListener{
private IExceptionHandler exceptionHandler;
private Button btn1, btn2;
private Student student;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
exceptionHandler = new ExceptionHandler(getApplicationContext());
btn1 = (Button)findViewById(R.id.button1);
btn1.setOnClickListener(this);
btn2 = (Button)findViewById(R.id.button2);
btn2.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.button1:
try{//捕獲了異常,異常的堆棧信息寫入了SDcard中同時也上報到了後臺
int m = 1/0;
System.out.print(m);
}catch (Exception e){
exceptionHandler..handlerException(e);
}
break;
case R.id.button2://沒有捕獲異常,應用程序直接退了,異常的堆棧信息寫入了SDcard中同時也上報到了後臺
System.out.print(student.getId());
break;
}
}
}
這樣友好的提示也給了用戶,異常信息也及時上報了。可是該方式也有本身的缺點:大量的try catch會致使代碼的效率不高。 詳細代碼點擊下載 --------------------- 做者:Ztw2017 來源:CSDN 原文:https://blog.csdn.net/zhangteng22/article/details/52947992 版權聲明:本文爲博主原創文章,轉載請附上博文連接!