अप्रयुक्त अपवाद को संभालने और लॉग फ़ाइल भेजने की आवश्यकता है


114

अद्यतन: कृपया नीचे "स्वीकृत" समाधान देखें

जब मेरा ऐप केवल समाप्त करने के बजाय एक अपवादित अपवाद बनाता है, तो मैं सबसे पहले उपयोगकर्ता को एक लॉग फ़ाइल भेजने का अवसर देना चाहूंगा। मुझे लगता है कि एक यादृच्छिक अपवाद प्राप्त करने के बाद अधिक काम करना जोखिम भरा है लेकिन, हे, सबसे खराब अनुप्रयोग समाप्त हो रहा है और लॉग फ़ाइल नहीं भेजी जाती है। यह मुझे उम्मीद की तुलना में मुश्किल हो रहा है :)

क्या काम करता है: (1) अनकैप्ड अपवाद को फँसाना, (2) लॉग जानकारी निकालने और किसी फ़ाइल में लिखना।

क्या अभी तक काम नहीं करता है: (3) ईमेल भेजने के लिए एक गतिविधि शुरू करना। अंत में, मेरे पास उपयोगकर्ता की अनुमति पूछने के लिए एक और गतिविधि होगी। यदि मुझे ईमेल गतिविधि काम कर रही है, तो मुझे दूसरे के लिए बहुत परेशानी की उम्मीद नहीं है।

समस्या की जड़ यह है कि मेरे अनुप्रयोग वर्ग में अनहेल्दी अपवाद पकड़ा गया है। चूंकि यह कोई गतिविधि नहीं है, इसलिए यह स्पष्ट नहीं है कि Intent.ACTION_SEND के साथ गतिविधि कैसे शुरू करें। यही है, आम तौर पर एक गतिविधि शुरू करने के लिए एक कॉल startActivity और onActivityResult के साथ फिर से शुरू होती है। ये विधियाँ गतिविधि द्वारा समर्थित हैं, लेकिन अनुप्रयोग द्वारा नहीं।

इस संबंध में कोई सुझाव कि इसे कैसे किया जा सकता है?

शुरुआती गाइड के रूप में यहां कुछ कोड स्निप हैं:

public class MyApplication extends Application
{
  defaultUncaughtHandler = Thread.getDefaultUncaughtExceptionHandler();
  public void onCreate ()
  {
    Thread.setDefaultUncaughtExceptionHandler (new Thread.UncaughtExceptionHandler()
    {
      @Override
      public void uncaughtException (Thread thread, Throwable e)
      {
        handleUncaughtException (thread, e);
      }
    });
  }

  private void handleUncaughtException (Thread thread, Throwable e)
  {
    String fullFileName = extractLogToFile(); // code not shown

    // The following shows what I'd like, though it won't work like this.
    Intent intent = new Intent (Intent.ACTION_SEND);
    intent.setType ("plain/text");
    intent.putExtra (Intent.EXTRA_EMAIL, new String[] {"me@mydomain.com"});
    intent.putExtra (Intent.EXTRA_SUBJECT, "log file");
    intent.putExtra (Intent.EXTRA_STREAM, Uri.parse ("file://" + fullFileName));
    startActivityForResult (intent, ACTIVITY_REQUEST_SEND_LOG);
  }

  public void onActivityResult (int requestCode, int resultCode, Intent data)
  {
    if (requestCode == ACTIVITY_REQUEST_SEND_LOG)
      System.exit(1);
  }
}

4
व्यक्तिगत रूप से मैं सिर्फ एसीआरए का उपयोग करता हूं , हालांकि यह खुला स्रोत है ताकि आप जांच सकें कि वे इसे कैसे करते हैं ...
डेविड ओ'मैरा

जवाबों:


240

यहां पूरा समाधान है (लगभग: मैंने यूआई लेआउट और बटन हैंडलिंग को छोड़ दिया है) - बहुत से प्रयोग और विभिन्न पोस्ट से जुड़े मुद्दों से संबंधित हैं जो रास्ते में आए थे।

कई चीजें हैं जो आपको करने की आवश्यकता है:

  1. अपने एप्लिकेशन उपवर्ग में बिना किसी अपवाद के हैंडल करें।
  2. एक अपवाद को पकड़ने के बाद, उपयोगकर्ता को लॉग भेजने के लिए कहने के लिए एक नई गतिविधि शुरू करें।
  3. लॉगकैट की फाइलों से लॉग जानकारी निकालें और अपनी खुद की फाइल पर लिखें।
  4. एक ईमेल ऐप शुरू करें, जो आपकी फ़ाइल को अटैचमेंट के रूप में प्रदान करे।
  5. प्रकट: अपने अपवाद हैंडलर द्वारा पहचानी जाने वाली अपनी गतिविधि को फ़िल्टर करें।
  6. वैकल्पिक रूप से, सेटअप प्रोटीज को Log.d () और Log.v () से अलग करने के लिए।

