program tip

포착되지 않은 예외를 처리하고 로그 파일을 보내야합니다.

radiobox 2020. 8. 11. 08:14
반응형

포착되지 않은 예외를 처리하고 로그 파일을 보내야합니다.


업데이트 : 아래의 "허용 된"솔루션을 참조하십시오.

내 앱이 단순히 종료하는 것이 아니라 처리되지 않은 예외를 생성 할 때 먼저 사용자에게 로그 파일을 보낼 수있는 기회를 제공하고 싶습니다. 무작위 예외가 발생한 후 더 많은 작업을 수행하는 것이 위험하다는 것을 알고 있지만 최악의 경우 앱이 중단되고 로그 파일이 전송되지 않는다는 것입니다. 이것은 내가 예상했던 것보다 까다로운 것으로 판명되었습니다. :)

작동 방식 : (1) 포착되지 않은 예외 포착, (2) 로그 정보 추출 및 파일에 쓰기.

아직 작동하지 않는 것 : (3) 이메일을 보내기위한 활동 시작. 궁극적으로 사용자의 승인을 요청하는 또 다른 활동이 있습니다. 이메일 활동이 작동하면 다른 사람에게 많은 문제가 발생할 것으로 예상하지 않습니다.

문제의 핵심은 처리되지 않은 예외가 내 Application 클래스에서 포착된다는 것입니다. 활동이 아니기 때문에 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);
  }
}

여기에 완전한 솔루션이 있습니다 (거의 : UI 레이아웃과 버튼 처리를 생략했습니다)-많은 실험과 그 과정에서 발생한 문제와 관련된 다른 사람들의 다양한 게시물에서 파생되었습니다.

다음과 같은 여러 가지 작업을 수행해야합니다.

  1. Application 하위 클래스에서 uncaughtException을 처리합니다.
  2. 예외를 포착 한 후 새 활동을 시작하여 사용자에게 로그를 보내도록 요청하십시오.
  3. logcat의 파일에서 로그 정보를 추출하고 자신의 파일에 씁니다.
  4. 이메일 앱을 시작하여 파일을 첨부 파일로 제공합니다.
  5. 매니페스트 : 예외 처리기가 인식하도록 활동을 필터링합니다.
  6. 선택적으로 Log.d () 및 Log.v ()를 제거하도록 Proguard를 설정합니다.

이제 세부 정보는 다음과 같습니다.

(1 & 2) uncaughtException 처리, 로그 활동 보내기 시작 :

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의 모습은 다음과 같습니다 (하지만 UI를 추가해야합니다).

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) Proguard 설치 :

project.properties에서 구성 줄을 변경합니다. "optimize"를 지정해야합니다. 그렇지 않으면 Proguard가 Log.v () 및 Log.d () 호출을 제거 하지 않습니다 .

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

In proguard-project.txt, add the following. This tell Proguard to assume Log.v and Log.d have no side effects (even though they do since they write to the logs) and thus can be removed during optimization:

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

That's it! If you have any suggestions for improvements to this, please let me know and I may update this.


Today there are many crash reprting tools that do this easily.

  1. crashlytics - A crash reporting tool, free of charge but gives you basic reports Advantages : Free

  2. Gryphonet - A more advanced reporting tool, requires some kind of fee. Advantages : Easy recreation of crashes, ANR's, slowness...

If you are a private developer I would suggest Crashlytics, but if it's a big organization, I would go for Gryphonet.

Good Luck!


Try using ACRA instead - it handles sending the stack trace as well as tons of other useful debug information to your backend, or to Google Docs document you've set up.

https://github.com/ACRA/acra


@PeriHartman's answer works well when the UI thread throws uncaught exception. I made some improvements for when the uncaught exception is thrown by a non UI thread.

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
}

Nicely explained. But one observation here, instead of writing into file using File Writer and Streaming, I made use of the logcat -f option directly. Here is the code

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();
        }

This helped me in flushing the latest buffer info. Using File streaming gave me one issue that it was not flushing the latest logs from buffer. But anyway, this was really helpful guide. Thank you.


You can handle the uncaught exceptions using FireCrasher library and do a recovery from it.

you can know more about the library in this Medium Article

참고URL : https://stackoverflow.com/questions/19897628/need-to-handle-uncaught-exception-and-send-log-file

반응형