To print logs, we first need to provide a way to print logs externally, mainly providing adapters for various levels of printing and formats of printing logs:

/** * A proxy interface to enable additional operations. * Contains all possible Log message usages. */ public interface  Printer { void addAdapter(@NonNull LogAdapter adapter); Printer t(@Nullable String tag); void d(@NonNull String message, @Nullable Object... args); void d(@Nullable Object object); /** * Formats the given json content and print it */ void json(@Nullable String json); /** * Formats the given xml content and print it */ void xml(@Nullable String xml); void log(int priority, @Nullable String tag, @Nullable String message, @Nullable Throwable throwable); void clearLogAdapters(); }Copy the code

As a Logger format adapter, LogAdapter can customize the format of printed logs. Implementation method:

class LoggerPrinter implements Printer { /** * It is used for json pretty print */ private static final int JSON_INDENT = 2; /** * Provides one-time used tag for the log message */ private final ThreadLocal<String> localTag = new ThreadLocal<>(); private final List<LogAdapter> logAdapters = new ArrayList<>(); @Override public Printer t(String tag) { if (tag ! = null) { localTag.set(tag); } return this; } @Override public void d(@NonNull String message, @Nullable Object... args) { log(DEBUG, null, message, args); } @Override public void d(@Nullable Object object) { log(DEBUG, null, Utils.toString(object)); } @Override public void json(@Nullable String json) { if (Utils.isEmpty(json)) { d("Empty/Null json content"); return; } try { json = json.trim(); if (json.startsWith("{")) { JSONObject jsonObject = new JSONObject(json); String message = jsonObject.toString(JSON_INDENT); d(message); return; } if (json.startsWith("[")) { JSONArray jsonArray = new JSONArray(json);  String message = jsonArray.toString(JSON_INDENT); d(message); return; } e("Invalid Json");  } catch (JSONException e) { e("Invalid Json");  } } @Override public void xml(@Nullable String xml) { if (Utils.isEmpty(xml)) { d("Empty/Null xml content"); return;  } try { Source xmlInput = new StreamSource(new StringReader(xml));  StreamResult xmlOutput = new StreamResult(new StringWriter());  Transformer transformer = TransformerFactory.newInstance().newTransformer();  transformer.setOutputProperty(OutputKeys.INDENT, "yes");  transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");  transformer.transform(xmlInput, xmlOutput); d(xmlOutput.getWriter().toString().replaceFirst(">", ">\n"));  } catch (TransformerException e) { e("Invalid xml");  } } @Override public synchronized void log(int priority, @Nullable String tag, @Nullable String message, @Nullable Throwable throwable) { if (throwable != null && message != null) { message += " : " + Utils.getStackTraceString(throwable); } if (throwable ! = null && message == null) { message = Utils.getStackTraceString(throwable);  } if (Utils.isEmpty(message)) { message = "Empty/NULL log message"; } for (LogAdapter adapter : logAdapters) { if (adapter.isLoggable(priority, tag)) { adapter.log(priority, tag, message);  } } } @Override public void clearLogAdapters() { logAdapters.clear();  } @Override public void addAdapter(@NonNull LogAdapter adapter) { logAdapters.add(checkNotNull(adapter));  } /** * This method is synchronized in order to avoid messy of logs' order. */ private synchronized void log(int priority, @Nullable Throwable throwable, @NonNull String msg, @Nullable Object... args) { checkNotNull(msg);  String tag = getTag(); String message = createMessage(msg, args); log(priority, tag, message, throwable);  } /** * @return the appropriate tag based on local or global */ @Nullable private String getTag() { String tag = localTag.get(); if (tag ! = null) { localTag.remove(); return tag; } return null;  } @NonNull private String createMessage(@NonNull String message, @Nullable Object... args) { return args == null || args.length == 0 ? message : String.format(message, args);  } } final class Utils { private Utils() { // Hidden constructor. } /** * Copied from "android.util.Log.getStackTraceString()" in order to avoid usage of Android stack * in unit tests. * * @return Stack trace in form of String */ static String getStackTraceString(Throwable tr) { if (tr == null) { return "";  } // This is to reduce the amount of log spew that apps do in the non-error // condition of the network being unavailable. Throwable t = tr; while (t ! = null) { if (t instanceof UnknownHostException) { return ""; } t = t.getCause();  } StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); tr.printStackTrace(pw); pw.flush();  return sw.toString(); } static String logLevel(int value) { switch (value) { case VERBOSE: return "VERBOSE"; case DEBUG: return "DEBUG"; case INFO: return "INFO"; case WARN: return "WARN"; case ERROR: return "ERROR"; case ASSERT: return "ASSERT"; default: return "UNKNOWN";  } } @NonNull static <T> T checkNotNull(@Nullable final T obj) { if (obj == null) { throw new NullPointerException();  } return obj; } }Copy the code

Public void d(@nonnull String message, @nullable Object) public void d(@nonnull String message, @nullable Object… Args) prints logs through the List logAdapters = New ArrayList<>() collection in the class. That is, the logs will be printed as many times as the number of logAdapters configured.

public interface LogAdapter {

  boolean isLoggable(int priority, @Nullable String tag);

  void log(int priority, @Nullable String tag, @NonNull String message);
}
Copy the code

Set the Adapter at initialization time.

/** * @param application * @param tag Default value :"PRETTY_LOGGER" * @param isDebug Whether DEBUG * @param sdcard Whether saving logs to the memory card is enabled * @param saveDays The maximum duration for saving logs is 1000 x 60 x 60 * 24 * 7 * @param logCapacitySize Default log capacity 10 * 1024 * 1024 * @param minSaveLevel Minimum storage level Default logger. INFO */ public static void init(Application application, @Nullable String tag, final boolean isDebug, final boolean sdcard, final long saveDays, final long logCapacitySize, final int minSaveLevel) { LogHelper.isDebug = isDebug; Logger.addLogAdapter(new DiskLogAdapter(LogFormatStrategy.newBuilder().setEncodeType(sEncodeType) .showMillis(false).tag(tag).build( application.getPackageName(), application.getPackageManager().getApplicationLabel(application.getApplicationInfo()).toString().trim(), application, new DiskLogConditions(saveDays, logCapacitySize))) { @Override public boolean isLoggable(int priority, @Nullable String tag) { if(priority >= minSaveLevel) return sdcard; else return false; }}); LogReport.getInstance().init(application); }Copy the code

Add a print policy to the Adapter.

/**
 * Draws borders around the given log message along with additional information such as 
 * </pre>
 *
 * <h3>Customize</h3>
 * <pre><code>
 *   FormatStrategy formatStrategy = PrettyFormatStrategy.newBuilder()
 *       .showThreadInfo(false)  // (Optional) Whether to show thread info or not. Default true
 *       .methodCount(0)         // (Optional) How many method line to show. Default 2
 *       .methodOffset(7)        // (Optional) Hides internal method calls up to offset. Default 5
 *       .logStrategy(customLog) // (Optional) Changes the log strategy to print out. Default LogCat
 *       .tag("My custom tag")   // (Optional) Global tag for every log. Default PRETTY_LOGGER
 *       .build();
 * </code></pre>
 */
public class PrettyFormatStrategy implements FormatStrategy {