अब, यहाँ विवरण हैं:

(1 और 2) हैंडल अनक्रेडिसेप्शन, प्रारंभ लॉग गतिविधि भेजें:

public class MyApplication extends Application
{
  public void onCreate ()
  {
    // Setup handler for uncaught exceptions.
    Thread.setDefaultUncaughtExceptionHandler (new Thread.UncaughtExceptionHandler()
    {
      @Override
      public void uncaughtException (Thread thread, Throwable e)
      {
        handleUncaughtException (thread, e);
      }
    });
  }

  public void handleUncaughtException (Thread thread, Throwable e)
  {
    e.printStackTrace(); // not all Android versions will print the stack trace automatically

    Intent intent = new Intent ();
    intent.setAction ("com.mydomain.SEND_LOG"); // see step 5.
    intent.setFlags (Intent.FLAG_ACTIVITY_NEW_TASK); // required when starting from Application
    startActivity (intent);

    System.exit(1); // kill off the crashed app
  }
}

(3) एक्स्ट्रेक्ट लॉग (मैंने इसे एक मेरा SendLog गतिविधि डाला):

private String extractLogToFile()
{
  PackageManager manager = this.getPackageManager();
  PackageInfo info = null;
  try {
    info = manager.getPackageInfo (this.getPackageName(), 0);
  } catch (NameNotFoundException e2) {
  }
  String model = Build.MODEL;
  if (!model.startsWith(Build.MANUFACTURER))
    model = Build.MANUFACTURER + " " + model;

  // Make file name - file must be saved to external storage or it wont be readable by
  // the email app.
  String path = Environment.getExternalStorageDirectory() + "/" + "MyApp/";
  String fullName = path + <some name>;

  // Extract to file.
  File file = new File (fullName);
  InputStreamReader reader = null;
  FileWriter writer = null;
  try
  {
    // For Android 4.0 and earlier, you will get all app's log output, so filter it to
    // mostly limit it to your app's output.  In later versions, the filtering isn't needed.
    String cmd = (Build.VERSION.SDK_INT <= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) ?
                  "logcat -d -v time MyApp:v dalvikvm:v System.err:v *:s" :
                  "logcat -d -v time";

    // get input stream
    Process process = Runtime.getRuntime().exec(cmd);
    reader = new InputStreamReader (process.getInputStream());

    // write output stream
    writer = new FileWriter (file);
    writer.write ("Android version: " +  Build.VERSION.SDK_INT + "\n");
    writer.write ("Device: " + model + "\n");
    writer.write ("App version: " + (info == null ? "(null)" : info.versionCode) + "\n");

    char[] buffer = new char[10000];
    do 
    {
      int n = reader.read (buffer, 0, buffer.length);
      if (n == -1)
        break;
      writer.write (buffer, 0, n);
    } while (true);

    reader.close();
    writer.close();
  }
  catch (IOException e)
  {
    if (writer != null)
      try {
        writer.close();
      } catch (IOException e1) {
      }
    if (reader != null)
      try {
        reader.close();
      } catch (IOException e1) {
      }

    // You might want to write a failure message to the log here.
    return null;
  }

  return fullName;
}

(4) एक ईमेल ऐप शुरू करें (मेरी SendLog गतिविधि में भी):

private void sendLogFile ()
{
  String fullName = extractLogToFile();
  if (fullName == null)
    return;

  Intent intent = new Intent (Intent.ACTION_SEND);
  intent.setType ("plain/text");
  intent.putExtra (Intent.EXTRA_EMAIL, new String[] {"log@mydomain.com"});
  intent.putExtra (Intent.EXTRA_SUBJECT, "MyApp log file");
  intent.putExtra (Intent.EXTRA_STREAM, Uri.parse ("file://" + fullName));
  intent.putExtra (Intent.EXTRA_TEXT, "Log file attached."); // do this so some email clients don't complain about empty body.
  startActivity (intent);
}

(3 और 4) यहाँ SendLog कैसा दिखता है (आपको यूआई जोड़ना होगा, हालाँकि):

public class SendLog extends Activity implements OnClickListener
{
  @Override
  public void onCreate(Bundle savedInstanceState)
  {
    super.onCreate(savedInstanceState);
    requestWindowFeature (Window.FEATURE_NO_TITLE); // make a dialog without a titlebar
    setFinishOnTouchOutside (false); // prevent users from dismissing the dialog by tapping outside
    setContentView (R.layout.send_log);
  }

