需要处理未捕获的异常,并发送日志文件异常、文件、日志

2023-09-11 20:14:03 作者:魅力.极限

更新:请参阅接受的解决方案

在我的应用程序创建一个未处理的异常,而不是简单地结束,我想首先给用户一个机会发出一个日志文件。我知道,这样做获得一个随机的异常后,更多的工作是有风险的,但是,嘿,最糟糕的是应用程序完成崩溃和日志文件不会被发送。这是谈到了将比我预想的:)麻烦

什么工作:(1)捕获未捕获的异常;(2)提取日志信息,并写入文件

有什么不工作尚未:(3)启动一个活动来发送电子邮件。最终,我将有另一个活动要求用户的权限。如果我得到了电子邮件活动的工作,我不奢望太多麻烦对方。

问题的关键在于未处理的异常被夹在我的应用程序类。因为这不是一个活动,这不是很明显的开始Intent.ACTION_SEND的活动。也就是说,一般启动一个叫startActivity,并继续执行onActivityResult的活动。这些方法是通过活动,但不应用支持。

这是如何做到这一点有什么建议?

怎么捕获异常,让代码继续

下面是一些code剪作为起始指南:

 公共类MyApplication的扩展应用
{
  defaultUncaughtHandler = Thread.getDefaultUncaughtExceptionHandler();
  公共无效的onCreate()
  {
    Thread.setDefaultUncaughtExceptionHandler(新Thread.UncaughtExceptionHandler()
    {
      @覆盖
      公共无效uncaughtException(线程的线程,可抛出E)
      {
        handleUncaughtException(螺纹,E);
      }
    });
  }

  私人无效handleUncaughtException(线程的线程,可抛出E)
  {
    串fullFileName = extractLogToFile(); // code未显示

    //下面显示什么,我想,但它不会像这样工作。
    意向意图=新的意图(Intent.ACTION_SEND);
    intent.setType(纯/文);
    intent.putExtra(Intent.EXTRA_EMAIL,新的String [] {me@mydomain.com});
    intent.putExtra(Intent.EXTRA_SUBJECT,日志文件);
    intent.putExtra(Intent.EXTRA_STREAM,Uri.parse(文件://+ fullFileName));
    startActivityForResult(意向,ACTIVITY_REQUEST_SEND_LOG);
  }

  公共无效onActivityResult(INT申请code,INT结果code,意图数据)
  {
    如果(要求code == ACTIVITY_REQUEST_SEND_LOG)
      System.exit(1);
  }
}
 

解决方案

下面是完整的解决方案(差不多:我省略了UI布局和按钮操作) - 从大量的试验和各种职位来源于其他相关的附带问题沿途。

有一些事情你需要做的:

在您的应用程序子类处理uncaughtException。 在捕获异常后,开始一个新的活动要求用户发送 一个日志。 提取从logcat中的文件的日志信息,并写信给你 自己的文件。 在启动电子邮件应用程序,为您的文件作为附件。 清单:筛选活动由异常处理程序识别 (可选)设置Proguard的剥离出来Log.d()和Log.v()。

现在,下面是详细信息:

(1和2)手柄uncaughtException,启动发送日志活动:

 公共类MyApplication的扩展应用
{
  公共无效的onCreate()
  {
    //为未捕获的异常安装的处理程序。
    Thread.setDefaultUncaughtExceptionHandler(新Thread.UncaughtExceptionHandler()
    {
      @覆盖
      公共无效uncaughtException(线程的线程,可抛出E)
      {
        handleUncaughtException(螺纹,E);
      }
    });
  }

  公共无效handleUncaughtException(线程的线程,可抛出E)
  {
    e.printStackTrace(); //并不是所有的Andr​​oid版本将自动打印堆栈跟踪

    意向意图=新的意图();
    intent.setAction(com.mydomain.SEND_LOG); //参阅第5步。
    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); //从应用程序启动时,需要
    startActivity(意向);

    System.exit(1); //杀死崩溃的应用程序
  }
}
 

(3)提取日志(我把这样的一个我SendLog活动):

 私人字符串extractLogToFile()
{
  PackageManager经理= this.getPackageManager();
  PackageInfo信息= NULL;
  尝试 {
    信息= manager.getPackageInfo(this.getPackageName(),0);
  }赶上(E2的NameNotFoundException){
  }
  弦模型= Build.MODEL;
  如果(!model.startsWith(Build.MANUFACTURER))
    模型= Build.MANUFACTURER ++模式;

  //使文件名 - 文件必须保存到外部存储,也不会被读取
  //电子邮件应用程序。
  字符串路径= Environment.getExternalStorageDirectory()+/+的MyApp /;
  字符串全名=路径+ LT;有的名称&gt ;;

  //解压到文件。
  档案文件=新的文件(全名);
  InputStreamReader的读卡器= NULL;
  FileWriter的作家= NULL;
  尝试
  {
    //为Android 4.0和更早的版本,你会得到所有的应用程序日志的输出,所以它过滤到
    //主要是限制到您的应用程序的输出。在以后的版本中,则不需要进行过滤。
    字符串CMD =(Build.VERSION.SDK_INT< = Build.VERSION_ codeS.ICE_CREAM_SANDWICH_MR1)?
                  logcat的-d -v时间的MyApp:v dalvikvm:v System.err的:V *:■:
                  logcat的-d -v时间;

    //获得输入流
    。工艺过程=调用Runtime.getRuntime()EXEC(CMD);
    读者=新的InputStreamReader(process.getInputStream());

    //写入输出流
    作家=新的FileWriter(文件);
    writer.write(Android版:+ Build.VERSION.SDK_INT +\ N);
    writer.write(设备:+型号+\ N);
    writer.write(应用程序版本:+(资讯== NULL(空):info.version code)+\ N);

    的char []缓冲区=新的char [10000]
    做
    {
      INT N = reader.read(缓冲,0,buffer.length);
      如果(N == -1)
        打破;
      writer.write(缓冲液,0,n)的;
    }而(真);

    reader.close();
    writer.close();
  }
  赶上(IOException异常E)
  {
    如果(作家!= NULL)
      尝试 {
        writer.close();
      }赶上(IOException异常E1){
      }
    如果(读者!= NULL)
      尝试 {
        reader.close();
      }赶上(IOException异常E1){
      }

    //你可能想在这里写一个失败的消息日志。
    返回null;
  }

  返回全名;
}
 

(4)启动电子邮件应用程序(也有我的SendLog活动):

 私人无效sendLogFile()
{
  串全名= extractLogToFile();
  如果(全名== NULL)
    返回;

  意向意图=新的意图(Intent.ACTION_SEND);
  intent.setType(纯/文);
  intent.putExtra(Intent.EXTRA_EMAIL,新的String [] {log@mydomain.com});
  intent.putExtra(Intent.EXTRA_SUBJECT,MyApp的日志文件);
  intent.putExtra(Intent.EXTRA_STREAM,Uri.parse(文件://+全名));
  intent.putExtra(Intent.EXTRA_TEXT,附加日志文件中。); //这样做,所以一些电子邮件客户端不抱怨空体。
  startActivity(意向);
}
 

(3和4)下面是SendLog样子(你必须添加的用户界面,虽然):

 公共类SendLog扩展活动实现OnClickListener
{
  @覆盖
  公共无效的onCreate(包savedInstanceState)
  {
    super.onCreate(savedInstanceState);
    requestWindowFeature(Window.FEATURE_NO_TITLE); //使对话框没有标题栏
    setFinishOnTouchOutside(假); // prevent从外面窃听关闭对话框的用户
    的setContentView(R.layout.send_log);
  }

  @覆盖
  公共无效的onClick(视图v)
  {
    //按钮点击在UI响应
  }

  私人无效sendLogFile()
  {
    //方法如上图所示
  }

  私人字符串extractLogToFile()
  {
    //方法如上图所示
  }
}
 

(5)清单:

 <舱单的xmlns:机器人=htt​​p://schemas.android.com/apk/res/android...>
    <! - 需要为Android 4.0.x和早期的教 - >
    <使用-权限的Andr​​oid:名称=android.permission.READ_LOGS/>

    <应用...>
        <活动
            机器人:名称=com.mydomain.SendLog
            机器人:主题=@安卓风格/ Theme.Dialog
            机器人:textAppearance =@安卓风格/ TextAppearance.Large
            机器人:windowSoftInputMode =stateHidden>
            <意向滤光器>
              <作用机器人:名称=com.mydomain.SEND_LOG/>
              <类机器人:名称=android.intent.category.DEFAULT/>
            &所述; /意图滤光器>
        < /活性GT;
     < /用途>
< /舱单>
 

(6)安装Proguard的:

在project.properties,更改配置行。您必须指定优化或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类android.util.Log {
    公共静态INT V(...);
    公共静态INT D(...);
}
 

这就是它!如果您有关于改进这个任何建议,请让我知道,我会更新这个。

UPDATE: Please see "accepted" solution below

When my app creates an unhandled exception, rather than simply terminating, I'd like to first give the user an opportunity to send a log file. I realize that doing more work after getting a random exception is risky but, hey, the worst is the app finishes crashing and the log file doesn't get sent. This is turning out to be trickier than I expected :)

What works: (1) trapping the uncaught exception, (2) extracting log info and writing to a file.

What doesn't work yet: (3) starting an activity to send email. Ultimately, I'll have yet another activity to ask the user's permission. If I get the email activity working, I don't expect much trouble for the other.

The crux of the problem is that the unhandled exception is caught in my Application class. Since that isn't an Activity, it's not obvious how to start an activity with Intent.ACTION_SEND. That is, normally to start an activity one calls startActivity and resumes with onActivityResult. These methods are supported by Activity but not by Application.

Any suggestions on how to do this?

Here are some code snips as a starting guide:

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

解决方案

Here's the complete solution (almost: I omitted the UI layout and button handling) - derived from a lot of experimentation and various posts from others related to issues that came up along the way.

There are a number of things you need to do:

Handle uncaughtException in your Application subclass. After catching an exception, start a new activity to ask the user to send a log. Extract the log info from logcat's files and write to your own file. Start an email app, providing your file as an attachment. Manifest: filter your activity to be recognized by your exception handler. Optionally, setup Proguard to strip out Log.d() and Log.v().

Now, here are the details:

(1 & 2) Handle uncaughtException, start send log activity:

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) Extract log (I put this an my SendLog Activity):

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) Start an email app (also in my SendLog Activity):

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) Here's what SendLog looks like (you'll have to add the UI, though):

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:

<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) Setup Proguard:

In project.properties, change the config line. You must specify "optimize" or Proguard will not remove Log.v() and Log.d() calls.

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.