  /**
   * Android's max limit for a log entry is ~4076 bytes,
   * so 4000 bytes is used as chunk size since default charset
   * is UTF-8
   */
  private static final int CHUNK_SIZE = 4000;

  /**
   * The minimum stack trace index, starts at this class after two native calls.
   */
  private static final int MIN_STACK_OFFSET = 5;

  /**
   * Drawing toolbox
   */
  private static final char TOP_LEFT_CORNER = '┌';
  private static final char BOTTOM_LEFT_CORNER = '└';
  private static final char MIDDLE_CORNER = '├';
  private static final char HORIZONTAL_LINE = '│';
  private static final String DOUBLE_DIVIDER = "────────────────────────────────────────────────────────";
  private static final String SINGLE_DIVIDER = "┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄";
  private static final String TOP_BORDER = TOP_LEFT_CORNER + DOUBLE_DIVIDER + DOUBLE_DIVIDER;
  private static final String BOTTOM_BORDER = BOTTOM_LEFT_CORNER + DOUBLE_DIVIDER + DOUBLE_DIVIDER;
  private static final String MIDDLE_BORDER = MIDDLE_CORNER + SINGLE_DIVIDER + SINGLE_DIVIDER;

  private final int methodCount;
  private final int methodOffset;
  private final boolean showThreadInfo;
  @NonNull private final LogStrategy logStrategy;
  @Nullable private final String tag;

  private PrettyFormatStrategy(@NonNull Builder builder) {
    checkNotNull(builder);

    methodCount = builder.methodCount;
    methodOffset = builder.methodOffset;
    showThreadInfo = builder.showThreadInfo;
    logStrategy = builder.logStrategy;
    tag = builder.tag;
  }

  @NonNull public static Builder newBuilder() {
    return new Builder();
  }

  @Override public void log(int priority, @Nullable String onceOnlyTag, @NonNull String message) {
    checkNotNull(message);

    String tag = formatTag(onceOnlyTag);

    logTopBorder(priority, tag);
    logHeaderContent(priority, tag, methodCount);

    //get bytes of message with system's default charset (which is UTF-8 for Android)
    byte[] bytes = message.getBytes();
    int length = bytes.length;
    if (length <= CHUNK_SIZE) {
      if (methodCount > 0) {
        logDivider(priority, tag);
      }
      logContent(priority, tag, message);
      logBottomBorder(priority, tag);
      return;
    }
    if (methodCount > 0) {
      logDivider(priority, tag);
    }
    for (int i = 0; i < length; i += CHUNK_SIZE) {
      int count = Math.min(length - i, CHUNK_SIZE);
      //create a new String with system's default charset (which is UTF-8 for Android)
      logContent(priority, tag, new String(bytes, i, count));
    }
    logBottomBorder(priority, tag);
  }

  private void logTopBorder(int logType, @Nullable String tag) {
    logChunk(logType, tag, TOP_BORDER);
  }

  @SuppressWarnings("StringBufferReplaceableByString")
  private void logHeaderContent(int logType, @Nullable String tag, int methodCount) {
    StackTraceElement[] trace = Thread.currentThread().getStackTrace();
    if (showThreadInfo) {
      logChunk(logType, tag, HORIZONTAL_LINE + " Thread: " + Thread.currentThread().getName());
      logDivider(logType, tag);
    }
    String level = "";

    int stackOffset = getStackOffset(trace) + methodOffset;

    //corresponding method count with the current stack may exceeds the stack trace. Trims the count
    if (methodCount + stackOffset > trace.length) {
      methodCount = trace.length - stackOffset - 1;
    }

    for (int i = methodCount; i > 0; i--) {
      int stackIndex = i + stackOffset;
      if (stackIndex >= trace.length) {
        continue;
      }
      StringBuilder builder = new StringBuilder();
      builder.append(HORIZONTAL_LINE)
          .append(' ')
          .append(level)
          .append(getSimpleClassName(trace[stackIndex].getClassName()))
          .append(".")
          .append(trace[stackIndex].getMethodName())
          .append(" ")
          .append(" (")
          .append(trace[stackIndex].getFileName())
          .append(":")
          .append(trace[stackIndex].getLineNumber())
          .append(")");
      level += "   ";
      logChunk(logType, tag, builder.toString());
    }
  }

  private void logBottomBorder(int logType, @Nullable String tag) {
    logChunk(logType, tag, BOTTOM_BORDER);
  }

  private void logDivider(int logType, @Nullable String tag) {
    logChunk(logType, tag, MIDDLE_BORDER);
  }

  private void logContent(int logType, @Nullable String tag, @NonNull String chunk) {
    checkNotNull(chunk);

    String[] lines = chunk.split(System.getProperty("line.separator"));
    for (String line : lines) {
      logChunk(logType, tag, HORIZONTAL_LINE + " " + line);
    }
  }

  private void logChunk(int priority, @Nullable String tag, @NonNull String chunk) {
    checkNotNull(chunk);

    logStrategy.log(priority, tag, chunk);
  }

  private String getSimpleClassName(@NonNull String name) {
    checkNotNull(name);

    int lastIndex = name.lastIndexOf(".");
    return name.substring(lastIndex + 1);
  }

  /**
   * Determines the starting index of the stack trace, after method calls made by this class.
   *
   * @param trace the stack trace
   * @return the stack offset
   */
  private int getStackOffset(@NonNull StackTraceElement[] trace) {
    checkNotNull(trace);

    for (int i = MIN_STACK_OFFSET; i < trace.length; i++) {
      StackTraceElement e = trace[i];
      String name = e.getClassName();
      if (!name.equals(LoggerPrinter.class.getName()) && !name.equals(Logger.class.getName())) {
        return --i;
      }
    }
    return -1;
  }