  @Override
  public void onClick (View v) 
  {
    // respond to button clicks in your UI
  }

  private void sendLogFile ()
  {
    // method as shown above
  }

  private String extractLogToFile()
  {
    // method as shown above
  }
}

(5) प्रकट:

<manifest xmlns:android="http://schemas.android.com/apk/res/android" ... >
    <!-- needed for Android 4.0.x and eariler -->
    <uses-permission android:name="android.permission.READ_LOGS" /> 

    <application ... >
        <activity
            android:name="com.mydomain.SendLog"
            android:theme="@android:style/Theme.Dialog"
            android:textAppearance="@android:style/TextAppearance.Large"
            android:windowSoftInputMode="stateHidden">
            <intent-filter>
              <action android:name="com.mydomain.SEND_LOG" />
              <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>
     </application>
</manifest>

(6) सेटअप रक्षक:

Project.properties में, कॉन्फ़िग लाइन बदलें। आपको "ऑप्टिमाइज़" निर्दिष्ट करना होगा या प्रोगार्ड Log.v () और Log.d () कॉल को नहीं हटाएगा।

proguard.config=${sdk.dir}/tools/proguard/proguard-android-optimize.txt:proguard-project.txt

Proguard-project.txt में, निम्नलिखित जोड़ें। यह बताता है कि Log.v और Log.d मानने के लिए Proguard का कोई दुष्प्रभाव नहीं है (भले ही वे लॉग लिखने के बाद से करते हैं) और इस प्रकार अनुकूलन के दौरान हटाया जा सकता है:

-assumenosideeffects class android.util.Log {
    public static int v(...);
    public static int d(...);
}

बस! यदि आपके पास इसमें सुधार के लिए कोई सुझाव है, तो कृपया मुझे बताएं और मैं इसे अपडेट कर सकता हूं।


10
बस एक नोट: कभी भी System.exit को कॉल न करें। आप बिना किसी अपवाद के हैंडलर श्रृंखला को तोड़ देंगे। बस इसे आगे के लिए अग्रेषित करें। आपके पास पहले से ही "defaultUncaughtHandler" है getDefaultUncaughtExceptionHandler से, बस कॉल तब पास करें जब आप कर लें।
गिल्म

2
@gilm, क्या आप इस पर एक उदाहरण प्रदान कर सकते हैं कि "इसे आगे कैसे करें" और हैंडलर श्रृंखला में और क्या हो सकता है? यह एक समय हो गया है, लेकिन मैंने कई परिदृश्यों का परीक्षण किया, और कॉलिंग System.exit () सबसे अच्छा समाधान प्रतीत हुआ। आखिरकार, ऐप क्रैश हो गया है और इसे समाप्त करने की आवश्यकता है।
पेरी हार्टमैन

मुझे लगता है कि मुझे याद है कि अगर आप अनकैप्ड अपवाद को जारी रखने देते हैं तो क्या होता है: सिस्टम "ऐप समाप्त" नोटिस डालता है। आम तौर पर, यह ठीक होगा। लेकिन चूंकि नई गतिविधि (जो लॉग के साथ ईमेल भेजती है) पहले से ही अपना संदेश डाल रही है, सिस्टम को एक और डाल देना भ्रम की स्थिति है। इसलिए मैं इसे System.exit () के साथ चुपचाप गर्भपात के लिए मजबूर करता हूं।
पेरी हार्टमैन

8
@PeriHartman सुनिश्चित करें: केवल एक डिफ़ॉल्ट हैंडलर है। setDefaultUncaughtExceptionHandler () कॉल करने से पहले, आपको getDefaultUncaughtExceptionHandler () कॉल करना होगा और उस संदर्भ को रखना होगा। तो, मान लें कि आपके पास बगैरह, दुर्घटनाग्रस्त है और आपका हैंडलर अंतिम स्थापित है, सिस्टम केवल आपका कॉल करेगा। यह आपका काम है कि आप उस संदर्भ को कॉल करें जिसे आप getDefaultUncaughtExceptionHandler () के माध्यम से प्राप्त करते हैं और श्रृंखला में अगले थ्रेड और थ्रेडेबल को पास करते हैं। यदि आप सिर्फ System.exit (), दूसरों को नहीं बुलाया जाएगा।
गिल्म

4
आपको एप्लिकेशन टैग में एक विशेषता के साथ प्रकट रूप में आवेदन के अपने कार्यान्वयन को पंजीकृत करने की आवश्यकता है, उदाहरण के लिए: <एप्लीकेशन एंड्रॉइड: नाम = "com.mydomain.MyApplication" अन्य attrs ... />
मैट

