【问题标题】:Read logcat programmatically within application在应用程序中以编程方式读取 logcat
【发布时间】:2012-09-23 09:55:33
【问题描述】:

我想在我的应用程序中读取 logcat 日志并做出反应。

我找到了以下代码:

try {
  Process process = Runtime.getRuntime().exec("logcat -d");
  BufferedReader bufferedReader = new BufferedReader(
  new InputStreamReader(process.getInputStream()));

  StringBuilder log=new StringBuilder();
  String line = "";
  while ((line = bufferedReader.readLine()) != null) {
    log.append(line);
  }
  TextView tv = (TextView)findViewById(R.id.textView1);
  tv.setText(log.toString());
  } 
catch (IOException e) {}

此代码确实返回了在应用程序启动之前生成的 logcat 日志 -

但是是否有可能连续收听新的 logcat 日志?

【问题讨论】:

  • -d 选项将转储日志并退出。只要去掉 -d 选项,logcat 就不会退出。
  • 如何清除? - 我需要找到关于我的应用程序的特定行
  • 不需要root。
  • 如果不需要root,只有开发构建可以访问自己的日志?这段代码到底是在哪里执行的? with 在 apk 应用程序中?
  • 我尝试了一个测试。我只能读取我自己进程的事件。我认为您需要 root 才能读取其他进程事件。

标签: android logcat android-logcat


【解决方案1】:

您可以继续阅读日志,只需删除上面代码中的“-d”标志。

“-d”标志指示 logcat 显示日志内容并退出。如果您删除该标志,logcat 将不会终止并继续发送添加到其中的任何新行。

请记住,如果设计不正确,这可能会阻止您的应用程序。

祝你好运。

【讨论】:

  • 如果我删除“-d”应用程序卡住,我得到强制关闭对话框。
  • 正如我上面所说,它需要仔细的应用程序设计。您需要让上述代码在单独的线程中运行(以避免阻塞 UI),并且由于您想使用日志信息更新 UI 中的 textview,您需要使用 Handler 将信息发布回 UI。
  • 嗨 Luis,你能发布一个分离线程的示例代码吗?
  • 查看答案stackoverflow.com/a/59511458/1185087 它正在使用不会阻塞 UI 线程的协程。
【解决方案2】:

使用协程和官方的lifecycle-livedata-ktxlifecycle-viewmodel-ktx 库就很简单:

class LogCatViewModel : ViewModel() {
    fun logCatOutput() = liveData(viewModelScope.coroutineContext + Dispatchers.IO) {
        Runtime.getRuntime().exec("logcat -c")
        Runtime.getRuntime().exec("logcat")
                .inputStream
                .bufferedReader()
                .useLines { lines -> lines.forEach { line -> emit(line) }
        }
    }
}

用法

val logCatViewModel by viewModels<LogCatViewModel>()

logCatViewModel.logCatOutput().observe(this, Observer{ logMessage ->
    logMessageTextView.append("$logMessage\n")
})

【讨论】:

  • 很好的建议。我想知道是否有办法使用WebView 而不是TextView
  • 为什么我不能访问另一个应用程序的logcat? Runtime.getRuntime().exec("logcat some.other.app")
  • @Arsenius 您设备中的 Android 操作系统版本是多少?从 Android5/6/7 及更高版本开始,您将无法读取有关其他应用程序的信息,包括其状态和日志。所以绝对没有办法做到这一点............除非你已经扎根。
  • 有什么办法可以在非 UI 线程中解决这个问题吗?
  • @EyalElbaz 我相信这个答案在非 UI 线程上。 liveData(viewModelScope.coroutineContext + Dispatchers.IO) 使 logcat 代码在子线程上使用 Kotlin 中的 Coroutines 进行 IO 操作。
【解决方案3】:

您可以在将 logcat 写入文件后使用此方法清除您的 logcat,以避免重复行:

public void clearLog(){
     try {
         Process process = new ProcessBuilder()
         .command("logcat", "-c")
         .redirectErrorStream(true)
         .start();
    } catch (IOException e) {
    }
}

【讨论】:

  • 我刚刚发现清除日志可能需要一些时间。所以如果你清除日志然后立即读取日志,一些旧条目可能还没有被清除。此外,可以删除一些新添加的日志条目,因为它们是在清除完成之前添加的。即使你调用 process.waitfor() 也没有什么区别。
【解决方案4】:

这是一个快速的汇总/插入,可用于捕获所有当前或所有新的(自上次请求以来)日志项。

您应该修改/扩展它,因为您可能希望返回连续流而不是 LogCapture。

Android LogCat“手册”:https://developer.android.com/studio/command-line/logcat.html

import android.util.Log;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Stack;

/**
* Created by triston on 6/30/17.
*/

public class Logger {

