+ * 描述
+ */
+public class WorkThreadHandler {
+
+ private Looper mThreadLooper;
+ private Handler mHandler;
+ private HandlerThread mThread;
+
+ public static WorkThreadHandler newInstance(String name ) {
+ return new WorkThreadHandler( name );
+ }
+
+ private WorkThreadHandler(String name ) {
+ // private constructor
+ mThread = new HandlerThread( name );
+ mThread.start();
+ mThreadLooper = mThread.getLooper();
+ mHandler = new Handler( mThreadLooper );
+ }
+
+ private WorkThreadHandler() {
+ // private constructor
+ this( "work-thread-handler" );
+ }
+
+ private static final class InstanceHolder {
+ private static final WorkThreadHandler INSTANCE = new WorkThreadHandler();
+ }
+
+ public static WorkThreadHandler getInstance() {
+ return InstanceHolder.INSTANCE;
+ }
+
+ public Looper getLooper() {
+ return mThreadLooper;
+ }
+
+ private Object readResolve() {
+ // 阻止反序列化,必须实现 Serializable 接口
+ return InstanceHolder.INSTANCE;
+ }
+
+ private Object sToken = new Object();
+
+ public boolean post( Runnable r ) {
+ return mHandler != null && mHandler.post( r );
+ }
+
+ public boolean postDelayed( Runnable r, long delayMillis ) {
+ return mHandler != null && mHandler.postDelayed( r, delayMillis );
+ }
+
+ public Handler getWorkThreadHandler() {
+ return mHandler;
+ }
+
+ public boolean postOnceDelayed( Runnable r, long delayMillis ) {
+ if ( mHandler == null ) {
+ return false;
+ } else {
+ mHandler.removeCallbacks( r, sToken );
+ return mHandler.postDelayed( r, delayMillis );
+ }
+ }
+
+ public void removeCallbacks( Runnable runnable ) {
+ if ( mHandler != null ) {
+ mHandler.removeCallbacks( runnable );
+ }
+ }
+}
diff --git a/foudations/mogo-utils/src/main/java/com/mogo/utils/logger/LogLevel.java b/foudations/mogo-utils/src/main/java/com/mogo/utils/logger/LogLevel.java
new file mode 100644
index 0000000..1296d3a
--- /dev/null
+++ b/foudations/mogo-utils/src/main/java/com/mogo/utils/logger/LogLevel.java
@@ -0,0 +1,24 @@
+
+package com.mogo.utils.logger;
+
+public enum LogLevel {
+
+ OFF( Integer.MAX_VALUE),
+
+ VERBOSE(1),
+
+ DEBUG(2),
+
+ INFO(3),
+
+ WARN(4),
+
+ ERROR(5);
+
+ public final int level;
+
+ private LogLevel(final int level) {
+ this.level = level;
+ }
+
+}
diff --git a/foudations/mogo-utils/src/main/java/com/mogo/utils/logger/Logger.java b/foudations/mogo-utils/src/main/java/com/mogo/utils/logger/Logger.java
new file mode 100644
index 0000000..1527799
--- /dev/null
+++ b/foudations/mogo-utils/src/main/java/com/mogo/utils/logger/Logger.java
@@ -0,0 +1,57 @@
+
+package com.mogo.utils.logger;
+
+public final class Logger {
+ private static final Printer sPrinter = new LoggerPrinter();
+
+ private Logger() {
+ }
+
+ public static Settings init() {
+ return sPrinter.init(LogLevel.DEBUG);
+ }
+
+ public static Settings init(LogLevel logLevel) {
+ return sPrinter.init(logLevel);
+ }
+
+ public static void d( String tag, String message, Object... args) {
+ if(isLoggable(LogLevel.DEBUG)) sPrinter.d(tag, message, args);
+ }
+
+ public static void e( String tag, String message, Object... args) {
+ if(isLoggable(LogLevel.ERROR)) sPrinter.e(tag, null, message, args);
+ }
+
+ public static void e( String tag, Throwable throwable, String message, Object... args) {
+ if(isLoggable(LogLevel.ERROR)) sPrinter.e(tag, throwable, message, args);
+ }
+
+ public static void i( String tag, String message, Object... args) {
+ if(isLoggable(LogLevel.INFO)) sPrinter.i(tag, message, args);
+ }
+
+ public static void v( String tag, String message, Object... args) {
+ if(isLoggable(LogLevel.VERBOSE)) sPrinter.v(tag, message, args);
+ }
+
+ public static void w( String tag, String message, Object... args) {
+ if(isLoggable(LogLevel.WARN)) sPrinter.w(tag, message, args);
+ }
+
+ public static void easyLog( String tag, String message) {
+ if(isLoggable(LogLevel.DEBUG)) sPrinter.d(tag, message);
+ }
+
+ public static void json( String tag, String json) {
+ if(isLoggable(LogLevel.DEBUG)) sPrinter.json(tag, json);
+ }
+
+ public static void xml( String tag, String xml) {
+ if(isLoggable(LogLevel.DEBUG)) sPrinter.xml(tag, xml);
+ }
+
+ private static boolean isLoggable(LogLevel logLevel){
+ return sPrinter.getSettings().getLogLevel().level <= logLevel.level;
+ }
+}
diff --git a/foudations/mogo-utils/src/main/java/com/mogo/utils/logger/LoggerPrinter.java b/foudations/mogo-utils/src/main/java/com/mogo/utils/logger/LoggerPrinter.java
new file mode 100644
index 0000000..d822a53
--- /dev/null
+++ b/foudations/mogo-utils/src/main/java/com/mogo/utils/logger/LoggerPrinter.java
@@ -0,0 +1,261 @@
+
+package com.mogo.utils.logger;
+
+import android.text.TextUtils;
+import android.util.Log;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.StringReader;
+import java.io.StringWriter;
+
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.stream.StreamResult;
+import javax.xml.transform.stream.StreamSource;
+
+final class LoggerPrinter implements Printer {
+ private static final String TAG = "LoggerPrinter";
+
+ private static final int CHUNK_SIZE = 4000;
+ private static final int JSON_INDENT = 4;
+ private static final int MIN_STACK_OFFSET = 3;
+ 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_DOUBLE_LINE = '║';
+ private static final String DOUBLE_DIVIDER = "════════════════════════════════════════════";
+ private static final String SINGLE_DIVIDER = "────────────────────────────────────────────";
+ private static final String TOP_BORDER = "╔════════════════════════════════════════════════════════════════════════════════════════";
+ private static final String BOTTOM_BORDER = "╚════════════════════════════════════════════════════════════════════════════════════════";
+ private static final String MIDDLE_BORDER = "╟────────────────────────────────────────────────────────────────────────────────────────";
+
+ private final Settings mSettings = new Settings();
+
+ LoggerPrinter() {
+ }
+
+ public Settings init(LogLevel logLevel) {
+ return mSettings.setLogLevel(logLevel);
+ }
+
+ public Settings getSettings() {
+ return mSettings;
+ }
+
+ public void d( String tag, String message, Object... args) {
+ this.log(tag, LogLevel.DEBUG, message, args);
+ }
+
+ public void e( String tag, String message, Object... args) {
+ this.e(tag, null, message, args);
+ }
+
+ public void e( String tag, Throwable throwable, String message, Object... args) {
+ if (throwable != null && message != null) {
+ message = message + " : " + Log.getStackTraceString( throwable);
+ }
+
+ if (throwable != null && message == null) {
+ message = throwable.toString();
+ }
+
+ if (message == null) {
+ message = "No message/exception is set";
+ }
+
+ this.log(tag, LogLevel.ERROR, message, args);
+ }
+
+ public void w( String tag, String message, Object... args) {
+ this.log(tag, LogLevel.WARN, message, args);
+ }
+
+ public void i( String tag, String message, Object... args) {
+ this.log(tag, LogLevel.INFO, message, args);
+ }
+
+ public void v( String tag, String message, Object... args) {
+ this.log(tag, LogLevel.VERBOSE, message, args);
+ }
+
+ public void json( String tag, String json) {
+ if ( TextUtils.isEmpty(json)) {
+ this.d(tag, "Empty/Null json content");
+ } else {
+ try {
+ String message;
+ if (json.startsWith("{")) {
+ JSONObject e1 = new JSONObject(json);
+ message = e1.toString(4);
+ this.d(tag, message);
+ return;
+ }
+
+ if (json.startsWith("[")) {
+ JSONArray e = new JSONArray(json);
+ message = e.toString(4);
+ this.d(tag, message);
+ }
+ } catch ( JSONException var4) {
+ this.e(tag, var4.getCause().getMessage() + "\n" + json);
+ }
+
+ }
+ }
+
+ public void xml( String tag, String xml) {
+ if ( TextUtils.isEmpty(xml)) {
+ this.d(tag, "Empty/Null xml content");
+ } else {
+ try {
+ StreamSource e = new StreamSource(new StringReader(xml));
+ StreamResult xmlOutput = new StreamResult(new StringWriter());
+ Transformer transformer = TransformerFactory.newInstance().newTransformer();
+ transformer.setOutputProperty("indent", "yes");
+ transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
+ transformer.transform(e, xmlOutput);
+ this.d(tag, xmlOutput.getWriter().toString().replaceFirst(">", ">\n"));
+ } catch ( TransformerException var5) {
+ this.e(tag, var5.getCause().getMessage() + "\n" + xml);
+ }
+
+ }
+ }
+
+ public void normalLog( String tag, String message) {
+ if (!TextUtils.isEmpty(message)) {
+ this.logChunk(LogLevel.DEBUG, tag, message);
+ }
+ }
+
+ private synchronized void log(String tag, LogLevel logLevel, String msg, Object... args) {
+ String message = this.createMessage(msg, args);
+ int methodCount = this.getMethodCount();
+ this.logTopBorder(logLevel, tag);
+ this.logHeaderContent(logLevel, tag, methodCount);
+ byte[] bytes = message.getBytes();
+ int length = bytes.length;
+ if (length <= 4000) {
+ if (methodCount > 0) {
+ this.logDivider(logLevel, tag);
+ }
+
+ this.logContent(logLevel, tag, message);
+ this.logBottomBorder(logLevel, tag);
+ } else {
+ if (methodCount > 0) {
+ this.logDivider(logLevel, tag);
+ }
+
+ for (int i = 0; i < length; i += 4000) {
+ int count = Math.min(length - i, 4000);
+ this.logContent(logLevel, tag, new String(bytes, i, count));
+ }
+
+ this.logBottomBorder(logLevel, tag);
+ }
+ }
+
+ private void logTopBorder(LogLevel logLevel, String tag) {
+ this.logChunk(logLevel, tag, "╔════════════════════════════════════════════════════════════════════════════════════════");
+ }
+
+ private void logHeaderContent(LogLevel logLevel, String tag, int methodCount) {
+ StackTraceElement[] trace = Thread.currentThread().getStackTrace();
+ if (mSettings.isShowThreadInfo()) {
+ this.logChunk(logLevel, tag, "║ Thread: " + Thread.currentThread().getName());
+ this.logDivider(logLevel, tag);
+ }
+
+ String level = "";
+ int stackOffset = this.getStackOffset(trace) + mSettings.getMethodOffset();
+ 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) {
+ StringBuilder builder = new StringBuilder();
+ builder.append("║ ").append(level).append(this.getSimpleClassName(trace[stackIndex].getClassName())).append(".").append(trace[stackIndex].getMethodName()).append(" ").append(" (").append(trace[stackIndex].getFileName()).append(":").append(trace[stackIndex].getLineNumber()).append(")");
+ level = level + " ";
+ this.logChunk(logLevel, tag, builder.toString());
+ }
+ }
+
+ }
+
+ private void logBottomBorder(LogLevel logLevel, String tag) {
+ this.logChunk(logLevel, tag, "╚════════════════════════════════════════════════════════════════════════════════════════");
+ }
+
+ private void logDivider(LogLevel logLevel, String tag) {
+ this.logChunk(logLevel, tag, "╟────────────────────────────────────────────────────────────────────────────────────────");
+ }
+
+ private void logContent(LogLevel logLevel, String tag, String chunk) {
+ String[] lines = chunk.split( System.getProperty("line.separator"));
+
+ for ( String line : lines) {
+ this.logChunk(logLevel, tag, "║ " + line);
+ }
+ }
+
+ private void logChunk(LogLevel logLevel, String tag, String chunk) {
+ String finalTag = this.checkTag(tag);
+ switch (logLevel) {
+ case VERBOSE:
+ Log.v(finalTag, chunk);
+ break;
+ case INFO:
+ Log.i(finalTag, chunk);
+ break;
+ case DEBUG:
+ Log.d(finalTag, chunk);
+ break;
+ case WARN:
+ Log.w(finalTag, chunk);
+ break;
+ case ERROR:
+ Log.e(finalTag, chunk);
+ break;
+ case OFF:
+ break;
+ }
+
+ }
+
+ private String getSimpleClassName( String name) {
+ int lastIndex = name.lastIndexOf(".");
+ return name.substring(lastIndex + 1);
+ }
+
+ private String checkTag( String tag) {
+ return TextUtils.isEmpty(tag) ? TAG : tag;
+ }
+
+ private String createMessage( String message, Object... args) {
+ return (args == null || args.length == 0) ? message : String.format(message, args);
+ }
+
+ private int getMethodCount() {
+ return mSettings.getMethodCount();
+ }
+
+ private int getStackOffset( StackTraceElement[] trace) {
+ for (int i = 3; i < trace.length; ++i) {
+ StackTraceElement e = trace[i];
+ String name = e.getClassName();
+ if (!name.equals(LoggerPrinter.class.getName()) && !name.equals(Logger.class.getName())) {
+ --i;
+ return i;
+ }
+ }
+
+ return -1;
+ }
+}
diff --git a/foudations/mogo-utils/src/main/java/com/mogo/utils/logger/Printer.java b/foudations/mogo-utils/src/main/java/com/mogo/utils/logger/Printer.java
new file mode 100644
index 0000000..7bfec3f
--- /dev/null
+++ b/foudations/mogo-utils/src/main/java/com/mogo/utils/logger/Printer.java
@@ -0,0 +1,26 @@
+
+package com.mogo.utils.logger;
+
+public interface Printer {
+ Settings init(LogLevel logLevel);
+
+ Settings getSettings();
+
+ void d(String tag, String message, Object... args);
+
+ void e(String tag, String message, Object... args);
+
+ void e(String tag, Throwable throwable, String message, Object... args);
+
+ void w(String tag, String message, Object... args);
+
+ void i(String tag, String message, Object... args);
+
+ void v(String tag, String message, Object... args);
+
+ void json(String tag, String json);
+
+ void xml(String tag, String xml);
+
+ void normalLog(String tag, String message);
+}
diff --git a/foudations/mogo-utils/src/main/java/com/mogo/utils/logger/Settings.java b/foudations/mogo-utils/src/main/java/com/mogo/utils/logger/Settings.java
new file mode 100644
index 0000000..af6ec22
--- /dev/null
+++ b/foudations/mogo-utils/src/main/java/com/mogo/utils/logger/Settings.java
@@ -0,0 +1,52 @@
+
+package com.mogo.utils.logger;
+
+public final class Settings {
+ private int methodCount = 2;
+ private boolean showThreadInfo = true;
+ private int methodOffset = 0;
+ private LogLevel logLevel = LogLevel.DEBUG;
+
+ public Settings() {
+ }
+
+ public Settings hideThreadInfo() {
+ this.showThreadInfo = false;
+ return this;
+ }
+
+ public Settings setMethodCount(int methodCount) {
+ if(methodCount < 0) {
+ methodCount = 0;
+ }
+
+ this.methodCount = methodCount;
+ return this;
+ }
+
+ public Settings setLogLevel(LogLevel logLevel) {
+ this.logLevel = logLevel;
+ return this;
+ }
+
+ public Settings setMethodOffset(int offset) {
+ this.methodOffset = offset;
+ return this;
+ }
+
+ public int getMethodCount() {
+ return this.methodCount;
+ }
+
+ public boolean isShowThreadInfo() {
+ return this.showThreadInfo;
+ }
+
+ public LogLevel getLogLevel() {
+ return this.logLevel;
+ }
+
+ public int getMethodOffset() {
+ return this.methodOffset;
+ }
+}
diff --git a/foudations/mogo-utils/src/main/java/com/mogo/utils/storage/SharedPrefsMgr.java b/foudations/mogo-utils/src/main/java/com/mogo/utils/storage/SharedPrefsMgr.java
new file mode 100644
index 0000000..c57aa62
--- /dev/null
+++ b/foudations/mogo-utils/src/main/java/com/mogo/utils/storage/SharedPrefsMgr.java
@@ -0,0 +1,154 @@
+package com.mogo.utils.storage;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.support.annotation.NonNull;
+
+
+import java.util.Set;
+
+public class SharedPrefsMgr {
+
+ private static final String File_Name = "app_shared_pref";
+ private static SharedPrefsMgr sInstance;
+ private static SharedPreferences sSharedPrefs;
+
+ public synchronized static SharedPrefsMgr getInstance(@NonNull Context context ) {
+ if ( sInstance == null ) {
+ try {
+ sInstance = new SharedPrefsMgr( context.getApplicationContext() );
+ } catch ( Exception e ) {
+ sInstance = new SharedPrefsMgr();
+ }
+ }
+ return sInstance;
+ }
+
+ private SharedPrefsMgr() {
+
+ }
+
+ private SharedPrefsMgr( Context context ) {
+ try {
+ sSharedPrefs = context.getSharedPreferences( File_Name, Context.MODE_PRIVATE );
+ } catch ( Exception e ) {
+ e.printStackTrace();
+ }
+ }
+
+ public void putString( String key, String value ) {
+ try {
+ SharedPreferences.Editor editor = sSharedPrefs.edit();
+ editor.putString( key, value );
+ editor.apply();
+ } catch ( Exception e ) {
+ }
+ }
+
+ public String getString( String tag ) {
+ try {
+ return sSharedPrefs.getString( tag, "" );
+ } catch ( Exception e ) {
+ return "";
+ }
+ }
+
+ public String getString( String tag, String defVal ) {
+ try {
+ return sSharedPrefs.getString( tag, defVal );
+ } catch ( Exception e ) {
+ return "";
+ }
+ }
+
+ public boolean getBoolean( String key, boolean defaultValue ) {
+ try {
+ return sSharedPrefs.getBoolean( key, defaultValue );
+ } catch ( Exception e ) {
+ return defaultValue;
+ }
+ }
+
+ public long getLong( String key, long defaultValue ) {
+ try {
+ return sSharedPrefs.getLong( key, defaultValue );
+ } catch ( Exception e ) {
+ return defaultValue;
+ }
+ }
+
+ public float getFloat( String key, float defaultValue ) {
+ try {
+ return sSharedPrefs.getFloat( key, defaultValue );
+ } catch ( Exception e ) {
+ return defaultValue;
+ }
+ }
+
+ public int getInt( String key, int value ) {
+ try {
+ return sSharedPrefs.getInt( key, value );
+ } catch ( Exception e ) {
+ return value;
+ }
+ }
+
+ public void putBoolean( String key, boolean value ) {
+ try {
+ SharedPreferences.Editor editor = sSharedPrefs.edit();
+ editor.putBoolean( key, value );
+ editor.apply();
+ } catch ( Exception e ) {
+ }
+ }
+
+ public void putLong( String key, long value ) {
+ try {
+ SharedPreferences.Editor editor = sSharedPrefs.edit();
+ editor.putLong( key, value );
+ editor.apply();
+ } catch ( Exception e ) {
+ }
+ }
+
+ public void putInt( String key, int value ) {
+ try {
+ SharedPreferences.Editor editor = sSharedPrefs.edit();
+ editor.putInt( key, value );
+ editor.apply();
+ } catch ( Exception e ) {
+ }
+ }
+
+ public void putFloat( String key, float value ) {
+ try {
+ SharedPreferences.Editor editor = sSharedPrefs.edit();
+ editor.putFloat( key, value );
+ editor.apply();
+ } catch ( Exception e ) {
+ }
+ }
+
+ public void remove( String key ) {
+ try {
+ SharedPreferences.Editor editor = sSharedPrefs.edit();
+ editor.remove( key );
+ editor.apply();
+ } catch ( Exception e ) {
+ }
+ }
+
+ public void putStringSet( String key, Set< String > values ) {
+ try {
+ SharedPreferences.Editor editor = sSharedPrefs.edit();
+ editor.putStringSet( key, values );
+ editor.apply();
+ } catch ( Exception e ) {
+ }
+ }
+
+ public Set The cache stores its data in a directory on the filesystem. This
+ * directory must be exclusive to the cache; the cache may delete or overwrite
+ * files from its directory. It is an error for multiple processes to use the
+ * same cache directory at the same time.
+ *
+ * This cache limits the number of bytes that it will store on the
+ * filesystem. When the number of stored bytes exceeds the limit, the cache will
+ * remove entries in the background until the limit is satisfied. The limit is
+ * not strict: the cache may temporarily exceed it while waiting for files to be
+ * deleted. The limit does not include filesystem overhead or the cache
+ * journal so space-sensitive applications should set a conservative limit.
+ *
+ * Clients call {@link #edit} to create or update the values of an entry. An
+ * entry may have only one editor at one time; if a value is not available to be
+ * edited then {@link #edit} will return null.
+ * Clients call {@link #get} to read a snapshot of an entry. The read will
+ * observe the value at the time that {@link #get} was called. Updates and
+ * removals after the call do not impact ongoing reads.
+ *
+ * This class is tolerant of some I/O errors. If files are missing from the
+ * filesystem, the corresponding entries will be dropped from the cache. If
+ * an error occurs while writing a cache value, the edit will fail silently.
+ * Callers should handle other problems by catching {@code IOException} and
+ * responding appropriately.
+ */
+public final class DiskLruCache implements Closeable {
+ static final String JOURNAL_FILE = "journal";
+ static final String JOURNAL_FILE_TEMP = "journal.tmp";
+ static final String JOURNAL_FILE_BACKUP = "journal.bkp";
+ static final String MAGIC = "libcore.io.DiskLruCache";
+ static final String VERSION_1 = "1";
+ static final long ANY_SEQUENCE_NUMBER = -1;
+ static final String STRING_KEY_PATTERN = "[a-z0-9_-]{1,120}";
+ static final Pattern LEGAL_KEY_PATTERN = Pattern.compile(STRING_KEY_PATTERN);
+ private static final String CLEAN = "CLEAN";
+ private static final String DIRTY = "DIRTY";
+ private static final String REMOVE = "REMOVE";
+ private static final String READ = "READ";
+
+ /*
+ * This cache uses a journal file named "journal". A typical journal file
+ * looks like this:
+ * libcore.io.DiskLruCache
+ * 1
+ * 100
+ * 2
+ *
+ * CLEAN 3400330d1dfc7f3f7f4b8d4d803dfcf6 832 21054
+ * DIRTY 335c4c6028171cfddfbaae1a9c313c52
+ * CLEAN 335c4c6028171cfddfbaae1a9c313c52 3934 2342
+ * REMOVE 335c4c6028171cfddfbaae1a9c313c52
+ * DIRTY 1ab96a171faeeee38496d8b330771a7a
+ * CLEAN 1ab96a171faeeee38496d8b330771a7a 1600 234
+ * READ 335c4c6028171cfddfbaae1a9c313c52
+ * READ 3400330d1dfc7f3f7f4b8d4d803dfcf6
+ *
+ * The first five lines of the journal form its header. They are the
+ * constant string "libcore.io.DiskLruCache", the disk cache's version,
+ * the application's version, the value count, and a blank line.
+ *
+ * Each of the subsequent lines in the file is a record of the state of a
+ * cache entry. Each line contains space-separated values: a state, a key,
+ * and optional state-specific values.
+ * o DIRTY lines track that an entry is actively being created or updated.
+ * Every successful DIRTY action should be followed by a CLEAN or REMOVE
+ * action. DIRTY lines without a matching CLEAN or REMOVE indicate that
+ * temporary files may need to be deleted.
+ * o CLEAN lines track a cache entry that has been successfully published
+ * and may be read. A publish line is followed by the lengths of each of
+ * its values.
+ * o READ lines track accesses for LRU.
+ * o REMOVE lines track entries that have been deleted.
+ *
+ * The journal file is appended to as cache operations occur. The journal may
+ * occasionally be compacted by dropping redundant lines. A temporary file named
+ * "journal.tmp" will be used during compaction; that file should be deleted if
+ * it exists when the cache is opened.
+ */
+
+ private final File directory;
+ private final File journalFile;
+ private final File journalFileTmp;
+ private final File journalFileBackup;
+ private final int appVersion;
+ private long maxSize;
+ private final int valueCount;
+ private long size = 0;
+ private Writer journalWriter;
+ private final LinkedHashMap< String, Entry> lruEntries =
+ new LinkedHashMap< String, Entry>(0, 0.75f, true);
+ private int redundantOpCount;
+
+ /**
+ * To differentiate between old and current snapshots, each entry is given
+ * a sequence number each time an edit is committed. A snapshot is stale if
+ * its sequence number is not equal to its entry's sequence number.
+ */
+ private long nextSequenceNumber = 0;
+
+ /** This cache uses a single background thread to evict entries. */
+ final ThreadPoolExecutor executorService =
+ new ThreadPoolExecutor(0, 1, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue< Runnable >());
+ private final Callable< Void > cleanupCallable = new Callable< Void >() {
+ public Void call() throws Exception {
+ synchronized (DiskLruCache.this) {
+ if (journalWriter == null) {
+ return null; // Closed.
+ }
+ trimToSize();
+ if (journalRebuildRequired()) {
+ rebuildJournal();
+ redundantOpCount = 0;
+ }
+ }
+ return null;
+ }
+ };
+
+ private DiskLruCache( File directory, int appVersion, int valueCount, long maxSize) {
+ this.directory = directory;
+ this.appVersion = appVersion;
+ this.journalFile = new File(directory, JOURNAL_FILE);
+ this.journalFileTmp = new File(directory, JOURNAL_FILE_TEMP);
+ this.journalFileBackup = new File(directory, JOURNAL_FILE_BACKUP);
+ this.valueCount = valueCount;
+ this.maxSize = maxSize;
+ }
+
+ /**
+ * Opens the cache in {@code directory}, creating a cache if none exists
+ * there.
+ *
+ * @param directory a writable directory
+ * @param valueCount the number of values per cache entry. Must be positive.
+ * @param maxSize the maximum number of bytes this cache should use to store
+ * @throws IOException if reading or writing the cache directory fails
+ */
+ public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)
+ throws IOException {
+ if (maxSize <= 0) {
+ throw new IllegalArgumentException("maxSize <= 0");
+ }
+ if (valueCount <= 0) {
+ throw new IllegalArgumentException("valueCount <= 0");
+ }
+
+ // If a bkp file exists, use it instead.
+ File backupFile = new File(directory, JOURNAL_FILE_BACKUP);
+ if (backupFile.exists()) {
+ File journalFile = new File(directory, JOURNAL_FILE);
+ // If journal file also exists just delete backup file.
+ if (journalFile.exists()) {
+ backupFile.delete();
+ } else {
+ renameTo(backupFile, journalFile, false);
+ }
+ }
+
+ // Prefer to pick up where we left off.
+ DiskLruCache cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
+ if (cache.journalFile.exists()) {
+ try {
+ cache.readJournal();
+ cache.processJournal();
+ return cache;
+ } catch ( IOException journalIsCorrupt) {
+ System.out
+ .println("DiskLruCache "
+ + directory
+ + " is corrupt: "
+ + journalIsCorrupt.getMessage()
+ + ", removing");
+ cache.delete();
+ }
+ }
+
+ // Create a new empty cache.
+ directory.mkdirs();
+ cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
+ cache.rebuildJournal();
+ return cache;
+ }
+
+ private void readJournal() throws IOException {
+ StrictLineReader reader = new StrictLineReader(new FileInputStream(journalFile), CacheUtil.US_ASCII);
+ try {
+ String magic = reader.readLine();
+ String version = reader.readLine();
+ String appVersionString = reader.readLine();
+ String valueCountString = reader.readLine();
+ String blank = reader.readLine();
+ if (!MAGIC.equals(magic)
+ || !VERSION_1.equals(version)
+ || !Integer.toString(appVersion).equals(appVersionString)
+ || !Integer.toString(valueCount).equals(valueCountString)
+ || !"".equals(blank)) {
+ throw new IOException("unexpected journal header: [" + magic + ", " + version + ", "
+ + valueCountString + ", " + blank + "]");
+ }
+
+ int lineCount = 0;
+ while (true) {
+ try {
+ readJournalLine(reader.readLine());
+ lineCount++;
+ } catch ( EOFException endOfJournal) {
+ break;
+ }
+ }
+ redundantOpCount = lineCount - lruEntries.size();
+
+ // If we ended on a truncated line, rebuild the journal before appending to it.
+ if (reader.hasUnterminatedLine()) {
+ rebuildJournal();
+ } else {
+ journalWriter = new BufferedWriter(new OutputStreamWriter(
+ new FileOutputStream(journalFile, true), CacheUtil.US_ASCII));
+ }
+ } finally {
+ CacheUtil.closeQuietly(reader);
+ }
+ }
+
+ private void readJournalLine( String line) throws IOException {
+ int firstSpace = line.indexOf(' ');
+ if (firstSpace == -1) {
+ throw new IOException("unexpected journal line: " + line);
+ }
+
+ int keyBegin = firstSpace + 1;
+ int secondSpace = line.indexOf(' ', keyBegin);
+ final String key;
+ if (secondSpace == -1) {
+ key = line.substring(keyBegin);
+ if (firstSpace == REMOVE.length() && line.startsWith(REMOVE)) {
+ lruEntries.remove(key);
+ return;
+ }
+ } else {
+ key = line.substring(keyBegin, secondSpace);
+ }
+
+ Entry entry = lruEntries.get(key);
+ if (entry == null) {
+ entry = new Entry(key);
+ lruEntries.put(key, entry);
+ }
+
+ if (secondSpace != -1 && firstSpace == CLEAN.length() && line.startsWith(CLEAN)) {
+ String[] parts = line.substring(secondSpace + 1).split(" ");
+ entry.readable = true;
+ entry.currentEditor = null;
+ entry.setLengths(parts);
+ } else if (secondSpace == -1 && firstSpace == DIRTY.length() && line.startsWith(DIRTY)) {
+ entry.currentEditor = new Editor(entry);
+ } else if (secondSpace == -1 && firstSpace == READ.length() && line.startsWith(READ)) {
+ // This work was already done by calling lruEntries.get().
+ } else {
+ throw new IOException("unexpected journal line: " + line);
+ }
+ }
+
+ /**
+ * Computes the initial size and collects garbage as a part of opening the
+ * cache. Dirty entries are assumed to be inconsistent and will be deleted.
+ */
+ private void processJournal() throws IOException {
+ deleteIfExists(journalFileTmp);
+ for ( Iterator This class is used for buffered reading of lines. For purposes of this class, a line ends
+ * with "\n" or "\r\n". End of input is reported by throwing {@code EOFException}. Unterminated
+ * line at end of input is invalid and will be ignored, the caller may use {@code
+ * hasUnterminatedLine()} to detect it after catching the {@code EOFException}.
+ *
+ * This class is intended for reading input that strictly consists of lines, such as line-based
+ * cache entries or cache journal. Unlike the {@link java.io.BufferedReader} which in conjunction
+ * with {@link java.io.InputStreamReader} provides similar functionality, this class uses different
+ * end-of-input reporting and a more restrictive definition of a line.
+ *
+ * This class supports only charsets that encode '\r' and '\n' as a single byte with value 13
+ * and 10, respectively, and the representation of no other character contains these values.
+ * We currently check in constructor that the charset is one of US-ASCII, UTF-8 and ISO-8859-1.
+ * The default charset is US_ASCII.
+ */
+class StrictLineReader implements Closeable {
+ private static final byte CR = (byte) '\r';
+ private static final byte LF = (byte) '\n';
+
+ private final InputStream in;
+ private final Charset charset;
+
+ /*
+ * Buffered data is stored in {@code buf}. As long as no exception occurs, 0 <= pos <= end
+ * and the data in the range [pos, end) is buffered for reading. At end of input, if there is
+ * an unterminated line, we set end == -1, otherwise end == pos. If the underlying
+ * {@code InputStream} throws an {@code IOException}, end may remain as either pos or -1.
+ */
+ private byte[] buf;
+ private int pos;
+ private int end;
+
+ /**
+ * Constructs a new {@code LineReader} with the specified charset and the default capacity.
+ *
+ * @param in the {@code InputStream} to read data from.
+ * @param charset the charset used to decode data. Only US-ASCII, UTF-8 and ISO-8859-1 are
+ * supported.
+ * @throws NullPointerException if {@code in} or {@code charset} is null.
+ * @throws IllegalArgumentException if the specified charset is not supported.
+ */
+ public StrictLineReader( InputStream in, Charset charset) {
+ this(in, 8192, charset);
+ }
+
+ /**
+ * Constructs a new {@code LineReader} with the specified capacity and charset.
+ *
+ * @param in the {@code InputStream} to read data from.
+ * @param capacity the capacity of the buffer.
+ * @param charset the charset used to decode data. Only US-ASCII, UTF-8 and ISO-8859-1 are
+ * supported.
+ * @throws NullPointerException if {@code in} or {@code charset} is null.
+ * @throws IllegalArgumentException if {@code capacity} is negative or zero
+ * or the specified charset is not supported.
+ */
+ public StrictLineReader( InputStream in, int capacity, Charset charset) {
+ if (in == null || charset == null) {
+ throw new NullPointerException();
+ }
+ if (capacity < 0) {
+ throw new IllegalArgumentException("capacity <= 0");
+ }
+ if (!(charset.equals(CacheUtil.US_ASCII))) {
+ throw new IllegalArgumentException("Unsupported encoding");
+ }
+
+ this.in = in;
+ this.charset = charset;
+ buf = new byte[capacity];
+ }
+
+ /**
+ * Closes the reader by closing the underlying {@code InputStream} and
+ * marking this reader as closed.
+ *
+ * @throws IOException for errors when closing the underlying {@code InputStream}.
+ */
+ public void close() throws IOException {
+ synchronized (in) {
+ if (buf != null) {
+ buf = null;
+ in.close();
+ }
+ }
+ }
+
+ /**
+ * Reads the next line. A line ends with {@code "\n"} or {@code "\r\n"},
+ * this end of line marker is not included in the result.
+ *
+ * @return the next line from the input.
+ * @throws IOException for underlying {@code InputStream} errors.
+ * @throws EOFException for the end of source stream.
+ */
+ public String readLine() throws IOException {
+ synchronized (in) {
+ if (buf == null) {
+ throw new IOException("LineReader is closed");
+ }
+
+ // Read more data if we are at the end of the buffered labelList.
+ // Though it's an error to read after an exception, we will let {@code fillBuf()}
+ // throw again if that happens; thus we need to handle end == -1 as well as end == pos.
+ if (pos >= end) {
+ fillBuf();
+ }
+ // Try to find LF in the buffered data and return the line if successful.
+ for (int i = pos; i != end; ++i) {
+ if (buf[i] == LF) {
+ int lineEnd = (i != pos && buf[i - 1] == CR) ? i - 1 : i;
+ String res = new String(buf, pos, lineEnd - pos, charset.name());
+ pos = i + 1;
+ return res;
+ }
+ }
+
+ // Let's anticipate up to 80 characters on top of those already read.
+ ByteArrayOutputStream out = new ByteArrayOutputStream(end - pos + 80) {
+ @Override
+ public String toString() {
+ int length = (count > 0 && buf[count - 1] == CR) ? count - 1 : count;
+ try {
+ return new String(buf, 0, length, charset.name());
+ } catch ( UnsupportedEncodingException e) {
+ throw new AssertionError(e); // Since we control the charset this will never happen.
+ }
+ }
+ };
+
+ while (true) {
+ out.write(buf, pos, end - pos);
+ // Mark unterminated line in case fillBuf throws EOFException or IOException.
+ end = -1;
+ fillBuf();
+ // Try to find LF in the buffered data and return the line if successful.
+ for (int i = pos; i != end; ++i) {
+ if (buf[i] == LF) {
+ if (i != pos) {
+ out.write(buf, pos, i - pos);
+ }
+ pos = i + 1;
+ return out.toString();
+ }
+ }
+ }
+ }
+ }
+
+ public boolean hasUnterminatedLine() {
+ return end == -1;
+ }
+
+ /**
+ * Reads new input data into the buffer. Call only with pos == end or end == -1,
+ * depending on the desired outcome if the function throws.
+ */
+ private void fillBuf() throws IOException {
+ int result = in.read(buf, 0, buf.length);
+ if (result == -1) {
+ throw new EOFException();
+ }
+ pos = 0;
+ end = result;
+ }
+}
+
diff --git a/gradle.properties b/gradle.properties
index a980ec8..a425807 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,35 +1,40 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
-# Gradle settings configured through the IDE *will override*
-# any settings specified in this file.
+# Settings specified in this file will override any Gradle settings
+# configured through the IDE.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
+# The Gradle daemon aims to improve the startup and execution time of Gradle.
+# When set to true the Gradle daemon is to run the build.
+org.gradle.daemon=true
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
-org.gradle.jvmargs=-Xmx2048m
+# Default value: -Xmx10248m -XX:MaxPermSize=256m
+org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
-# org.gradle.parallel=true
-# AndroidX package structure to make it clearer which packages are bundled with the
-# Android operating system, and which are packaged with your app"s APK
-# https://developer.android.com/topic/libraries/support-library/androidx-rn
-android.useAndroidX=true
-# Automatically convert third-party libraries to use AndroidX
-android.enableJetifier=true
+org.gradle.parallel=true
+# Enables new incubating mode that makes Gradle selective when configuring projects.
+# Only relevant projects are configured which results in faster builds for large multi-projects.
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:configuration_on_demand
+org.gradle.configureondemand=true
+# Kotlin code style for this project: "official" or "obsolete":
+kotlin.code.style=official
## maven 配置
RELEASE_REPOSITORY_URL=http://nexus.zhidaoauto.com/repository/maven-releases/
SNAPSHOT_REPOSITORY_URL=http://nexus.zhidaoauto.com/repository/maven-snapshots/
USERNAME=xintai
PASSWORD=xintai2018
# 编译模式: false - 依赖本地版本, true - 依赖 maven 版本
-RELEASE=false
+RELEASE=true
# AI CLOUD 云平台
-MOGO_NETWORK_VERSION=1.0.12-SNAPSHOT
-MOGO_HTTPDNS_VERSION=1.0.12-SNAPSHOT
-MOGO_PASSPORT_VERSION=1.0.12-SNAPSHOT
-MOGO_SOCKET_VERSION=1.0.12-SNAPSHOT
-MOGO_REALTIME_VERSION=1.0.12-SNAPSHOT
-MOGO_TANLU_VERSION=1.0.12-SNAPSHOT
-MOGO_LIVE_VERSION=1.0.12-SNAPSHOT
-MOGO_TRAFFICLIVE_VERSION=1.0.12-SNAPSHOT
+MOGO_UTILS_VERSION=1.0.13-SNAPSHOT
+MOGO_NETWORK_VERSION=1.0.13-SNAPSHOT
+MOGO_HTTPDNS_VERSION=1.0.13-SNAPSHOT
+MOGO_PASSPORT_VERSION=1.0.13-SNAPSHOT
+MOGO_SOCKET_VERSION=1.0.13-SNAPSHOT
+MOGO_REALTIME_VERSION=1.0.13-SNAPSHOT
+MOGO_TANLU_VERSION=1.0.13-SNAPSHOT
+MOGO_LIVE_VERSION=1.0.13-SNAPSHOT
+MOGO_TRAFFICLIVE_VERSION=1.0.13-SNAPSHOT
diff --git a/modules.txt b/modules.txt
index 5881de5..9c3c2e8 100644
--- a/modules.txt
+++ b/modules.txt
@@ -1,3 +1,4 @@
+:foudations:mogo-utils
:foudations:mogo-httpdns
:foudations:mogo-passport
:foudations:mogo-network
diff --git a/modules/mogo-realtime/src/main/java/com/mogo/realtime/core/SimpleLocationCorrectStrategy.java b/modules/mogo-realtime/src/main/java/com/mogo/realtime/core/SimpleLocationCorrectStrategy.java
index f50927d..ac806c5 100644
--- a/modules/mogo-realtime/src/main/java/com/mogo/realtime/core/SimpleLocationCorrectStrategy.java
+++ b/modules/mogo-realtime/src/main/java/com/mogo/realtime/core/SimpleLocationCorrectStrategy.java
@@ -1,8 +1,7 @@
package com.mogo.realtime.core;
import android.os.SystemClock;
-
-import androidx.annotation.Keep;
+import android.support.annotation.Keep;
import com.mogo.cloud.commons.utils.CoordinateUtils;
import com.mogo.cloud.passport.MoGoAiCloudClient;
diff --git a/modules/mogo-realtime/src/main/java/com/mogo/realtime/core/UploadInTimeHandler.java b/modules/mogo-realtime/src/main/java/com/mogo/realtime/core/UploadInTimeHandler.java
index 700ff2c..9ed7490 100644
--- a/modules/mogo-realtime/src/main/java/com/mogo/realtime/core/UploadInTimeHandler.java
+++ b/modules/mogo-realtime/src/main/java/com/mogo/realtime/core/UploadInTimeHandler.java
@@ -2,8 +2,7 @@ package com.mogo.realtime.core;
import android.os.Handler;
import android.os.Message;
-
-import androidx.annotation.Keep;
+import android.support.annotation.Keep;
import com.mogo.utils.WorkThreadHandler;
diff --git a/modules/mogo-realtime/src/main/java/com/mogo/realtime/entity/CloudLocationInfo.java b/modules/mogo-realtime/src/main/java/com/mogo/realtime/entity/CloudLocationInfo.java
index 919bf0e..d2b6259 100644
--- a/modules/mogo-realtime/src/main/java/com/mogo/realtime/entity/CloudLocationInfo.java
+++ b/modules/mogo-realtime/src/main/java/com/mogo/realtime/entity/CloudLocationInfo.java
@@ -3,7 +3,7 @@ package com.mogo.realtime.entity;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
-import androidx.annotation.RequiresApi;
+import android.support.annotation.RequiresApi;
import com.mogo.cloud.commons.utils.CoordinateUtils;
diff --git a/modules/mogo-realtime/src/main/java/com/mogo/realtime/socket/SocketHandler.java b/modules/mogo-realtime/src/main/java/com/mogo/realtime/socket/SocketHandler.java
index 8e60e8f..2adf80b 100644
--- a/modules/mogo-realtime/src/main/java/com/mogo/realtime/socket/SocketHandler.java
+++ b/modules/mogo-realtime/src/main/java/com/mogo/realtime/socket/SocketHandler.java
@@ -16,8 +16,8 @@ import com.mogo.realtime.entity.MogoSnapshotSetData;
import com.mogo.realtime.entity.OnePerSecondSendContent;
import com.mogo.realtime.spi.RealTimeProviderImp;
import com.mogo.realtime.util.MortonCode;
+import com.mogo.utils.GsonUtil;
import com.mogo.utils.logger.Logger;
-import com.mogo.utils.network.utils.GsonUtil;
import com.zhidao.ptech.connsvr.protocol.MogoConnsvr;
import java.util.ArrayList;
diff --git a/modules/mogo-realtime/src/main/java/com/mogo/realtime/util/MogoLatLng.java b/modules/mogo-realtime/src/main/java/com/mogo/realtime/util/MogoLatLng.java
index 99ccc37..df38fdb 100644
--- a/modules/mogo-realtime/src/main/java/com/mogo/realtime/util/MogoLatLng.java
+++ b/modules/mogo-realtime/src/main/java/com/mogo/realtime/util/MogoLatLng.java
@@ -3,8 +3,7 @@ package com.mogo.realtime.util;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
-
-import androidx.annotation.RequiresApi;
+import android.support.annotation.RequiresApi;
import java.util.Objects;
diff --git a/modules/mogo-tanlu/src/main/java/com/mogo/cloud/tanlu/api/MogoRoadSearchManager.java b/modules/mogo-tanlu/src/main/java/com/mogo/cloud/tanlu/api/MogoRoadSearchManager.java
index b6dbed7..801e907 100644
--- a/modules/mogo-tanlu/src/main/java/com/mogo/cloud/tanlu/api/MogoRoadSearchManager.java
+++ b/modules/mogo-tanlu/src/main/java/com/mogo/cloud/tanlu/api/MogoRoadSearchManager.java
@@ -11,7 +11,7 @@ import com.mogo.cloud.tanlu.bean.RoadInfoRequest;
import com.mogo.cloud.tanlu.bean.RoadInfos;
import com.mogo.cloud.tanlu.constant.HttpConstant;
import com.mogo.cloud.tanlu.net.TanluApiService;
-import com.mogo.utils.network.utils.GsonUtil;
+import com.mogo.utils.GsonUtil;
import java.util.HashMap;
import java.util.Map;
diff --git a/modules/mogo-tanlu/src/main/java/com/mogo/cloud/tanlu/utils/FileUtil.kt b/modules/mogo-tanlu/src/main/java/com/mogo/cloud/tanlu/utils/FileUtil.kt
index 0449e4b..03b2f15 100644
--- a/modules/mogo-tanlu/src/main/java/com/mogo/cloud/tanlu/utils/FileUtil.kt
+++ b/modules/mogo-tanlu/src/main/java/com/mogo/cloud/tanlu/utils/FileUtil.kt
@@ -3,8 +3,7 @@ package com.mogo.cloud.tanlu.utils
import android.graphics.Bitmap
import android.media.MediaMetadataRetriever
import android.os.Environment
-import android.util.Log
-import androidx.annotation.Keep
+import android.support.annotation.Keep
import java.io.*
import java.text.SimpleDateFormat
import java.util.*
diff --git a/modules/mogo-tanlu/src/main/java/com/mogo/cloud/tanlu/utils/TanluUtils.java b/modules/mogo-tanlu/src/main/java/com/mogo/cloud/tanlu/utils/TanluUtils.java
index 6392114..2eef7e2 100644
--- a/modules/mogo-tanlu/src/main/java/com/mogo/cloud/tanlu/utils/TanluUtils.java
+++ b/modules/mogo-tanlu/src/main/java/com/mogo/cloud/tanlu/utils/TanluUtils.java
@@ -1,6 +1,7 @@
package com.mogo.cloud.tanlu.utils;
-import androidx.annotation.Keep;
+
+import android.support.annotation.Keep;
import java.util.regex.Pattern;
diff --git a/settings.gradle b/settings.gradle
index 43363df..fe16c67 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1,5 +1,5 @@
include ':modules:mogo-trafficlive'
-include ':foudations:mogo-common'
+include ':foudations:mogo-utils'
include ':foudations:mogo-live'
include ':foudations:mogo-socket'
include ':modules:mogo-realtime'
+ *
+ * Every {@link #edit} call must be matched by a call to {@link Editor#commit}
+ * or {@link Editor#abort}. Committing is atomic: a read observes the full set
+ * of values as they were before or after the commit, but never a mix of values.
+ *
+ *