  @Nullable private String formatTag(@Nullable String tag) {
    if (!Utils.isEmpty(tag) && !Utils.equals(this.tag, tag)) {
      return this.tag + "-" + tag;
    }
    return this.tag;
  }

  public static class Builder {
    int methodCount = 2;
    int methodOffset = 0;
    boolean showThreadInfo = true;
    @Nullable LogStrategy logStrategy;
    @Nullable String tag = "PRETTY_LOGGER";

    private Builder() {
    }

    @NonNull public Builder methodCount(int val) {
      methodCount = val;
      return this;
    }

    @NonNull public Builder methodOffset(int val) {
      methodOffset = val;
      return this;
    }

    @NonNull public Builder showThreadInfo(boolean val) {
      showThreadInfo = val;
      return this;
    }

    @NonNull public Builder logStrategy(@Nullable LogStrategy val) {
      logStrategy = val;
      return this;
    }

    @NonNull public Builder tag(@Nullable String tag) {
      this.tag = tag;
      return this;
    }

    @NonNull public PrettyFormatStrategy build() {
      if (logStrategy == null) {
        logStrategy = new LogcatLogStrategy();
      }
      return new PrettyFormatStrategy(this);
    }
  }
}

public interface FormatStrategy {

  void log(int priority, @Nullable String tag, @NonNull String message);
}
  
public class LogcatLogStrategy implements LogStrategy {

  static final String DEFAULT_TAG = "NO_TAG";

  @Override public void log(int priority, @Nullable String tag, @NonNull String message) {
    checkNotNull(message);

    if (tag == null) {
      tag = DEFAULT_TAG;
    }

    Log.println(priority, tag, message);
  }
}
      
public class DiskLogAdapter implements LogAdapter {

  @NonNull private final FormatStrategy formatStrategy;

  public DiskLogAdapter() {
    formatStrategy = CsvFormatStrategy.newBuilder().build();
  }

  public DiskLogAdapter(@NonNull FormatStrategy formatStrategy) {
    this.formatStrategy = checkNotNull(formatStrategy);
  }

  @Override public boolean isLoggable(int priority, @Nullable String tag) {
    return true;
  }

  @Override public void log(int priority, @Nullable String tag, @NonNull String message) {
    formatStrategy.log(priority, tag, message);
  }
}
   
public class LogFormatStrategy implements FormatStrategy {

  private static final String NEW_LINE = System.getProperty("line.separator");
  private static final String SEPARATOR = " ";

  private final Date date;
  private final SimpleDateFormat dateFormat;
  private final LogStrategy logStrategy;
  private final IEncryption encryption;
  private final String tag;
  private final boolean showMillis;//是否显示毫秒值

  private LogFormatStrategy(Builder builder) {
    date = builder.date;
    dateFormat = builder.dateFormat;
    logStrategy = builder.logStrategy;
    showMillis = builder.showMillis;
    encryption = builder.encryption;
    tag = builder.tag;
  }

  public static Builder newBuilder() {
    return new Builder();
  }

  @Override
  public void log(int priority, String onceOnlyTag, String message) {
    String tag = formatTag(onceOnlyTag);

    date.setTime(System.currentTimeMillis());
    StringBuilder header = new StringBuilder();
    // machine-readable date/time
    if (showMillis){
      header.append(date.getTime());
      // human-readable date/time
      header.append(SEPARATOR);
    }
    header.append(dateFormat.format(date));
    // level
    header.append(SEPARATOR);
    header.append(logLevel(priority));
    // tag
    header.append("/");
    header.append(tag);
    header.append(SEPARATOR);

    StringBuilder builder = new StringBuilder();
    builder.append(header);

    // message
    if (message.contains(NEW_LINE)) {
      message = message.replaceAll(NEW_LINE, NEW_LINE + header.toString());
    }
    builder.append(message);
    // new line
    builder.append(NEW_LINE);

    String encodeString = null;//加密日志内容
    try {
      encodeString = encodeString(new String((builder.toString().trim()+"\n").getBytes(),"UTF-8"));
    } catch (UnsupportedEncodingException e) {
      e.printStackTrace();
    }
//    Log.d(LogFormatStrategy.class.getSimpleName(),"加密内容:"+encodeString);
    if (encodeString == null){
      logStrategy.log(priority, tag, builder.toString());
    }else {
      logStrategy.log(priority, tag, encodeString);
    }
  }


  public synchronized String encodeString(String content) {
    if (encryption != null) {
      try {
        return encryption.encrypt(content);
      } catch (Exception e) {
        e.printStackTrace();
        return content;
      }
    }
    return content;
  }

  private String formatTag(String tag) {
    if (!Utils.isEmpty(tag) && !Utils.equals(this.tag, tag)) {
      return this.tag + "-" + tag;
    }
    return this.tag;
  }

  public static final class Builder {
    Date date;
    SimpleDateFormat dateFormat;
    LogStrategy logStrategy;
    boolean showMillis = true;
    IEncryption encryption;
    String tag = "PRETTY_LOGGER";

    private Builder() {
    }

    public Builder date(Date val) {
      date = val;
      return this;
    }

    public Builder dateFormat(SimpleDateFormat val) {
      dateFormat = val;
      return this;
    }

    public Builder logStrategy(LogStrategy val) {
      logStrategy = val;
      return this;
    }

    public Builder showMillis(boolean showMs) {
      showMillis = showMs;
      return this;
    }

    public Builder setEncodeType(IEncryption encodeType) {
      encryption = encodeType;
      return this;
    }

    public Builder tag(String tag) {
      this.tag = tag;
      return this;
    }

      /**
       * @param pkgName 包名
     * @param appName 应用名
     * @return
       */
    public LogFormatStrategy build(String pkgName, String appName, Application application, DiskLogConditions conditions) {
      if (date == null) {
        date = new Date();
      }
      if (dateFormat == null) {
        dateFormat = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss.SSS", Locale.UK);
      }
      if (logStrategy == null) {
        String diskPath = Environment.getExternalStorageDirectory().getAbsolutePath();
        String folder = diskPath + File.separatorChar + "Android" + File.separatorChar + "data" + File.separatorChar + pkgName + File.separatorChar + "log";

        HandlerThread ht = new HandlerThread("AndroidFileLogger." + folder);
        ht.start();
        Handler handler = new NewestDiskLogStrategy.WriteHandler(ht.getLooper(), folder, appName, application, conditions);
        logStrategy = new NewestDiskLogStrategy(handler, conditions);
      }
      return new LogFormatStrategy(this);
    }
  }