  // http://www.java2s.com/Tutorial/Java/0040__Data-Type/SimpleDateFormat.htm
  private static final String ANDROID_LOG_TIME_FORMAT = "MM-dd kk:mm:ss.SSS";
  private static SimpleDateFormat logCatDate = new SimpleDateFormat(ANDROID_LOG_TIME_FORMAT);

  public static String lineEnding = "\n";
  private final String logKey;

  private static List<String> logKeys = new ArrayList<String>();

  Logger(String tag) {
    logKey = tag;
    if (! logKeys.contains(tag)) logKeys.add(logKey);
  }

  public static class LogCapture {
    private String lastLogTime = null;
    public final String buffer;
    public final List<String> log, keys;
    LogCapture(String oLogBuffer, List<String>oLogKeys) {
      this.buffer = oLogBuffer;
      this.keys = oLogKeys;
      this.log = new ArrayList<>();
    }
    private void close() {
      if (isEmpty()) return;
      String[] out = log.get(log.size() - 1).split(" ");
      lastLogTime = (out[0]+" "+out[1]);
    }
    private boolean isEmpty() {
      return log.size() == 0;
    }
    public LogCapture getNextCapture() {
      LogCapture capture = getLogCat(buffer, lastLogTime, keys);
      if (capture == null || capture.isEmpty()) return null;
      return capture;
    }
    public String toString() {
      StringBuilder output = new StringBuilder();
      for (String data : log) {
        output.append(data+lineEnding);
      }
      return output.toString();
    }
  }

  /**
   * Get a list of the known log keys
   * @return copy only
   */
  public static List<String> getLogKeys() {
    return logKeys.subList(0, logKeys.size() - 1);
  }

  /**
   * Platform: Android
   * Get the logcat output in time format from a buffer for this set of static logKeys.
   * @param oLogBuffer logcat buffer ring
   * @return A log capture which can be used to make further captures.
   */
  public static LogCapture getLogCat(String oLogBuffer) { return getLogCat(oLogBuffer, null, getLogKeys()); }

  /**
   * Platform: Android
   * Get the logcat output in time format from a buffer for a set of log-keys; since a specified time.
   * @param oLogBuffer logcat buffer ring
   * @param oLogTime time at which to start capturing log data, or null for all data
   * @param oLogKeys logcat tags to capture
   * @return A log capture; which can be used to make further captures.
   */
  public static LogCapture getLogCat(String oLogBuffer, String oLogTime, List<String> oLogKeys) {
    try {

      List<String>sCommand = new ArrayList<String>();
      sCommand.add("logcat");
      sCommand.add("-bmain");
      sCommand.add("-vtime");
      sCommand.add("-s");
      sCommand.add("-d");

      sCommand.add("-T"+oLogTime);

      for (String item : oLogKeys) sCommand.add(item+":V"); // log level: ALL
      sCommand.add("*:S"); // ignore logs which are not selected

      Process process = new ProcessBuilder().command(sCommand).start();

      BufferedReader bufferedReader = new BufferedReader(
        new InputStreamReader(process.getInputStream()));

      LogCapture mLogCapture = new LogCapture(oLogBuffer, oLogKeys);
      String line = "";

      long lLogTime = logCatDate.parse(oLogTime).getTime();
      if (lLogTime > 0) {
        // Synchronize with "NO YEAR CLOCK" @ unix epoch-year: 1970
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(new Date(oLogTime));
        calendar.set(Calendar.YEAR, 1970);
        Date calDate = calendar.getTime();
        lLogTime = calDate.getTime();
      }

      while ((line = bufferedReader.readLine()) != null) {
        long when = logCatDate.parse(line).getTime();
        if (when > lLogTime) {
          mLogCapture.log.add(line);
          break; // stop checking for date matching
        }
      }

      // continue collecting
      while ((line = bufferedReader.readLine()) != null) mLogCapture.log.add(line);

      mLogCapture.close();
      return mLogCapture;
    } catch (Exception e) {
      // since this is a log reader, there is nowhere to go and nothing useful to do
      return null;
    }
  }

  /**
   * "Error"
   * @param e
   */
  public void failure(Exception e) {
    Log.e(logKey, Log.getStackTraceString(e));
  }

  /**
   * "Error"
   * @param message
   * @param e
   */
  public void failure(String message, Exception e) {
    Log.e(logKey, message, e);
  }

  public void warning(String message) {
    Log.w(logKey, message);
  }

  public void warning(String message, Exception e) {
    Log.w(logKey, message, e);
  }

  /**
   * "Information"
   * @param message
   */
  public void message(String message) {
    Log.i(logKey, message);
  }

  /**
   * "Debug"
   * @param message a Message
   */
  public void examination(String message) {
    Log.d(logKey, message);
  }

  /**
   * "Debug"
   * @param message a Message
   * @param e An failure
   */
  public void examination(String message, Exception e) {
    Log.d(logKey, message, e);
  }

}

在您执行活动记录的项目中:

Logger log = new Logger("SuperLog");
// perform logging methods