9

आज कई क्रैश रिप्रिंट टूल हैं जो आसानी से ऐसा करते हैं।

  1. crashlytics - एक क्रैश रिपोर्टिंग टूल, नि: शुल्क लेकिन आपको मूल रिपोर्ट देता है

  2. ग्रिफोनेट - एक अधिक उन्नत रिपोर्टिंग उपकरण, किसी प्रकार के शुल्क की आवश्यकता होती है। लाभ: दुर्घटनाओं का आसान मनोरंजन, ANR's, सुस्ती ...

यदि आप एक निजी डेवलपर हैं तो मैं क्रैशलाईटिक्स का सुझाव दूंगा, लेकिन अगर यह एक बड़ा संगठन है, तो मैं ग्रिफोनेट के लिए जाऊंगा।

शुभ लाभ!


5

इसके बजाय ACRA का उपयोग करने का प्रयास करें - यह स्टैक ट्रेस के साथ-साथ आपके बैकएंड या आपके द्वारा सेट किए गए Google डॉक्स दस्तावेज़ में अन्य उपयोगी डिबग जानकारी के टन को भेजने का काम करता है।

https://github.com/ACRA/acra


5

@ पिरिहार्टमैन का उत्तर अच्छी तरह से काम करता है जब यूआई थ्रेड बिना अपवाद के फेंकता है। जब गैर-अपवादित थ्रेड को गैर UI थ्रेड द्वारा फेंक दिया जाता है, तो मैंने कुछ सुधार किए हैं।

public boolean isUIThread(){
    return Looper.getMainLooper().getThread() == Thread.currentThread();
}

public void handleUncaughtException(Thread thread, Throwable e) {
    e.printStackTrace(); // not all Android versions will print the stack trace automatically

    if(isUIThread()) {
        invokeLogActivity();
    }else{  //handle non UI thread throw uncaught exception

        new Handler(Looper.getMainLooper()).post(new Runnable() {
            @Override
            public void run() {
                invokeLogActivity();
            }
        });
    }
}

private void invokeLogActivity(){
    Intent intent = new Intent ();
    intent.setAction ("com.mydomain.SEND_LOG"); // see step 5.
    intent.setFlags (Intent.FLAG_ACTIVITY_NEW_TASK); // required when starting from Application
    startActivity (intent);

    System.exit(1); // kill off the crashed app
}

2

अच्छी तरह से समझाया। लेकिन यहाँ एक अवलोकन, फाइल राइटर और स्ट्रीमिंग का उपयोग करके फाइल में लिखने के बजाय, मैंने सीधे logcat -f विकल्प का उपयोग किया। यहाँ कोड है

String[] cmd = new String[] {"logcat","-f",filePath,"-v","time","<MyTagName>:D","*:S"};
        try {
            Runtime.getRuntime().exec(cmd);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

इससे मुझे नवीनतम बफर जानकारी को फ्लश करने में मदद मिली। फ़ाइल स्ट्रीमिंग का उपयोग करने से मुझे एक समस्या मिली कि यह बफर से नवीनतम लॉग फ्लश नहीं कर रहा था। लेकिन वैसे भी, यह वास्तव में मददगार मार्गदर्शक था। धन्यवाद।


मेरा मानना ​​है कि मैंने कोशिश की (या कुछ इसी तरह)। अगर मुझे याद है, मैं अनुमति समस्याओं में भाग गया। क्या आप एक निहित डिवाइस पर ऐसा कर रहे हैं?
पेरी हार्टमैन

ओह, मुझे खेद है अगर मैंने आपको भ्रमित किया है, लेकिन मैं अभी तक अपने एमुलेटर के साथ प्रयास कर रहा हूं। मैं इसे अभी तक एक डिवाइस के लिए पोर्ट कर रहा हूं (लेकिन हां, जिस डिवाइस को मैं परीक्षण के लिए उपयोग करता हूं वह एक जड़ है)।
schow

2

बिना पढ़े अपवाद को संभालना : जैसा कि @gilm ने बताया कि बस ऐसा करें, (kotlin):

private val defaultUncaughtHandler = Thread.getDefaultUncaughtExceptionHandler();

override fun onCreate() {
  //...
    Thread.setDefaultUncaughtExceptionHandler { t, e ->
        Crashlytics.logException(e)
        defaultUncaughtHandler?.uncaughtException(t, e)
    }
}

मुझे आशा है कि यह मदद करता है, यह मेरे लिए काम किया .. (: y)। मेरे मामले में मैंने त्रुटि ट्रैकिंग के लिए 'com.microsoft.appcenter.crashes.Crashes' लाइब्रेरी का उपयोग किया है।


हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.