포착되지 않은 예외를 처리하고 로그 파일을 보내야합니다. : 아래의 “허용 된”솔루션을 참조하십시오. 내 앱이 단순히

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

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

작동 방식 : (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

proguard-project.txt에서 다음을 추가합니다. 이렇게하면 Proguard에 Log.v 및 Log.d가 부작용이 없다고 가정하고 (로그에 기록한 이후에도 마찬가지 임) 최적화 중에 제거 할 수 있습니다.

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

그게 다야! 개선에 대한 제안이있는 경우 알려 주시면 업데이트하겠습니다.


답변

오늘날이를 쉽게 수행 할 수있는 많은 충돌 재현 도구가 있습니다.

  1. crashlytics- 무료로 제공되지만 기본 보고서를 제공하는 오류보고 도구 장점 : 무료

  2. Gryphonet- 고급보고 도구로 일종의 수수료가 필요합니다. 장점 : 충돌, ANR, 속도 저하 등을 쉽게 재현 할 수 있습니다.

개인 개발자라면 Crashlytics를 제안하고 큰 조직이라면 Gryphonet를 선택하겠습니다.

행운을 빕니다!


답변

대신 ACRA를 사용해보세요. 스택 추적 및 기타 유용한 디버그 정보를 백엔드 또는 설정 한 Google 문서 문서로 보내는 작업을 처리합니다.

https://github.com/ACRA/acra


답변

@PeriHartman의 대답은 UI 스레드가 잡히지 않은 예외를 던질 때 잘 작동합니다. 잡히지 않은 예외가 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
}


답변

잘 설명했습니다. 그러나 여기서 한 가지 관찰은 File Writer 및 Streaming을 사용하여 파일에 쓰는 대신 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();
        }

이것은 최신 버퍼 정보를 플러시하는 데 도움이되었습니다. 파일 스트리밍을 사용하면 버퍼에서 최신 로그를 플러시하지 않는다는 한 가지 문제가 발생했습니다. 그러나 어쨌든 이것은 정말 유용한 가이드였습니다. 감사합니다.


답변

잡히지 않은 예외 처리 :
@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’라이브러리를 사용했습니다.


답변

FireCrasher 라이브러리를 사용하여 포착되지 않은 예외를 처리하고 복구를 수행 할 수 있습니다.

매체 기사 에서 라이브러리에 대해 더 많이 알 수 있습니다.