当您想捕获通过“记录器”记录的所有内容时

LogCapture capture = Logger.getLogCat("main");

当你饿了,想吃更多的原木时

LogCapture nextCapture = capture.getNextCapture();

您可以将捕获作为字符串获取

String captureString = capture.toString();

或者你可以获取捕获的日志项

String logItem = capture.log.get(itemNumber);

没有精确的静态方法来捕获外日志键,但总有一种方法

LogCapture foreignCapture = Logger.getLogCat("main", null, foreignCaptureKeyList);

使用上述方法还可以让您在国外捕获时调用Logger.this.nextCapture

【讨论】:

  • 由于低开销[处理中]策略,这通常是执行日志记录和分析的最佳方法。此代码中可能有也可能没有错误。 logcat 对时间选择的处理必须大于为正确匹配给出的时间。缺少正确的时间选择算法将在 nextCapture 的第一个元素处创建重复的日志条目。
  • 缺少与 android-logcat 的时间选择器选项相关的时间格式和语言环境的文档可能会产生需要 time-format-interpolation-modifications 的错误。
  • 嗨..我使用了你的代码,但结果总是空的。还有效吗?
  • @img.simone;我已经用我的代码修订版更新了这个。 Android 的 logcat 在几个方面被破坏了。第一,logcat的日期格式输出没有年份分量,使得日期比较回退到1970年;其次,带有日期的 -t 和 -T 选项实际上并没有在指定的日期开始输出日志,因此我们必须解析日期并将其与同步到 1970 年的数字日期进行比较。我无法对此进行测试为您更新,但它绝对应该有效;因为代码来自一个工作存储库,并针对此上下文进行了特定的修改。
  • 可以在git.hsusa.core.log.controller.AndroidLogController.java下找到工作代码here;您可能希望使用我的 hscore 库而不是这种“快速而肮脏”的解决方案。要使用 hscore 进行日志记录,您可以使用:public final static SmartLogContext log = SmartLog.getContextFor("MyLogContext"); 开始。使用更好的 API,它的工作方式几乎相同。如果您需要任何帮助,可以使用我的 git hub 问题跟踪器。
【解决方案5】:

“-c”标志清除缓冲区。

-c 清除(刷新)整个日志并退出。

【讨论】:

  • 如何在上面的代码中使用-c。如果在日志中发现一些东西我想清除它
  • yuva,只需这样做: process = Runtime.getRuntime().exec("logcat -c"); bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream()), 1024); line = bufferedReader.readLine();
【解决方案6】:
            //CLEAR LOGS
            Runtime.getRuntime().exec("logcat -c");
            //LISTEN TO NEW LOGS
            Process pq=Runtime.getRuntime().exec("logcat v main");
            BufferedReader brq = new BufferedReader(new InputStreamReader(pq.getInputStream()));
            String sq="";
            while ((sq = brq.readLine()) != null)
            {
              //CHECK YOUR MSG HERE 
              if(sq.contains("send MMS with param"))
              {
              }
            }

我在我的应用程序中使用它并且它有效。 你可以在 Timer Task 中使用上面的代码,这样它就不会停止你的主线程

        Timer t;
        this.t.schedule(new TimerTask()
        {
          public void run()
          {
            try
            {
                ReadMessageResponse.this.startRecord();//ABOVE METHOD HERE

            }
            catch (IOException ex)
            {
              //NEED TO CHECK SOME VARIABLE TO STOP MONITORING LOGS 
              System.err.println("Record Stopped");
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            finally
            {
                ReadMessageResponse.this.t.cancel();
            }
          }
        }, 0L);
      }

【讨论】:

    【解决方案7】:

    根据@user1185087 的回答,没有 ViewModel 的简单解决方案可能是:

    在 IO 线程上启动作业:

    // Custom scope for collecting logs on IO threads.
    val scope = CoroutineScope(Job() + Dispatchers.IO)
    
    val job = scope.launch {
        Runtime.getRuntime().exec("logcat -c") // Clear logs
        Runtime.getRuntime().exec("logcat") // Start to capture new logs
            .inputStream 
            .bufferedReader()
            .useLines { lines ->
                // Note that this forEach loop is an infinite loop until this job is cancelled.
                lines.forEach { newLine ->
                    // Check whether this job is cancelled, since a coroutine must
                    // cooperate to be cancellable.
                    ensureActive()  
                    // TODO: Write newLine into a file or buffer or anywhere appropriate              
                }
            }
    }
    

    从主线程取消作业:

    MainScope().launch {
        // Cancel the job and wait for its completion on main thread.
        job.cancelAndJoin()
        job = null // May be necessary
        // TODO: Anything else you may want to clean up
    }
    

    如果您想在后台线程上连续收集应用程序的新日志,此解决方案就足够了。

    【讨论】:

      【解决方案8】:

      尝试将此权限添加到 mainfest:

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

      【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多