  static String logLevel(int value) {
    switch (value) {
      case VERBOSE:
        return "V";
      case DEBUG:
        return "D";
      case INFO:
        return "I";
      case WARN:
        return "W";
      case ERROR:
        return "E";
      case ASSERT:
        return "A";
      default:
        return "UNKNOWN";
    }
  }
}
      
/**
 * 最新的Disk日志存储策略
 * 目前是按天记录为一个日志文件,结合记录天数(默认 最多7天) + 总的日志文件大小(默认 10M), 存储条件超过其中一条则需要清除最早的一次文件,
 * 然后再进行新日志信息的保存
 */
public class NewestDiskLogStrategy implements LogStrategy {
    private static final String TAG = "NewestDiskLogStrategy";
    private final Handler handler;
    private final DiskLogConditions conditions;

    public NewestDiskLogStrategy(Handler handler, DiskLogConditions conditions) {
        this.handler = handler;
        this.conditions = conditions;
    }

    @Override
    public void log(int level, @Nullable String tag, @NonNull String message) {
        handler.sendMessage(handler.obtainMessage(level, message));
    }

    static class WriteHandler extends Handler {
        private static final long SAVE_DAYS = 1000 * 60 * 60 * 24 * 7; // 保留7天的日志
        private static final long SAVE_SIZES = 10 * 1024 * 1024; //10M
        private static SimpleDateFormat dirFormat = new SimpleDateFormat("yyyy-MM-dd");// 日志文件夹格式
        private SimpleDateFormat fileFormat = new SimpleDateFormat("yyyy-MM-dd");// 日志文件名格式
        private final String folder;
        private String appName = "Logger";
        private DiskLogConditions conditions;

        WriteHandler(Looper looper, String folder, String appName, @NonNull  Application application, @NonNull DiskLogConditions conditions) {
            super(looper);
//            this.folder = folder + File.separatorChar + dirFormat.format(new Date());
            this.folder = folder;
            this.appName = appName;
            this.conditions = conditions;
            if (conditions.getSaveDays() == 0) {
                conditions.setSaveDays(SAVE_DAYS);
            }
            if (conditions.getLogCapacitySize() == 0) {
                conditions.setLogCapacitySize(SAVE_SIZES);
            }
            orderByDate(this.folder);
            deleteLoggerFile(this.folder);
            addFileDeviceInfoHeader(application);
        }

        private void addFileDeviceInfoHeader(Application application) {
            File logFile = getLogFile(folder, appName);
            try {
                if (!logFile.getParentFile().exists()) {
                    logFile.getParentFile().mkdir();
                }
                if (!logFile.exists()) {
                    logFile.createNewFile();

                    writeFile(logFile, SystemInfoUtil.getSystemInfoHeader(application));
                }
            } catch (SecurityException e1) {
                Log.e(TAG, "addFileDeviceInfoHeader() "+e1.getMessage());
            } catch (IOException e2) {
                Log.e(TAG, "addFileDeviceInfoHeader() "+e2.getMessage());
            }
        }

        @SuppressWarnings("checkstyle:emptyblock")
        @Override
        public void handleMessage(Message msg) {
            String content = (String) msg.obj;


            File logFile = getLogFile(folder, appName);
            writeFile(logFile, content);
        }

        private void writeFile(File file, String content) {
            FileWriter fileWriter = null;
            try {
                fileWriter = new FileWriter(file, true);

                writeLog(fileWriter, content);

                fileWriter.flush();
                fileWriter.close();
            } catch (IOException e) {
                if (fileWriter != null) {
                    try {
                        fileWriter.flush();
                        fileWriter.close();
                    } catch (IOException e1) { /* fail silently */ }
                }
            }
        }

        public static void orderByDate(String filePath) {
            File file = new File(filePath);
            File[] files = file.listFiles();
            if(files == null || files.length == 0)
                return;

            Arrays.sort(files, new Comparator<File>() {
                public int compare(File f1, File f2) {
                    long diff = f1.lastModified() - f2.lastModified();
                    if (diff > 0)
                        return 1;
                    else if (diff == 0)
                        return 0;
                    else
                        return -1;//如果 if 中修改为 返回-1 同时此处修改为返回 1  排序就会是递减
                }

                public boolean equals(Object obj) {
                    return true;
                }

            });
            for (int i = 0; i < files.length; i++) {
                Log.i(TAG, files[i].getName());
                Log.i(TAG, "lastModified = " + new Date(files[i].lastModified()));
            }
        }

        /**
         * This is always called on a single background thread.
         * Implementing classes must ONLY write to the fileWriter and nothing more.
         * The abstract class takes care of everything else including close the stream and catching IOException
         *
         * @param fileWriter an instance of FileWriter already initialised to the correct file
         */
        private void writeLog(FileWriter fileWriter, String content) throws IOException {
            fileWriter.append(content);
        }

        private File getLogFile(String folderName, String fileName) {
            File folder = new File(folderName);
            if (!folder.exists()) {
                //TODO: What if folder is not created, what happens then?
                folder.mkdirs();
            }
            return new File(folder, String.format("%s_%s.log", fileName, fileFormat.format(new Date())));
        }

        /**
         * 删除SAVE_DAYS天前的日志
         */
        private synchronized void deleteLoggerFile(String path) {
            File file = new File(path);
            if (!file.exists()) {
                return;
            }
            File[] files = file.listFiles();
            for (File fil : files) {
                // 删除最后修改日期早于七天前的日志
                if (System.currentTimeMillis() - fil.lastModified() > conditions.getSaveDays()) {
                    fil.delete();
                }
            }

            files = file.listFiles();
            if (!file.exists()) {
                return;
            }

            long logFolderSize = FileUtil.folderSize(file);
            Log.i(TAG, "for before LogFolderSize = "+logFolderSize);
            for(File fil : files) {
                // 删除最后修改日期早于七天前的日志
                logFolderSize = FileUtil.folderSize(file);
                if(logFolderSize > conditions.getLogCapacitySize()) {
                    fil.delete();
                }
                Log.i(TAG, "LogFolderSize = "+logFolderSize);
            }
        }

        // 删除指定文件目录下的所有文件
        private synchronized void deleteDirWithFile(File dir) {
            if (dir == null || !dir.exists() || !dir.isDirectory()) {
                return;
            }
            for (File file : dir.listFiles()) {
                if (file.isFile())
                    file.delete(); // 删除所有文件
                else if (file.isDirectory())
                    deleteDirWithFile(file); // 递归的方式删除文件夹
            }
            dir.delete();// 删除目录本身
        }
    }
}
      
public class FileUtil {

