乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

      android和iOS平臺的崩潰捕獲和收集

       quasiceo 2013-02-20

      android和iOS平臺的崩潰捕獲和收集

      分類: Android開發(fā) IOS游戲開發(fā) IOS應用開發(fā) 503人閱讀 評論(0) 收藏 舉報
              通過崩潰捕獲和收集,可以收集到已發(fā)布應用(游戲)的異常,以便開發(fā)人員發(fā)現(xiàn)和修改bug,對于提高軟件質(zhì)量有著極大的幫助。本文介紹了iOS和 android平臺下崩潰捕獲和收集的原理及步驟,不過如果是個人開發(fā)應用或者沒有特殊限制的話,就不用往下看了,直接把友盟sdk(一個統(tǒng)計分析 sdk)加入到工程中就萬事大吉了,其中的錯誤日志功能完全能夠滿足需求,而且不需要額外準備接收服務器。  但是如果你對其原理更感興趣,或者像我一樣必須要兼容公司現(xiàn)有的bug收集系統(tǒng),那么下面的東西就值得一看了。

             要實現(xiàn)崩潰捕獲和收集的困難主要有這么幾個:

             1、如何捕獲崩潰(比如c++常見的野指針錯誤或是內(nèi)存讀寫越界,當發(fā)生這些情況時程序不是異常退出了嗎,我們?nèi)绾尾东@它呢)

             2、如何獲取堆棧信息(告訴我們崩潰是哪個函數(shù),甚至是第幾行發(fā)生的,這樣我們才可能重現(xiàn)并修改問題)

             3、將錯誤日志上傳到指定服務器(這個最好辦)


              我們先進行一個簡單的綜述。會引發(fā)崩潰的代碼本質(zhì)上就兩類,一個是c++語言層面的錯誤,比如野指針,除零,內(nèi)存訪問異常等等;另一類是未捕獲異常 (Uncaught Exception),iOS下面最常見的就是objective-c的NSException(通過@throw拋出,比如,NSArray訪問元素越 界),android下面就是java拋出的異常了。這些異常如果沒有在最上層try住,那么程序就崩潰了。  無論是iOS還是android系統(tǒng),其底層都是unix或者是類unix系統(tǒng),對于第一類語言層面的錯誤,可以通過信號機制來捕獲(signal或者 是sigaction,不要跟qt的信號插槽弄混了),即任何系統(tǒng)錯誤都會拋出一個錯誤信號,我們可以通過設定一個回調(diào)函數(shù),然后在回調(diào)函數(shù)里面打印并發(fā) 送錯誤日志。

            一、iOS平臺的崩潰捕獲和收集

      1、設置開啟崩潰捕獲

      1. static int s_fatal_signals[] = {  
      2.     SIGABRT,  
      3.     SIGBUS,  
      4.     SIGFPE,  
      5.     SIGILL,  
      6.     SIGSEGV,  
      7.     SIGTRAP,  
      8.     SIGTERM,  
      9.     SIGKILL,  
      10. };  
      11.   
      12. static const char* s_fatal_signal_names[] = {  
      13.     "SIGABRT",  
      14.     "SIGBUS",  
      15.     "SIGFPE",  
      16.     "SIGILL",  
      17.     "SIGSEGV",  
      18.     "SIGTRAP",  
      19.     "SIGTERM",  
      20.     "SIGKILL",  
      21. };  
      22.   
      23. static int s_fatal_signal_num = sizeof(s_fatal_signals) / sizeof(s_fatal_signals[0]);  
      24.   
      25. void InitCrashReport()  
      26. {  
      27.         // 1     linux錯誤信號捕獲  
      28.     for (int i = 0; i < s_fatal_signal_num; ++i) {  
      29.         signal(s_fatal_signals[i], SignalHandler);  
      30.     }  
      31.       
      32.         // 2      objective-c未捕獲異常的捕獲  
      33.     NSSetUncaughtExceptionHandler(&HandleException);  
      34. }  

      在游戲的最開始調(diào)用InitCrashReport()函數(shù)來開啟崩潰捕獲。  注釋1處對應上文所說的第一類崩潰,注釋2處對應objective-c(或者說是UIKit Framework)拋出但是沒有被處理的異常。

      2、打印堆棧信息

      1. + (NSArray *)backtrace  
      2. {  
      3.     void* callstack[128];  
      4.     int frames = backtrace(callstack, 128);  
      5.     char **strs = backtrace_symbols(callstack, frames);  
      6.       
      7.     int i;  
      8.     NSMutableArray *backtrace = [NSMutableArray arrayWithCapacity:frames];  
      9.     for (i = kSkipAddressCount;  
      10.          i < __min(kSkipAddressCount + kReportAddressCount, frames);  
      11.          ++i) {  
      12.         [backtrace addObject:[NSString stringWithUTF8String:strs[i]]];  
      13.     }  
      14.     free(strs);  
      15.       
      16.     return backtrace;  
      17. }  

      幸好,蘋果的iOS系統(tǒng)支持backtrace,通過這個函數(shù)可以直接打印出程序崩潰的調(diào)用堆棧。優(yōu)點是,什么符號函數(shù)表都不需要,也不需要保存發(fā)布出去 的對應版本,直接查看崩潰堆棧。缺點是,不能打印出具體哪一行崩潰,很多問題知道了是哪個函數(shù)崩的,但是還是查不出是因為什么崩的大哭

      3、日志上傳,這個需要看實際需求,比如我們公司就是把崩潰信息http post到一個php服務器。這里就不多做聲明了。

      4、技巧---崩潰后程序保持運行狀態(tài)而不退出

      1. CFRunLoopRef runLoop = CFRunLoopGetCurrent();  
      2.     CFArrayRef allModes = CFRunLoopCopyAllModes(runLoop);  
      3.       
      4.     while (!dismissed)  
      5.     {  
      6.         for (NSString *mode in (__bridge NSArray *)allModes)  
      7.         {  
      8.             CFRunLoopRunInMode((__bridge CFStringRef)mode, 0.001, false);  
      9.         }  
      10.     }  
      11.       
      12.     CFRelease(allModes);  

      在崩潰處理函數(shù)上傳完日志信息后,調(diào)用上述代碼,可以重新構(gòu)建程序主循環(huán)。這樣,程序即便崩潰了,依然可以正常運行(當然,這個時候是處于不穩(wěn)定狀態(tài),但是由于手持游戲和應用大多是短期操作,不會有掛機這種說法,所以穩(wěn)定與否就無關緊要了)。玩家甚至感受不到崩潰。

      這里要在說明一個感念,那就是“可重入(reentrant)”。 簡單來說,當我們的崩潰回調(diào)函數(shù)是可重入的時候,那么再次發(fā)生崩潰的時候,依然可以正常運行這個新的函數(shù);但是如果是不可重入的,則無法運行(這個時候就 徹底死了)。要實現(xiàn)上面描述的效果,并且還要保證回調(diào)函數(shù)是可重入的幾乎不可能。所以,我測試的結(jié)果是,objective-c的異常觸發(fā)多少次都可以正 常運行。但是如果多次觸發(fā)錯誤信號,那么程序就會卡死。  所以要慎重決定是否要應用這個技巧。


      二、android崩潰捕獲和收集

      1、android開啟崩潰捕獲

            首先是java代碼的崩潰捕獲,這個可以仿照最下面的完整代碼寫一個UncaughtExceptionHandler,然后在所有的Activity的onCreate函數(shù)最開始調(diào)用

      Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler(this));

            這樣,當發(fā)生崩潰的時候,就會自動調(diào)用UncaughtExceptionHandler的public void uncaughtException(Thread thread, Throwable exception)函數(shù),其中的exception包含堆棧信息,我們可以在這個函數(shù)里面打印我們需要的信息,并且上傳錯誤日志

          然后是重中之重,jni的c++代碼如何進行崩潰捕獲。

      1. void InitCrashReport()  
      2. {  
      3.     CCLOG("InitCrashReport");  
      4.   
      5.     // Try to catch crashes...  
      6.     struct sigaction handler;  
      7.     memset(&handler, 0, sizeof(struct sigaction));  
      8.   
      9.     handler.sa_sigaction = android_sigaction;  
      10.     handler.sa_flags = SA_RESETHAND;  
      11.   
      12. #define CATCHSIG(X) sigaction(X, &handler, &old_sa[X])  
      13.     CATCHSIG(SIGILL);  
      14.     CATCHSIG(SIGABRT);  
      15.     CATCHSIG(SIGBUS);  
      16.     CATCHSIG(SIGFPE);  
      17.     CATCHSIG(SIGSEGV);  
      18.     CATCHSIG(SIGSTKFLT);  
      19.     CATCHSIG(SIGPIPE);  
      20. }  
      通過singal的設置,當崩潰發(fā)生的時候就會調(diào)用android_sigaction函數(shù)。這同樣是linux的信號機制。 此處設置信號回調(diào)函數(shù)的代碼跟iOS有點不同,這個只是同一個功能的兩種不同寫法,沒有本質(zhì)區(qū)別。有興趣的可以google下兩者的區(qū)別。

      2、打印堆棧

            java語法可以直接通過exception獲取到堆棧信息,但是jni代碼不支持backtrace,那么我們?nèi)绾潍@取堆棧信息呢?    這里有個我想嘗試的新方法,就是使用google breakpad,貌似它現(xiàn)在完整的跨平臺了(支持windows, mac, linux, iOS和android等),它自己實現(xiàn)了一套minidump,在android上面限制會小很多。  但是這個庫有些大,估計要加到我們的工程中不是一件非常容易的事,所以我們還是使用了簡潔的“傳統(tǒng)”方案。 思路是,當發(fā)生崩潰的時候,在回調(diào)函數(shù)里面調(diào)用一個我們在Activity寫好的靜態(tài)函數(shù)。在這個函數(shù)里面通過執(zhí)行命令獲取logcat的輸出信息(輸出 信息里面包含了jni的崩潰地址),然后上傳這個崩潰信息。  當我們獲取到崩潰信息后,可以通過arm-linux-androideabi-addr2line(具體可能不是這個名字,在android ndk里面搜索*addr2line,找到實際的程序)解析崩潰信息。

            jni的崩潰回調(diào)函數(shù)如下:

      1. void android_sigaction(int signal, siginfo_t *info, void *reserved)  
      2. {  
      3.     if (!g_env) {  
      4.         return;  
      5.     }  
      6.   
      7.     jclass classID = g_env->FindClass(CLASS_NAME);  
      8.     if (!classID) {  
      9.         return;  
      10.     }  
      11.   
      12.     jmethodID methodID = g_env->GetStaticMethodID(classID, "onNativeCrashed""()V");  
      13.     if (!methodID) {  
      14.         return;  
      15.     }  
      16.   
      17.     g_env->CallStaticVoidMethod(classID, methodID);  
      18.   
      19.     old_sa[signal].sa_handler(signal);  
      20. }  

      可以看到,我們僅僅是通過jni調(diào)用了java的一個函數(shù),然后所有的處理都是在java層面完成。

      java對應的函數(shù)實現(xiàn)如下:

      1. public static void onNativeCrashed() {  
      2.         // http:///questions/1083154/how-can-i-catch-sigsegv-segmentation-fault-and-get-a-stack-trace-under-jni-on-a  
      3.         Log.e("handller""handle");  
      4.         new RuntimeException("crashed here (native trace should follow after the Java trace)").printStackTrace();  
      5.         s_instance.startActivity(new Intent(s_instance, CrashHandler.class));  
      6.     }  

      我們開啟了一個新的activity,因為當jni發(fā)生崩潰的時候,原始的activity可能已經(jīng)結(jié)束掉了。  這個新的activity實現(xiàn)如下:
      1. public class CrashHandler extends Activity  
      2. {  
      3.     public static final String TAG = "CrashHandler";  
      4.     protected void onCreate(Bundle state)  
      5.     {  
      6.         super.onCreate(state);  
      7.         setTitle(R.string.crash_title);  
      8.         setContentView(R.layout.crashhandler);  
      9.         TextView v = (TextView)findViewById(R.id.crashText);  
      10.         v.setText(MessageFormat.format(getString(R.string.crashed), getString(R.string.app_name)));  
      11.         final Button b = (Button)findViewById(R.id.report),  
      12.               c = (Button)findViewById(R.id.close);  
      13.         b.setOnClickListener(new View.OnClickListener(){  
      14.             public void onClick(View v){  
      15.                 final ProgressDialog progress = new ProgressDialog(CrashHandler.this);  
      16.                 progress.setMessage(getString(R.string.getting_log));  
      17.                 progress.setIndeterminate(true);  
      18.                 progress.setCancelable(false);  
      19.                 progress.show();  
      20.                 final AsyncTask task = new LogTask(CrashHandler.this, progress).execute();  
      21.                 b.postDelayed(new Runnable(){  
      22.                     public void run(){  
      23.                         if (task.getStatus() == AsyncTask.Status.FINISHED)  
      24.                             return;  
      25.                         // It's probably one of these devices where some fool broke logcat.  
      26.                         progress.dismiss();  
      27.                         task.cancel(true);  
      28.                         new AlertDialog.Builder(CrashHandler.this)  
      29.                             .setMessage(MessageFormat.format(getString(R.string.get_log_failed), getString(R.string.author_email)))  
      30.                             .setCancelable(true)  
      31.                             .setIcon(android.R.drawable.ic_dialog_alert)  
      32.                             .show();  
      33.                     }}, 3000);  
      34.             }});  
      35.         c.setOnClickListener(new View.OnClickListener(){  
      36.             public void onClick(View v){  
      37.                 finish();  
      38.             }});  
      39.     }  
      40.   
      41.     static String getVersion(Context c)  
      42.     {  
      43.         try {  
      44.             return c.getPackageManager().getPackageInfo(c.getPackageName(),0).versionName;  
      45.         } catch(Exception e) {  
      46.             return c.getString(R.string.unknown_version);  
      47.         }  
      48.     }  
      49. }  
      50.   
      51. class LogTask extends AsyncTask<Void, Void, Void>  
      52. {  
      53.     Activity activity;  
      54.     String logText;  
      55.     Process process;  
      56.     ProgressDialog progress;   
      57.   
      58.     LogTask(Activity a, ProgressDialog p) {  
      59.         activity = a;  
      60.         progress = p;  
      61.     }  
      62.   
      63.     @Override  
      64.     protected Void doInBackground(Void... v) {  
      65.         try {  
      66.             Log.e("crash""doInBackground begin");  
      67.             process = Runtime.getRuntime().exec(new String[]{"logcat","-d","-t","500","-v","threadtime"});  
      68.             logText = UncaughtExceptionHandler.readFromLogcat(process.getInputStream());  
      69.             Log.e("crash""doInBackground end");  
      70.         } catch (IOException e) {  
      71.             e.printStackTrace();  
      72.             Toast.makeText(activity, e.toString(), Toast.LENGTH_LONG).show();  
      73.         }  
      74.         return null;  
      75.     }  
      76.   
      77.     @Override  
      78.     protected void onCancelled() {  
      79.         Log.e("crash""onCancelled");  
      80.         process.destroy();  
      81.     }  
      82.   
      83.     @Override  
      84.     protected void onPostExecute(Void v) {  
      85.         Log.e("crash""onPostExecute");  
      86.         progress.setMessage(activity.getString(R.string.starting_email));  
      87.         UncaughtExceptionHandler.sendLog(logText, activity);  
      88.         progress.dismiss();  
      89.         activity.finish();  
      90.         Log.e("crash""onPostExecute over");  
      91.     }  

      最主要的地方是doInBackground函數(shù),這個函數(shù)通過logcat獲取了崩潰信息。 不要忘記在AndroidManifest.xml添加讀取LOG的權限

      1. <uses-permission android:name="android.permission.READ_LOGS" />  

      3、獲取到錯誤日志后,就可以寫到sd卡(同樣不要忘記添加權限),或者是上傳。  代碼很容易google到,不多說了。  最后再說下如何解析這個錯誤日志。

      我們在獲取到的錯誤日志中,可以截取到如下信息:

      1. 12-12 20:41:31.807 24206 24206 I DEBUG   :   
      2. 12-12 20:41:31.847 24206 24206 I DEBUG   :          #00  pc 004931f8  /data/data/org.cocos2dx.wing/lib/libhelloworld.so  
      3. 12-12 20:41:31.847 24206 24206 I DEBUG   :          #01  pc 005b3a5e  /data/data/org.cocos2dx.wing/lib/libhelloworld.so  
      4. 12-12 20:41:31.847 24206 24206 I DEBUG   :          #02  pc 005aab68  /data/data/org.cocos2dx.wing/lib/libhelloworld.so  
      5. 12-12 20:41:31.847 24206 24206 I DEBUG   :          #03  pc 005ad8aa  /data/data/org.cocos2dx.wing/lib/libhelloworld.so  
      6. 12-12 20:41:31.847 24206 24206 I DEBUG   :          #04  pc 005924a4  /data/data/org.cocos2dx.wing/lib/libhelloworld.so  
      7. 12-12 20:41:31.847 24206 24206 I DEBUG   :          #05  pc 005929b6  /data/data/org.cocos2dx.wing/lib/libhelloworld.so  

      1. 004931f8  
      這 個就是我們崩潰函數(shù)的地址,  libhelloworld.so就是崩潰的動態(tài)庫。我們要使用addr2line對這個動態(tài)庫進行解析(注意要是obj/local目錄下的那個比較 大的,含有符號文件的動態(tài)庫,不是Libs目錄下比較小的,同時發(fā)布版本時,這個動態(tài)庫也要保存好,之后查log都要有對應的動態(tài)庫)。命令如下:

      arm-linux-androideabi-addr2line.exe -e 動態(tài)庫名稱  崩潰地址

      例如:

      1. $ /cygdrive/d/devandroid/android-ndk-r8c-windows/android-ndk-r8c/toolchains/arm-linux-androideabi-4.6/prebuilt/windows/bin/arm-linux-androideabi-addr2line.exe -e obj/local/armeabi-v7a/libhelloworld.so 004931f8  
      得到的結(jié)果就是哪個cpp文件第幾行崩潰。  如果動態(tài)庫信息不對,返回的就是 ?:0




        本站是提供個人知識管理的網(wǎng)絡存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權內(nèi)容,請點擊一鍵舉報。
        轉(zhuǎn)藏 分享 獻花(0

        0條評論

        發(fā)表

        請遵守用戶 評論公約

        類似文章 更多