diff --git a/app/src/main/java/com/hippo/util/AppConfig.java b/app/src/main/java/com/hippo/util/AppConfig.java new file mode 100644 index 000000000..ff1777c6e --- /dev/null +++ b/app/src/main/java/com/hippo/util/AppConfig.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2015 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.util; + +import android.os.Environment; +import android.support.annotation.Nullable; + +import java.io.File; + +/** + * Something about this app + */ +public final class AppConfig { + + private static final String APP_FOLDER_NAME = "EhViewer"; + + public static @Nullable File getAppFolder() { + if (Environment.getExternalStorageState() + .equals(android.os.Environment.MEDIA_MOUNTED)) { + return new File(Environment.getExternalStorageDirectory(), APP_FOLDER_NAME); + } else { + return null; + } + } + + public static @Nullable File getFileInAppFolder(String filename) { + File appFolder = getAppFolder(); + if (appFolder != null) { + return new File(appFolder, filename); + } else { + return null; + } + } +} diff --git a/app/src/main/java/com/hippo/util/Storage.java b/app/src/main/java/com/hippo/util/FileUtils.java similarity index 54% rename from app/src/main/java/com/hippo/util/Storage.java rename to app/src/main/java/com/hippo/util/FileUtils.java index 80c80cf7d..1e07ca889 100644 --- a/app/src/main/java/com/hippo/util/Storage.java +++ b/app/src/main/java/com/hippo/util/FileUtils.java @@ -15,5 +15,23 @@ package com.hippo.util; -public final class Storage { +import java.io.File; + +public final class FileUtils { + + public static boolean ensureFile(File file) { + return file != null && (!file.exists() || file.isFile()); + } + + public static boolean ensureDirectory(File file) { + if (file != null) { + if (file.exists()) { + return file.isDirectory(); + } else { + return file.mkdirs(); + } + } else { + return false; + } + } } diff --git a/app/src/main/java/com/hippo/util/Log.java b/app/src/main/java/com/hippo/util/Log.java index 0b1f4d420..4c0d9c027 100644 --- a/app/src/main/java/com/hippo/util/Log.java +++ b/app/src/main/java/com/hippo/util/Log.java @@ -15,7 +15,6 @@ package com.hippo.util; -import android.os.Looper; import android.os.Process; import com.hippo.ehviewer.BuildConfig; @@ -23,8 +22,11 @@ import com.hippo.ehviewer.BuildConfig; import java.io.File; import java.io.FileWriter; import java.io.IOException; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; public class Log { @@ -33,14 +35,20 @@ public class Log { private static final String NO_MESSAGE = "No Message"; - private static final SaveLogThreadPool mSaveLogThreadPool; - private static final Pool mSaveLogTaskPool; + private static final SaveLogThreadExecutor sSaveLogThreadPool; + private static final Pool sSaveLogTaskPool; - private static final File sLogFile = new File("/sdcard/EhViewer/log.log"); // TODO + private static final DateFormat sDataFormat; + + private static final File sLogFile; + private static final boolean sSupportSaveLog; static { - mSaveLogThreadPool = new SaveLogThreadPool(); - mSaveLogTaskPool = new Pool<>(10); + sSaveLogThreadPool = new SaveLogThreadExecutor(); + sSaveLogTaskPool = new Pool<>(10); + sDataFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.ENGLISH); + sLogFile = AppConfig.getFileInAppFolder("ehviewer.log"); + sSupportSaveLog = FileUtils.ensureFile(sLogFile); } private static String avoidNull(String str) { @@ -56,24 +64,27 @@ public class Log { } private static void appendSaveLogTask(long time, String msg, Throwable tr) { - SaveMsgTask task = mSaveLogTaskPool.obtain(); + if (!sSupportSaveLog) { + return; + } + + SaveMsgTask task = sSaveLogTaskPool.obtain(); if (task == null) { task = new SaveMsgTask(); } task.setParams(time, msg, tr); try { - mSaveLogThreadPool.execute(task); + sSaveLogThreadPool.execute(task); } catch (Throwable t) { Log.e("Get error when save message to file", t); } } - // TODO Need a better serial thread executor - private static class SaveLogThreadPool extends ThreadPoolExecutor { + private static class SaveLogThreadExecutor extends SerialThreadExecutor { - public SaveLogThreadPool() { - super(0, 1, 10L, TimeUnit.SECONDS, new LinkedBlockingQueue(), + public SaveLogThreadExecutor() { + super(10L, TimeUnit.SECONDS, new LinkedBlockingQueue(), new PriorityThreadFactory("SaveLog", Process.THREAD_PRIORITY_BACKGROUND)); } @@ -81,7 +92,7 @@ public class Log { if (r instanceof SaveMsgTask) { SaveMsgTask task = (SaveMsgTask) r; task.clear(); - mSaveLogTaskPool.recycle(task); + sSaveLogTaskPool.recycle(task); } } } @@ -104,15 +115,13 @@ public class Log { @Override public void run() { - - Log.d("MainLooper thread is " + Looper.getMainLooper().getThread().toString()); - Log.d("This thread is " + Thread.currentThread().toString()); - FileWriter fw = null; try { fw = new FileWriter(sLogFile, true); - fw.append(Long.toString(mTime)).append(" : ").append(mMsg).append("\n") - .append(Log.getStackTraceString(mTr)); + fw.append(sDataFormat.format(new Date(mTime))).append(" ").append(mMsg).append("\n"); + if (mTr != null) { + fw.append(Log.getStackTraceString(mTr)).append("\n"); + } fw.flush(); // TODO Only do flush when the thread is about to close } catch (IOException e) { Log.e("Get error when save message to file", e); diff --git a/app/src/main/java/com/hippo/util/SerialThreadExecutor.java b/app/src/main/java/com/hippo/util/SerialThreadExecutor.java new file mode 100644 index 000000000..8a2d492cd --- /dev/null +++ b/app/src/main/java/com/hippo/util/SerialThreadExecutor.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2015 Hippo Seven + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hippo.util; + +import android.support.annotation.NonNull; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.Executor; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +public class SerialThreadExecutor implements Executor, Runnable { + + private long mKeepAliveTime; + private BlockingQueue mWorkQueue; + private ThreadFactory mThreadFactory; + + private transient Thread mThread; + + private final Lock mThreadLock = new ReentrantLock(); + private final Object mWaitLock = new Object(); + + public SerialThreadExecutor(long keepAliveTime, TimeUnit unit, + BlockingQueue workQueue, ThreadFactory threadFactory) { + mKeepAliveTime = unit.toMillis(keepAliveTime); + mWorkQueue = workQueue; + mThreadFactory = threadFactory; + } + + @Override + public void execute(@NonNull Runnable command) { + try { + mWorkQueue.put(command); + ensureThread(); + } catch (Exception e) { + putFailed(command, e); + } + } + + private void ensureThread() { + mThreadLock.lock(); + if (mThread == null) { + mThread = mThreadFactory.newThread(this); + mThread.start(); + } else { + synchronized (mWaitLock) { + mWaitLock.notifyAll(); + } + } + mThreadLock.unlock(); + } + + protected void putFailed(@NonNull Runnable command, @NonNull Exception e) { + } + + protected void beforeExecute(Thread t, Runnable r) { + } + + protected void afterExecute(Runnable r, Throwable t) { + } + + protected void beforeExcute() { + + } + + @Override + public void run() { + boolean hasWait = false; + + for (;;) { + mThreadLock.lock(); + + if (mWorkQueue.isEmpty()) { + if (hasWait) { + mThread = null; + mThreadLock.unlock(); + // Have waitted enough time + break; + } else { + hasWait = true; + synchronized (mWaitLock) { + mThreadLock.unlock(); + try { + mWaitLock.wait(mKeepAliveTime); + } catch (InterruptedException e) { + // Empty + } + continue; + } + } + } else { + mThreadLock.unlock(); + } + + hasWait = false; + Runnable runnable = mWorkQueue.poll(); + if (runnable != null) { + beforeExecute(mThread, runnable); + Throwable throwable = null; + try { + runnable.run(); + } catch (Throwable tr) { + throwable = tr; + } + afterExecute(runnable, throwable); + } + } + } +}