    private static String TAG = "FileUtil";

    /**
     * 递归删除目录下的所有文件及子目录下所有文件
     *
     * @param dir 将要删除的文件目录
     * @return 删除成功返回true,否则返回false
     */
    public static boolean deleteDir(File dir) {
        if (dir.isDirectory()) {
            String[] children = dir.list();
            // 递归删除目录中的子目录下
            for (String aChildren : children) {
                boolean success = deleteDir(new File(dir, aChildren));
                if (!success) {
                    return false;
                }
            }
        }
        // 目录此时为空,可以删除
        return dir.delete();
    }


    /**
     * 读取File中的内容
     *
     * @param file 请务必保证file文件已经存在
     * @return file中的内容
     */
    public static String getText(File file) {
        if (!file.exists()) {
            return null;
        }
        StringBuilder text = new StringBuilder();
        BufferedReader br = null;
        try {
            br = new BufferedReader(new FileReader(file));
            String line;
            while ((line = br.readLine()) != null) {
                text.append(line);
                text.append('\n');
            }

        } catch (IOException e) {
            Log.e(TAG, e.toString());
            e.printStackTrace();
        } finally {
            if (br != null) {
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return text.toString();
    }

    /**
     * 遍历获取Log文件夹下的所有crash文件
     *
     * @param logdir 从哪个文件夹下找起
     * @return 返回crash文件列表
     */
    public static ArrayList<File> getCrashList(File logdir) {
        ArrayList<File> crashFileList = new ArrayList<>();
        findFiles(logdir.getAbsolutePath(), crashFileList);
        return crashFileList;
    }


    /**
     * 将指定文件夹中满足要求的文件存储到list集合中
     *
     * @param f
     * @param list
     */

    /**
     * 递归查找文件
     *
     * @param baseDirName 查找的文件夹路径
     * @param fileList    查找到的文件集合
     */
    public static void findFiles(String baseDirName, List<File> fileList) {
        File baseDir = new File(baseDirName);       // 创建一个File对象
        if (!baseDir.exists() || !baseDir.isDirectory()) {  // 判断目录是否存在
            LogUtil.e(TAG, "文件查找失败:" + baseDirName + "不是一个目录!");
        }
        String tempName;
        //判断目录是否存在
        File tempFile;
        File[] files = baseDir.listFiles();
        for (File file : files) {
            tempFile = file;
            if (tempFile.isDirectory()) {
                findFiles(tempFile.getAbsolutePath(), fileList);
            } else if (tempFile.isFile()) {
                tempName = tempFile.getName();
                if (tempName.contains("Crash")) {
                    // 匹配成功,将文件名添加到结果集
                    fileList.add(tempFile.getAbsoluteFile());
                }
            }
        }
    }

    /**
     * 获取文件夹的大小
     *
     * @param directory 需要测量大小的文件夹
     * @return 返回文件夹大小,单位byte
     */
    public static long folderSize(File directory) {
        long length = 0;
        try {
            for (File file : directory.listFiles()) {
                if (file.isFile())
                    length += file.length();
                else
                    length += folderSize(file);
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        return length;
    }

    public static File createFile(File zipdir, File zipfile) {
        if (!zipdir.exists()) {
            boolean result = zipdir.mkdirs();
            LogUtil.d("TAG", "zipDir.mkdirs() = " + result);
        }
        if (!zipfile.exists()) {
            try {
                boolean result = zipfile.createNewFile();
                LogUtil.d("TAG", "zipDir.createNewFile() = " + result);
            } catch (IOException e) {
                e.printStackTrace();
                Log.e("TAG", e.getMessage());
            }
        }
        return zipfile;
    }

    /**
     * @des 保存内容到文本
     * @param filePath
     * @param content
     */
    public static boolean saveAsFileWriter(String filePath, String content) {
        FileWriter fwriter = null;
        try {
            //true表示不覆盖原来的内容,而是加到文件的后面。若要覆盖原来的内容,直接省略这个参数就好
            fwriter = new FileWriter(filePath, true);
            fwriter.write(content);
        } catch (IOException ex) {
            ex.printStackTrace();
        } finally {
            try {
                fwriter.flush();
                fwriter.close();
                return true;
            } catch (IOException ex) {
                ex.printStackTrace();
                return false;
            }
        }
    }


    /**
     * 获取 指定路径 下的 所有 后缀 的文件
     *
     * @param fileAbsolutePath 搜索的路径
     * @param suffix           文件后缀
     * @return
     */
    public static Vector<File> getAllTypeFile(String fileAbsolutePath, String suffix) {
        Vector<File> vecFile = new Vector<>();
        File file = new File(fileAbsolutePath);
        File[] subFile = file.listFiles();
        for (int iFileLength = 0; iFileLength < subFile.length; iFileLength++) {
            // 判断是否为文件夹
            if (!subFile[iFileLength].isDirectory()) {
                String filename = subFile[iFileLength].getName();
                // 判断是否为suffix结尾
                if (filename.trim().toLowerCase().endsWith(suffix)) {
                    vecFile.add(subFile[iFileLength]);
                }
            }
        }
        return vecFile;
    }

    /**
     * 分割文件
     * @param file 文件路径
     * @param unitSize 每个文件大小
     * @return 子文件路径
     */
    public static ArrayList<File> partiFile(File file , long unitSize) {
        if(!file.exists() || (!file.isFile()))
            return null;

        String fileName = file.getName();
        String path = file.getParent();
        long fileSize = file.length();
        Log.i(TAG, "zip fileSize = "+fileSize);
        long count = fileSize / unitSize;
        if(fileSize % unitSize != 0) {
            count ++;
        }

        String fn[] = fileName.split("\\.");
        if(fn.length == 2 && (!TextUtils.isEmpty(fn[0]))) {
            fileName = fn[0];
        }
        ArrayList<File> result = new ArrayList<File> ();
        FileInputStream in;
        FileOutputStream out = null;
        File fileTemp;
        String fileNameTemp;
        int tempbyte;

        if(count == 1) {
            result.add(file);
            return result;
        }

        try {
            in = new FileInputStream(file);
            for (int i = 1; i <= count; i++) {
                fileNameTemp = fileName + "_part"+i+".data";
                fileTemp = new File(path + "/"+fileNameTemp);
                result.add(fileTemp);
                if(!fileTemp.exists()) {
                    fileTemp.createNewFile();
                }

                out = new FileOutputStream(fileTemp);
                long total = 0;
                long x = fileSize % unitSize;
                while ((tempbyte =  in.read()) != -1) {
                    out.write(tempbyte);
                    total ++;
                    if(i != count && total == unitSize || (i == count && total == fileSize % unitSize))
                        break;//子文件到达指定大小就创建下一个文件;
                }
            }
            out.close();
            in.close();
        }
        catch (IOException  e) {
            e.printStackTrace();
        }

        return result;
    }

}
Copy the code

Obtain compressed logs.

    /**
     * 获取日志文件夹压缩包
     * @param application
     * @return
     */
    public static File getLogZipFile(Application application) {
        return LogReport.getInstance().getLogZipFile(application);
    }

    /**
     *  获取日志文件夹压缩包分片列表
     * @param application
     * @param unitSize 每片大小
     * @return
     */
    public static ArrayList<File> getPartiFiles(Application application, long unitSize) {
        return FileUtil.partiFile(getLogZipFile(application), unitSize);
    }

    /**
     * 测试调试阶段打印或存储
     * 支持msg的日志,例如:
     * LogHelper.t("name=%s,age=%d", "zhangsan", 18);
     *
     * @param msg
     * @param args
     */
    public static void t(String msg, Object... args) {
        if(LogHelper.isDebug)
            Logger.v(msg, args);
    }

    /**
     * 测试调试阶段打印或存储
     * 支持msg的日志,例如:
     * LogHelper.t("name=%s,age=%d", "zhangsan", 18);
     *
     * @param msg
     * @param args
     */
    public static void t(String tag, String msg, Object... args) {
        if(LogHelper.isDebug)
            Logger.t(tag).v(msg, args);
    }

    /**
     * 支持msg的日志,例如:
     * LogHelper.v("name=%s,age=%d", "zhangsan", 18);
     *
     * @param msg
     * @param args
     */
    public static void v(String msg, Object... args) {
        Logger.v(msg, args);
    }

    /**
     * 支持msg的日志,例如:
     * LogHelper.v("name=%s,age=%d", "zhangsan", 18);
     *
     * @param msg
     * @param args
     */
    public static void v(String tag, String msg, Object... args) {
        Logger.t(tag).v(msg, args);
    }

    /**
     * 支持msg的日志,例如:
     * LogHelper.i("name=%s,age=%d", "zhangsan", 18);
     *
     * @param msg
     * @param args
     */
    public static void i(String msg, Object... args) {
        Logger.i(msg, args);
    }

    /**
     * 支持msg的日志,例如:
     * LogHelper.i("name=%s,age=%d", "zhangsan", 18);
     *
     * @param msg
     * @param args
     */
    public static void i(String tag, String msg, Object... args) {
        Logger.t(tag).i(msg, args);
    }

    /**
     * 支持msg的日志,例如:
     * LogHelper.d("name=%s,age=%d", "zhangsan", 18);
     *
     * @param msg
     * @param args
     */
    public static void d(String msg, Object... args) {
        Logger.d(msg, args);
    }

    /**
     * 支持msg的日志,例如:
     * LogHelper.d("name=%s,age=%d", "zhangsan", 18);
     *
     * @param msg
     * @param args
     */
    public static void d(String tag, String msg, Object... args) {
        Logger.t(tag).d(msg, args);
    }

    /**
     * 支持msg的日志,例如:
     * LogHelper.w("name=%s,age=%d", "zhangsan", 18);
     *
     * @param msg
     * @param args
     */
    public static void w(String msg, Object... args) {
        Logger.w(msg, args);
    }

    /**
     * 支持msg的日志,例如:
     * LogHelper.w("name=%s,age=%d", "zhangsan", 18);
     *
     * @param msg
     * @param args
     */
    public static void w(String tag, String msg, Object... args) {
        Logger.t(tag).w(msg, args);
    }

    /**
     * 支持msg的日志,例如:
     * LogHelper.e("name=%s,age=%d", "zhangsan", 18);
     *
     * @param msg
     * @param args
     */
    public static void e(String msg, Object... args) {
        Logger.e(msg, args);
    }

    /**
     * 支持msg的日志,例如:
     * LogHelper.e("name=%s,age=%d", "zhangsan", 18);
     *
     * @param msg
     * @param args
     */
    public static void e(String tag, String msg, Object... args) {
        Logger.t(tag).e(msg, args);
    }

    /**
     * 支持msg的日志,例如:
     * LogHelper.wtf("name=%s,age=%d", "zhangsan", 18);
     *
     * @param msg
     * @param args
     */
    public static void wtf(String msg, Object... args) {
        Logger.wtf(msg, args);
    }

    /**
     * 支持msg的日志,例如:
     * LogHelper.wtf("name=%s,age=%d", "zhangsan", 18);
     *
     * @param msg
     * @param args
     */
    public static void wtf(String tag, String msg, Object... args) {
        Logger.t(tag).wtf(msg, args);
    }

    /**
     * @param throwable
     * @param msg
     * @param args
     */
    public static void e(Throwable throwable, String msg, Object... args) {
        Logger.e(throwable, msg, args);
    }

    /**
     * @des 打印json
     * @param json
     */
    public static void json(String json) {
        Logger.json(json);
    }

    /**
     * @des 打印json
     * @param json
     */
    public static void json(String tag, String json) {
        Logger.t(tag).json(json);
    }

    /**
     * @des xml
     * @param xml
     */
    public static void xml(String xml) {
        Logger.xml(xml);
    }

    /**
     * @des xml
     * @param xml
     */
    public static void xml(String tag, String xml) {
        Logger.t(tag).xml(xml);
    }


    public static IEncryption getEncodeType() {
        return sEncodeType;
    }

    /**
     * @des 设置加密方式
     * @param encodeType
     */
    public static void setEncodeType(IEncryption encodeType) {
        sEncodeType = encodeType;
    }
     
/**
 * 日志崩溃管理框架
 */
public final class LogReport {

    private static LogReport mLogReport;

    /**
     * 设置缓存文件夹的大小,默认是10MB
     */
    private long mCacheSize = 10 * 1024 * 1024;

    /**
     * 设置日志保存的路径
     */
    private String mROOT;

    /**
     * 设置加密方式
     */
    private IEncryption mEncryption;

    /**
     * 设置日志的保存方式
     */
    private ISave mLogSaver;

    /**
     * 设置在哪种网络状态下上传,true为只在wifi模式下上传,false是wifi和移动网络都上传
     */
    private boolean mWifiOnly = true;
    /**
     * 压缩文件时间格式
     */
    private static final SimpleDateFormat ZIP_FOLDER_TIME_FORMAT =
            new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS", Locale.getDefault());
    private File mLogFolder;
    private File mZipFolder;

    private LogReport() {
    }

    public static LogReport getInstance() {
        if (mLogReport == null) {
            synchronized (LogReport.class) {
                if (mLogReport == null) {
                    mLogReport = new LogReport();
                }
            }
        }
        return mLogReport;
    }

    public LogReport setEncryption(IEncryption encryption) {
        this.mEncryption = encryption;
        return this;
    }


    public LogReport setWifiOnly(boolean wifiOnly) {
        mWifiOnly = wifiOnly;
        return this;
    }

    public LogReport setLogDir(Context context, String logDir) {
        if (TextUtils.isEmpty(logDir)) {
            //如果SD不可用,则存储在沙盒中
            if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
                mROOT = context.getExternalCacheDir().getAbsolutePath();
            } else {
                mROOT = context.getCacheDir().getAbsolutePath();
            }
        } else {
            mROOT = logDir;
        }
        return this;
    }

    public LogReport setLogSaver(ISave logSaver) {
        this.mLogSaver = logSaver;
        return this;
    }

    public String getROOT() {
        return mROOT;
    }

    public void init(Context context) {
        if (TextUtils.isEmpty(mROOT)) {
            //如果SD不可用,则存储在沙盒中
            if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
                mROOT = context.getExternalCacheDir().getAbsolutePath();
            } else {
                mROOT = context.getCacheDir().getAbsolutePath();
            }
        }
        if (mEncryption != null) {
            mLogSaver.setEncodeType(mEncryption);
        }
        CrashHandler.getInstance().init(mLogSaver);
        LogWriter.getInstance().init(mLogSaver);
    }

    public long getCacheSize() {
        return mCacheSize;
    }

    public LogReport setCacheSize(long cacheSize) {
        this.mCacheSize = cacheSize;
        return this;
    }

    /**
     * @param application
     * @return
     * @des 获取原始加密压缩文件, 取回日志之后再做解密处理
     */
    public File getLogZipFile(Application application) {

        String diskPath = Environment.getExternalStorageDirectory().getAbsolutePath();
        String folder = diskPath
                + File.separatorChar
                + "Android"
                + File.separatorChar
                + "data"
                + File.separatorChar
                + application.getPackageName()
                + File.separatorChar
                + "log/";
        mLogFolder = new File(folder);

        // 如果Log文件夹都不存在,说明不存在崩溃日志,检查缓存是否超出大小后退出
        if (!mLogFolder.exists() || mLogFolder.listFiles().length == 0) {
            LogHelper.d("Log文件夹都不存在,无需上传");
            return null;
        }

        mZipFolder = new File(mLogFolder + "alreadyUpload/");
        File zipFile = new File(mZipFolder,
                "UploadOn" + ZIP_FOLDER_TIME_FORMAT.format(System.currentTimeMillis()) + ".zip");

        //        File logFile = new File(mLogFolder, String.format("%s.log", appName));//原始加密文件
        //        File decodeFile = new File(mLogFolder, String.format("%s_%s.log", appName, "decode"));//解密后的文件
        //        FileUtil.saveAsFileWriter(decodeFile.getPath(), decodeString(FileUtil.getText(logFile)));

        //创建文件,如果父路径缺少,创建父路径
        zipFile = FileUtil.createFile(mZipFolder, zipFile);

        if (CompressUtil.zipFileAtPath(mLogFolder.getAbsolutePath(),
                zipFile.getAbsolutePath())) { //压缩加密文件
            LogHelper.d("日志文件压缩==========>成功===>大小:", FileUtil.folderSize(mZipFolder));
            return zipFile;
        } else {
            LogHelper.d("日志文件压缩==========>失败");
        }

        return null;
    }

    /**
     * @des 删除原日志文件
     */
    public boolean deleteLog() {
        if (mLogFolder == null) {
            return false;
        }
        return FileUtil.deleteDir(mLogFolder);
    }

    /**
     * @des 删除压缩日志
     */
    public boolean deleteZipLog() {
        if (mZipFolder == null) {
            return false;
        }
        return FileUtil.deleteDir(mZipFolder);
    }

    /**
     * @des 解密日志
     */
    private String decodeString(String content) {
        if (LogHelper.getEncodeType() != null) {
            try {
                return LogHelper.getEncodeType().decrypt(content);
            } catch (Exception e) {
                LogHelper.e(e.toString());
                e.printStackTrace();
                return content;
            }
        }
        return content;
    }
}
Copy the code

Upload log information:

class RealWarnReport private constructor() {
    private val TAG = "RealWarnReport"
    private val URL_UPLOAD_APP_LOG = "/cloud/v1/log/uploadAppLog"
    private var mJobjBaseParam: JSONObject? = null
    private var mBaseUrl: String? = null
    private var application: Application? = null

    companion object {
        val instance = RealWarnReportHolder.holder
        val SP_FILE = "LogHelper"
        val SP_CONTENT = "content"
    }

    private object RealWarnReportHolder {
        val holder = RealWarnReport()
    }

    fun init(provider: NetBodyProvider, application: Application) {
        this.application = application
        mJobjBaseParam = provider.getNetBody()
        mBaseUrl = provider.getServerUrl()
        PreferencesUtils.init(application)
        checkInitParam()
        if(!TextUtils.isEmpty(mJobjBaseParam!!.optString("token")) || (!TextUtils.isEmpty(mJobjBaseParam!!.optString("accountID")))) {
            checkUploadFailureAndUplpad()
        }
    }

    /**
     * 登录后把用户信息注入
     */
    fun setAccountInfo(token: String?, accountID: String?) {
        if (mJobjBaseParam != null) {
            try {
                mJobjBaseParam!!.put("token", token)
                mJobjBaseParam!!.put("accountID", accountID)
                checkUploadFailureAndUplpad()
            } catch (e: JSONException) {
                e.printStackTrace()
            }
        }
    }

    fun accountLogout() {
        setAccountInfo("", "")
    }

    private fun checkUploadFailureAndUplpad() {
        if(TextUtils.isEmpty(mJobjBaseParam!!.optString("token")) || TextUtils.isEmpty(mJobjBaseParam!!.optString("accountID"))) {
            return
        }

        if (NetUtil.isConnected(this.application!!)) {
            val content = PreferencesUtils.from(SP_FILE)
                .getString(SP_CONTENT, null)
            if (!TextUtils.isEmpty(content)) {
                val jarr: JSONArray
                val parm: JSONObject
                jarr = try {
                    JSONArray(content)
                } catch (e: JSONException) {
                    e.printStackTrace()
                    return
                }
                try {
                    parm = JSONObject(mJobjBaseParam.toString())
                    parm.put("appTimestamp", System.currentTimeMillis().toString() + "")
                    parm.put("traceId", System.currentTimeMillis().toString() + "")
                    parm.put("method", "uploadAppLog")
                    parm.put("type", "abnormal")
                    parm.put("logs", jarr)
                } catch (e: JSONException) {
                    e.printStackTrace()
                    return
                }
                Log.i(
                    TAG,
                    "checkUploadFailureAndUplpad() content = $content"
                )
                //delete SP_CONTENT
                PreferencesUtils.from(SP_FILE)
                    .remove(SP_CONTENT)
                val intent = Intent(
                    application,
                    UploadRealWarnService::class.java
                )
                intent.putExtra(UploadRealWarnService.PARAM_BODY, parm.toString())
                intent.putExtra(UploadRealWarnService.PARAM_DATA, content)
                intent.putExtra(UploadRealWarnService.PARAM_RESEND, true)
                intent.putExtra(
                    UploadRealWarnService.PARAM_URL,
                    mBaseUrl + URL_UPLOAD_APP_LOG
                )
                this.application!!.startService(intent)
            } else {
                Log.i(
                    TAG,
                    "checkUploadFailureAndUplpad() content null"
                )
            }
        }
    }

    /**
     * 检查参数
     */
    private fun checkInitParam() {
        if (mJobjBaseParam == null || mJobjBaseParam!!.length() == 0) {
            throw RuntimeException("RealWarnReport mJobjBaseParam 参数错误")
        }
        if (TextUtils.isEmpty(mBaseUrl)) {
            throw RuntimeException("RealWarnReport mBaseUrl参数错误")
        }
    }

    /**
     * 上传关键日志到服务器,同时记录到详细日志中
     * @param applicationContext
     * @param msg
     */
    fun upload(applicationContext: Context, msg: String?) {
        //上传的关键日志也要记录到详细日志中
        LogHelper.e(TAG, msg)
        if(TextUtils.isEmpty(mJobjBaseParam!!.optString("token")) || TextUtils.isEmpty(mJobjBaseParam!!.optString("accountID"))) {
            return
        }

        checkInitParam()
        //如果网络可用,而且是移动网络,但是用户设置了只在wifi下上传,返回
        if (!NetUtil.isConnected(applicationContext)) {
            writeLocalFile(msg)
            return
        }
        val parm: JSONObject
        val data: JSONObject
        val jarr: JSONArray
        try {
            parm = JSONObject(mJobjBaseParam.toString())
            data = JSONObject()
            jarr = JSONArray()
            data.put("content", msg)
            data.put("time", System.currentTimeMillis())
            jarr.put(data)
            parm.put("appTimestamp", System.currentTimeMillis().toString() + "")
            parm.put("traceId", System.currentTimeMillis().toString() + "")
            parm.put("method", "uploadAppLog")
            parm.put("type", "abnormal")
            parm.put("logs", jarr)
        } catch (e: JSONException) {
            e.printStackTrace()
            return
        }
        val intent = Intent(
            applicationContext,
            UploadRealWarnService::class.java
        )
        intent.putExtra(UploadRealWarnService.PARAM_BODY, parm.toString())
        intent.putExtra(
            UploadRealWarnService.PARAM_URL,
            mBaseUrl + URL_UPLOAD_APP_LOG
        )
        applicationContext.startService(intent)
    }

    /**
     * 记录需要上传的关键日志到本地文件
     */
    fun writeLocalFile(msg: String?) {
        if (TextUtils.isEmpty(msg)) {
            return
        }
        val content = PreferencesUtils.from(SP_FILE)
            .getString(SP_CONTENT, null)
        Log.i(
            TAG,
            "writeUploadFailure() content = $content"
        )
        val jarr: JSONArray
        val job: JSONObject
        jarr = if (TextUtils.isEmpty(content)) {
            JSONArray()
        } else {
            try {
                JSONArray(content)
            } catch (e: JSONException) {
                e.printStackTrace()
                JSONArray()
            }
        }
        job = JSONObject()
        try {
            job.put("content", msg)
            job.put("time", System.currentTimeMillis())
            jarr.put(job)
        } catch (e: JSONException) {
            e.printStackTrace()
        }
        Log.d(
            TAG,
            "writeUploadFailure() jarr = $jarr"
        )
        PreferencesUtils.from(SP_FILE)
            .putString(SP_CONTENT, jarr.toString())
            .apply()
    }

    interface NetBodyProvider {
        fun getNetBody(): JSONObject
        fun getServerUrl(): String
    }
}

class UploadRealWarnService :
    IntentService(TAG) {
    override fun onCreate() {
        super.onCreate()
        PreferencesUtils.init(this.applicationContext)
    }

    /**
     * 同一时间只会有一个耗时任务被执行,其他的请求还要在后面排队,
     * onHandleIntent()方法不会多线程并发执行,所有无需考虑同步问题
     *
     * @param intent
     */
    override fun onHandleIntent(intent: Intent?) {
        val bodyAndParam =
            intent!!.getStringExtra(PARAM_BODY)
        val url = intent.getStringExtra(PARAM_URL)
        val isReSend =
            intent.getBooleanExtra(PARAM_RESEND, false)
        val data = intent.getStringExtra(PARAM_DATA)
        val report =
            HttpReporter(applicationContext)
        report.setUrl(url)
        report.sendReport(bodyAndParam, object : ILogUpload.OnUploadFinishedListener {
            override fun onSuceess() {}
            override fun onError(error: String?) {
                if (isReSend && TextUtils.isEmpty(data)) {
                    PreferencesUtils.from(RealWarnReport.SP_FILE)
                        .putString(RealWarnReport.SP_CONTENT, data)
                        .apply()
                }
            }
        })
    }

    companion object {
        const val TAG = "UploadRealWarnService"
        const val PARAM_BODY = "body_param"
        const val PARAM_DATA = "body_data"
        const val PARAM_RESEND = "body_resend"
        const val PARAM_URL = "url_param"
    }
}
Copy the code