├── .gitignore ├── README.md ├── build.gradle ├── proguard-rules.txt └── src └── main ├── AndroidManifest.xml └── java └── org └── syxc └── util ├── CrashHandler.java ├── DateFormater.java ├── Logger.java └── SafeAsyncTask.java /.gitignore: -------------------------------------------------------------------------------- 1 | # built application files 2 | *.apk 3 | *.ap_ 4 | *.jar 5 | 6 | # lint folder 7 | lint 8 | 9 | # files for the dex VM 10 | *.dex 11 | 12 | # Java class files 13 | *.class 14 | classes/ 15 | 16 | # generated files 17 | bin/ 18 | gen/ 19 | classes/ 20 | gen-external-apklibs/ 21 | 22 | # Local configuration file (sdk path, etc) 23 | local.properties 24 | 25 | # Eclipse project files 26 | .classpath 27 | .project 28 | .settings/ 29 | */.classpath 30 | */.project 31 | */.settings/ 32 | 33 | # Proguard folder generated by Eclipse 34 | proguard/ 35 | 36 | # Intellij project files 37 | *.iml 38 | *.ipr 39 | *.iws 40 | .idea/* 41 | 42 | # OSX files 43 | .DS_Store 44 | 45 | # Windows files 46 | Thumbs.db 47 | 48 | # Vim/Emacs 49 | *~ 50 | *.swp 51 | 52 | # backup files 53 | *.bak 54 | 55 | # Maven 56 | target/ 57 | 58 | # Gradle 59 | .gradle 60 | /build 61 | 62 | # Custom 63 | lib 64 | */test-output 65 | temp-testng-customsuite.xml 66 | **pom.xml.releaseBackup 67 | release.properties 68 | project.properties 69 | */seed.txt 70 | gen-external-apklibs 71 | 72 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LogUtil 2 | 3 | A custom Android log tools. 4 | 5 | 定制的 Android 日志处理工具,用于控制 LogCat 中日志信息的显示以及根据需要记录日志到 SD 卡中。 6 | 7 | > Android 开发中,LogCat 中日志信息的输出是比较消耗资源的,这尤其体现在用模拟器跑 Android 程序的时候。 8 | 在应用开发阶段,我们是需要在 LogCat 中查看我们的一些调试信息,但当应用发布给用户使用时, 9 | 我们可以关闭这些调试信息在 LogCat 中的输出,只根据我们的需要记录某些级别的日志信息。这个 LogUtil 就是用于解决这个问题。 10 | 11 | 使用 Gradle 编译需要注意的地方: 12 | 13 | - Gradle version >= Gradle 1.8 14 | - 在工程目录下添加一个 ```local.properties``` 文件,以下是我机器上该文件的配置: 15 | 16 | ```bash 17 | # This file is automatically generated by Android Studio. 18 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 19 | # 20 | # This file should *NOT* be checked into Version Control Systems, 21 | # as it contains information specific to your local configuration. 22 | # 23 | # Location of the SDK. This is only used by Gradle. 24 | # For customization when using a Version Control System, please read the 25 | # header note. 26 | sdk.dir=/Users/syxc/Application/android-sdk-macosx 27 | ``` 28 | 29 | Thanks! 30 | 31 | ## License 32 | 33 | The MIT license. 34 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | mavenCentral() 4 | } 5 | dependencies { 6 | classpath 'com.android.tools.build:gradle:0.6.+' 7 | } 8 | } 9 | 10 | apply plugin: 'android-library' 11 | 12 | repositories { 13 | mavenCentral() 14 | } 15 | 16 | android { 17 | compileSdkVersion 19 18 | buildToolsVersion '19.0.0' 19 | 20 | defaultConfig { 21 | minSdkVersion 8 22 | targetSdkVersion 19 23 | } 24 | release { 25 | runProguard false 26 | proguardFile 'proguard-rules.txt' 27 | proguardFile getDefaultProguardFile('proguard-android.txt') 28 | } 29 | } 30 | 31 | dependencies {} 32 | -------------------------------------------------------------------------------- /proguard-rules.txt: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/syxc/Application/android-sdk-macosx/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the ProGuard 5 | # include property in project.properties. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} -------------------------------------------------------------------------------- /src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/main/java/org/syxc/util/CrashHandler.java: -------------------------------------------------------------------------------- 1 | package org.syxc.util; 2 | 3 | import android.app.AlertDialog; 4 | import android.content.Context; 5 | import android.content.DialogInterface; 6 | import android.content.Intent; 7 | import android.content.pm.PackageInfo; 8 | import android.content.pm.PackageManager; 9 | import android.os.Build; 10 | import android.os.Environment; 11 | import android.os.Looper; 12 | import android.os.StatFs; 13 | 14 | import java.io.File; 15 | import java.io.PrintWriter; 16 | import java.io.StringWriter; 17 | import java.io.Writer; 18 | import java.lang.Thread.UncaughtExceptionHandler; 19 | import java.util.Date; 20 | import java.util.Locale; 21 | 22 | /** 23 | * {@link UncaughtExceptionHandler} Send an email with 24 | * some debug information to the developer. 25 | *

26 | * In the Activity of onCreate calling methods: 27 | *

28 | * CrashHandler crashHandler = CrashHandler.getInstance(); 29 | * crashHandler.init(this); 30 | */ 31 | public class CrashHandler implements UncaughtExceptionHandler { 32 | 33 | private static final String TAG = "CrashHandler"; 34 | 35 | private static final String RECIPIENT = "yourname@gmail.com"; 36 | 37 | private static CrashHandler instance; 38 | private Context mContext; 39 | private Thread.UncaughtExceptionHandler mDefaultHandler; 40 | 41 | private CrashHandler() { 42 | 43 | } 44 | 45 | public static CrashHandler getInstance() { 46 | if (instance == null) { 47 | instance = new CrashHandler(); 48 | } 49 | return instance; 50 | } 51 | 52 | public void init(Context ctx) { 53 | mContext = ctx; 54 | mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler(); 55 | Thread.setDefaultUncaughtExceptionHandler(this); 56 | } 57 | 58 | @Override 59 | public void uncaughtException(Thread t, Throwable e) { 60 | try { 61 | Date curDate = new Date(); 62 | 63 | StringBuilder report = new StringBuilder(); 64 | report.append("Error report collected on: ").append(curDate.toString()).append('\n').append('\n'); 65 | report.append("Information: ").append('\n'); 66 | addInformation(report); 67 | report.append('\n').append('\n'); 68 | report.append("Stack: \n"); 69 | 70 | final Writer result = new StringWriter(); 71 | final PrintWriter printWriter = new PrintWriter(result); 72 | 73 | e.printStackTrace(printWriter); 74 | report.append(result.toString()); 75 | printWriter.close(); 76 | report.append('\n'); 77 | report.append("--- End of current Report ---"); 78 | 79 | Logger.e(TAG, "Error while sendErrorMail " + report); 80 | 81 | sendErrorMail(report); 82 | } catch (Throwable ignore) { 83 | Logger.e(TAG, "Error while sending error email", ignore); 84 | } 85 | } 86 | 87 | private StatFs getStatFs() { 88 | File path = Environment.getDataDirectory(); 89 | return new StatFs(path.getPath()); 90 | } 91 | 92 | private long getAvailableInternalMemorySize(StatFs stat) { 93 | long blockSize = stat.getBlockSize(); 94 | long availableBlocks = stat.getAvailableBlocks(); 95 | return availableBlocks * blockSize; 96 | } 97 | 98 | private long getTotalInternalMemorySize(StatFs stat) { 99 | long blockSize = stat.getBlockSize(); 100 | long totalBlocks = stat.getBlockCount(); 101 | return totalBlocks * blockSize; 102 | } 103 | 104 | private void addInformation(StringBuilder message) { 105 | message.append("Locale: ").append(Locale.getDefault()).append('\n'); 106 | try { 107 | PackageManager pm = mContext.getPackageManager(); 108 | PackageInfo pi; 109 | pi = pm.getPackageInfo(mContext.getPackageName(), 0); 110 | message.append("Version: ").append(pi.versionName).append('\n'); 111 | message.append("Package: ").append(pi.packageName).append('\n'); 112 | } catch (Exception e) { 113 | Logger.e(TAG, "Error", e); 114 | message.append("Could not get Version information for ").append(mContext.getPackageName()); 115 | } 116 | message.append("Phone Model: ").append(Build.MODEL).append('\n'); 117 | message.append("Android Version: ").append(Build.VERSION.RELEASE).append('\n'); 118 | message.append("Board: ").append(Build.BOARD).append('\n'); 119 | message.append("Brand: ").append(Build.BRAND).append('\n'); 120 | message.append("Device: ").append(Build.DEVICE).append('\n'); 121 | message.append("Host: ").append(Build.HOST).append('\n'); 122 | message.append("ID: ").append(Build.ID).append('\n'); 123 | message.append("Model: ").append(Build.MODEL).append('\n'); 124 | message.append("Product: ").append(Build.PRODUCT).append('\n'); 125 | message.append("Type: ").append(Build.TYPE).append('\n'); 126 | 127 | StatFs stat = getStatFs(); 128 | 129 | message.append("Total Internal memory: ") 130 | .append(getTotalInternalMemorySize(stat)) 131 | .append('\n'); 132 | message.append("Available Internal memory: ") 133 | .append(getAvailableInternalMemorySize(stat)) 134 | .append('\n'); 135 | } 136 | 137 | /** 138 | * This method for call alert dialog when application crashed! 139 | * 140 | * @param err 141 | */ 142 | private void sendErrorMail(final StringBuilder err) { 143 | final AlertDialog.Builder builder = new AlertDialog.Builder(mContext); 144 | 145 | new Thread() { 146 | 147 | @Override 148 | public void run() { 149 | Looper.prepare(); 150 | 151 | builder.setTitle("Sorry...!"); 152 | builder.create(); 153 | builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() { 154 | 155 | @Override 156 | public void onClick(DialogInterface dialog, int which) { 157 | System.exit(0); 158 | } 159 | }); 160 | builder.setPositiveButton("Report", new DialogInterface.OnClickListener() { 161 | 162 | @Override 163 | public void onClick(DialogInterface dialog, int which) { 164 | Intent sendIntent = new Intent(Intent.ACTION_SEND); 165 | 166 | String subject = "Your App crashed! Fix it!"; 167 | StringBuilder body = new StringBuilder("Yoddle"); 168 | body.append('\n').append('\n'); 169 | body.append(err).append('\n').append('\n'); 170 | 171 | sendIntent.setType("message/rfc822"); 172 | sendIntent.putExtra(Intent.EXTRA_EMAIL, new String[]{RECIPIENT}); 173 | sendIntent.putExtra(Intent.EXTRA_TEXT, body.toString()); 174 | sendIntent.putExtra(Intent.EXTRA_SUBJECT, subject); 175 | 176 | mContext.startActivity(Intent.createChooser(sendIntent, "Error Report")); 177 | 178 | System.exit(0); 179 | } 180 | }); 181 | builder.setMessage("Unfortunately, This application has stopped!"); 182 | builder.show(); 183 | 184 | Looper.loop(); 185 | } 186 | }.start(); 187 | } 188 | 189 | } 190 | -------------------------------------------------------------------------------- /src/main/java/org/syxc/util/DateFormater.java: -------------------------------------------------------------------------------- 1 | package org.syxc.util; 2 | 3 | public enum DateFormater { 4 | 5 | NORMAL("yyyy-MM-dd HH:mm"), 6 | DD("yyyy-MM-dd"), 7 | SS("yyyy-MM-dd HH:mm:ss"); 8 | 9 | private String value; 10 | 11 | private DateFormater(String value) { 12 | this.value = value; 13 | } 14 | 15 | public String getValue() { 16 | return value; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/org/syxc/util/Logger.java: -------------------------------------------------------------------------------- 1 | package org.syxc.util; 2 | 3 | import android.os.Environment; 4 | import android.util.Log; 5 | import android.util.SparseArray; 6 | 7 | import java.io.File; 8 | import java.io.FileNotFoundException; 9 | import java.io.FileOutputStream; 10 | import java.io.IOException; 11 | import java.text.DateFormat; 12 | import java.text.SimpleDateFormat; 13 | import java.util.Date; 14 | 15 | 16 | /** 17 | * A custom Android log class 18 | * 19 | * @author syxc 20 | */ 21 | public final class Logger { 22 | 23 | private static final String TAG = "Logger"; 24 | 25 | private static final String DOWNLOADS_PATH; 26 | 27 | protected static final String LOG_PREFIX; 28 | protected static final String LOG_DIR; 29 | 30 | public static boolean debug = false; // Log switch open, development, released when closed(LogCat) 31 | public static int level = Log.ERROR; // Write file level 32 | 33 | 34 | static { 35 | DOWNLOADS_PATH = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath(); 36 | LOG_PREFIX = setLogPrefix(""); 37 | LOG_DIR = setLogPath(""); 38 | } 39 | 40 | 41 | public static void v(String tag, String msg) { 42 | trace(Log.VERBOSE, tag, msg); 43 | } 44 | 45 | public static void v(String tag, String msg, Throwable tr) { 46 | trace(Log.VERBOSE, tag, msg, tr); 47 | } 48 | 49 | public static void d(String tag, String msg) { 50 | trace(Log.DEBUG, tag, msg); 51 | } 52 | 53 | public static void d(String tag, String msg, Throwable tr) { 54 | trace(Log.DEBUG, tag, msg, tr); 55 | } 56 | 57 | public static void i(String tag, String msg) { 58 | trace(Log.INFO, tag, msg); 59 | } 60 | 61 | public static void i(String tag, String msg, Throwable tr) { 62 | trace(Log.INFO, tag, msg, tr); 63 | } 64 | 65 | public static void w(String tag, String msg) { 66 | trace(Log.WARN, tag, msg); 67 | } 68 | 69 | public static void w(String tag, String msg, Throwable tr) { 70 | trace(Log.WARN, tag, msg, tr); 71 | } 72 | 73 | public static void e(String tag, String msg) { 74 | trace(Log.ERROR, tag, msg); 75 | } 76 | 77 | public static void e(String tag, String msg, Throwable tr) { 78 | trace(Log.ERROR, tag, msg, tr); 79 | } 80 | 81 | 82 | /** 83 | * Custom Log output style 84 | * 85 | * @param type Log type 86 | * @param tag TAG 87 | * @param msg Log message 88 | */ 89 | private static void trace(final int type, String tag, final String msg) { 90 | // LogCat 91 | if (debug) { 92 | switch (type) { 93 | case Log.VERBOSE: 94 | Log.v(tag, msg); 95 | break; 96 | case Log.DEBUG: 97 | Log.d(tag, msg); 98 | break; 99 | case Log.INFO: 100 | Log.i(tag, msg); 101 | break; 102 | case Log.WARN: 103 | Log.w(tag, msg); 104 | break; 105 | case Log.ERROR: 106 | Log.e(tag, msg); 107 | break; 108 | } 109 | } 110 | // Write to file 111 | if (type >= level) { 112 | writeLog(type, msg); 113 | } 114 | } 115 | 116 | /** 117 | * Custom Log output style 118 | * 119 | * @param type 120 | * @param tag 121 | * @param msg 122 | * @param tr 123 | */ 124 | private static void trace(final int type, final String tag, 125 | final String msg, final Throwable tr) { 126 | // LogCat 127 | if (debug) { 128 | switch (type) { 129 | case Log.VERBOSE: 130 | Log.v(tag, msg); 131 | break; 132 | case Log.DEBUG: 133 | Log.d(tag, msg); 134 | break; 135 | case Log.INFO: 136 | Log.i(tag, msg); 137 | break; 138 | case Log.WARN: 139 | Log.w(tag, msg); 140 | break; 141 | case Log.ERROR: 142 | Log.e(tag, msg); 143 | break; 144 | } 145 | } 146 | // Write to file 147 | if (type >= level) { 148 | writeLog(type, msg + '\n' + Log.getStackTraceString(tr)); 149 | } 150 | } 151 | 152 | /** 153 | * Write log file to the SDCard 154 | * 155 | * @param type 156 | * @param msg 157 | */ 158 | private static void writeLog(int type, String msg) { 159 | if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { 160 | return; 161 | } 162 | 163 | try { 164 | SparseArray logMap = new SparseArray(); 165 | logMap.put(Log.VERBOSE, " VERBOSE "); 166 | logMap.put(Log.DEBUG, " DEBUG "); 167 | logMap.put(Log.INFO, " INFO "); 168 | logMap.put(Log.WARN, " WARN "); 169 | logMap.put(Log.ERROR, " ERROR "); 170 | 171 | final StackTraceElement tag = new Throwable().fillInStackTrace().getStackTrace()[2]; // TODO: ... 172 | 173 | msg = new StringBuilder() 174 | .append("\r\n") 175 | .append(getDateFormat(DateFormater.SS.getValue())) 176 | .append(logMap.get(type)).append(tag.getClassName()) 177 | .append(" - ").append(tag.getMethodName()).append("(): ") 178 | .append(msg).toString(); 179 | 180 | final String fileName = new StringBuffer() 181 | .append(LOG_PREFIX) 182 | .append(getDateFormat(DateFormater.DD.getValue())) 183 | .append(".log").toString(); 184 | 185 | recordLog(LOG_DIR, fileName, msg, true); 186 | } catch (Exception e) { 187 | Logger.e("Logger: ", e.getMessage()); 188 | } 189 | } 190 | 191 | /** 192 | * Write log 193 | * 194 | * @param logDir Log path to save 195 | * @param fileName 196 | * @param msg Log content 197 | * @param append Save as type, false override save, true before file add save 198 | */ 199 | private static void recordLog(String logDir, String fileName, String msg, boolean append) { 200 | try { 201 | createDir(logDir); 202 | 203 | final File saveFile = new File(new StringBuffer() 204 | .append(logDir) 205 | .append("/") 206 | .append(fileName).toString()); 207 | 208 | if (!append && saveFile.exists()) { 209 | saveFile.delete(); 210 | saveFile.createNewFile(); 211 | write(saveFile, msg, append); 212 | } else if (append && saveFile.exists()) { 213 | write(saveFile, msg, append); 214 | } else if (append && !saveFile.exists()) { 215 | saveFile.createNewFile(); 216 | write(saveFile, msg, append); 217 | } else if (!append && !saveFile.exists()) { 218 | saveFile.createNewFile(); 219 | write(saveFile, msg, append); 220 | } 221 | } catch (IOException e) { 222 | recordLog(logDir, fileName, msg, append); 223 | } 224 | } 225 | 226 | private static String getDateFormat(String pattern) { 227 | final DateFormat format = new SimpleDateFormat(pattern); 228 | return format.format(new Date()); 229 | } 230 | 231 | private static File createDir(String dir) { 232 | final File file = new File(dir); 233 | if (!file.exists()) { 234 | file.mkdirs(); 235 | } 236 | return file; 237 | } 238 | 239 | /** 240 | * Write msg to file 241 | * 242 | * @param file 243 | * @param msg 244 | * @param append 245 | */ 246 | private static void write(final File file, final String msg, final boolean append) { 247 | 248 | new SafeAsyncTask() { // TODO: ... 249 | 250 | @Override 251 | public Void call() throws Exception { 252 | final FileOutputStream fos; 253 | try { 254 | fos = new FileOutputStream(file, append); 255 | try { 256 | fos.write(msg.getBytes()); 257 | } catch (IOException e) { 258 | Logger.e(TAG, "write fail!!!", e); 259 | } finally { 260 | if (fos != null) { 261 | try { 262 | fos.close(); 263 | } catch (IOException e) { 264 | Logger.d(TAG, "Exception closing stream: ", e); 265 | } 266 | } 267 | } 268 | } catch (FileNotFoundException e) { 269 | Logger.e(TAG, "write fail!!!", e); 270 | } 271 | 272 | return null; 273 | } 274 | }.execute(); 275 | } 276 | 277 | 278 | // ----------------------------------- 279 | // Logger config methods 280 | // ----------------------------------- 281 | 282 | /** 283 | * 设置日志文件名前缀 284 | * 285 | * @param prefix (prefix-20121212.log) 286 | * @return 287 | */ 288 | public static String setLogPrefix(final String prefix) { 289 | return prefix.length() == 0 ? "logger-" : prefix + "-"; 290 | } 291 | 292 | /** 293 | * 设置日志文件存放路径 294 | * 295 | * @param subPath 子路径("/Downloads/subPath") 296 | * @return 297 | */ 298 | public static String setLogPath(final String subPath) { 299 | return subPath.length() == 0 ? DOWNLOADS_PATH + "/logs" : subPath; 300 | } 301 | 302 | } 303 | -------------------------------------------------------------------------------- /src/main/java/org/syxc/util/SafeAsyncTask.java: -------------------------------------------------------------------------------- 1 | package org.syxc.util; 2 | 3 | import android.os.Handler; 4 | import android.os.Looper; 5 | 6 | import java.io.InterruptedIOException; 7 | import java.util.ArrayList; 8 | import java.util.Arrays; 9 | import java.util.concurrent.Callable; 10 | import java.util.concurrent.CountDownLatch; 11 | import java.util.concurrent.Executor; 12 | import java.util.concurrent.Executors; 13 | import java.util.concurrent.FutureTask; 14 | 15 | /** 16 | * Originally from RoboGuice: https://github.com/roboguice/roboguice/blob/master/roboguice/src/main/java/roboguice/util/SafeAsyncTask.java 17 | *

18 | * A class similar but unrelated to android's {@link android.os.AsyncTask}. 19 | *

20 | * Unlike AsyncTask, this class properly propagates exceptions. 21 | *

22 | * If you're familiar with AsyncTask and are looking for {@link android.os.AsyncTask#doInBackground(Object[])}, 23 | * we've named it {@link #call()} here to conform with java 1.5's {@link java.util.concurrent.Callable} interface. 24 | *

25 | * Current limitations: does not yet handle progress, although it shouldn't be 26 | * hard to add. 27 | *

28 | * If using your own executor, you must call future() to get a runnable you can execute. 29 | * 30 | * @param 31 | */ 32 | public abstract class SafeAsyncTask implements Callable { 33 | 34 | private static final String TAG = "SafeAsyncTask"; 35 | 36 | public static final int DEFAULT_POOL_SIZE = 25; 37 | protected static final Executor DEFAULT_EXECUTOR = Executors.newFixedThreadPool(DEFAULT_POOL_SIZE); 38 | 39 | protected Handler handler; 40 | protected Executor executor; 41 | protected StackTraceElement[] launchLocation; 42 | protected FutureTask future; 43 | 44 | 45 | /** 46 | * Sets executor to Executors.newFixedThreadPool(DEFAULT_POOL_SIZE) and 47 | * Handler to new Handler() 48 | */ 49 | public SafeAsyncTask() { 50 | this.executor = DEFAULT_EXECUTOR; 51 | } 52 | 53 | /** 54 | * Sets executor to Executors.newFixedThreadPool(DEFAULT_POOL_SIZE) 55 | */ 56 | public SafeAsyncTask(Handler handler) { 57 | this.handler = handler; 58 | this.executor = DEFAULT_EXECUTOR; 59 | } 60 | 61 | /** 62 | * Sets Handler to new Handler() 63 | */ 64 | public SafeAsyncTask(Executor executor) { 65 | this.executor = executor; 66 | } 67 | 68 | public SafeAsyncTask(Handler handler, Executor executor) { 69 | this.handler = handler; 70 | this.executor = executor; 71 | } 72 | 73 | 74 | public FutureTask future() { 75 | future = new FutureTask(newTask()); 76 | return future; 77 | } 78 | 79 | public SafeAsyncTask executor(Executor executor) { 80 | this.executor = executor; 81 | return this; 82 | } 83 | 84 | public Executor executor() { 85 | return executor; 86 | } 87 | 88 | public SafeAsyncTask handler(Handler handler) { 89 | this.handler = handler; 90 | return this; 91 | } 92 | 93 | public Handler handler() { 94 | return handler; 95 | } 96 | 97 | public void execute() { 98 | execute(Thread.currentThread().getStackTrace()); 99 | } 100 | 101 | protected void execute(StackTraceElement[] launchLocation) { 102 | this.launchLocation = launchLocation; 103 | executor.execute(future()); 104 | } 105 | 106 | public boolean cancel(boolean mayInterruptIfRunning) { 107 | if (future == null) 108 | throw new UnsupportedOperationException("You cannot cancel this task before calling future()"); 109 | 110 | return future.cancel(mayInterruptIfRunning); 111 | } 112 | 113 | 114 | /** 115 | * @throws Exception, captured on passed to onException() if present. 116 | */ 117 | protected void onPreExecute() throws Exception { 118 | } 119 | 120 | /** 121 | * @param t the result of {@link #call()} 122 | * @throws Exception, captured on passed to onException() if present. 123 | */ 124 | @SuppressWarnings({"UnusedDeclaration"}) 125 | protected void onSuccess(ResultT t) throws Exception { 126 | } 127 | 128 | /** 129 | * Called when the thread has been interrupted, likely because 130 | * the task was canceled. 131 | *

132 | * By default, calls {@link #onException(Exception)}, but this method 133 | * may be overridden to handle interruptions differently than other 134 | * exceptions. 135 | * 136 | * @param e an InterruptedException or InterruptedIOException 137 | */ 138 | protected void onInterrupted(Exception e) { 139 | onException(e); 140 | } 141 | 142 | /** 143 | * Logs the exception as an Error by default, but this method may 144 | * be overridden by subclasses. 145 | * 146 | * @param e the exception thrown from {@link #onPreExecute()}, {@link #call()}, or {@link #onSuccess(Object)} 147 | * @throws RuntimeException, ignored 148 | */ 149 | protected void onException(Exception e) throws RuntimeException { 150 | onThrowable(e); 151 | } 152 | 153 | protected void onThrowable(Throwable t) throws RuntimeException { 154 | Logger.e(TAG, "Throwable caught during background processing", t); 155 | } 156 | 157 | /** 158 | * @throws RuntimeException, ignored 159 | */ 160 | protected void onFinally() throws RuntimeException { 161 | } 162 | 163 | 164 | protected Task newTask() { 165 | return new Task(this); 166 | } 167 | 168 | 169 | public static class Task implements Callable { 170 | protected SafeAsyncTask parent; 171 | protected Handler handler; 172 | 173 | public Task(SafeAsyncTask parent) { 174 | this.parent = parent; 175 | this.handler = parent.handler != null ? parent.handler : new Handler(Looper.getMainLooper()); 176 | } 177 | 178 | public Void call() throws Exception { 179 | try { 180 | doPreExecute(); 181 | doSuccess(doCall()); 182 | 183 | } catch (final Exception e) { 184 | try { 185 | doException(e); 186 | } catch (Exception f) { 187 | // logged but ignored 188 | Logger.e(TAG, "Exception: ", f); 189 | } 190 | 191 | } catch (final Throwable t) { 192 | try { 193 | doThrowable(t); 194 | } catch (Exception f) { 195 | // logged but ignored 196 | Logger.e(TAG, "Exception: ", f); 197 | } 198 | } finally { 199 | doFinally(); 200 | } 201 | 202 | return null; 203 | } 204 | 205 | protected void doPreExecute() throws Exception { 206 | postToUiThreadAndWait(new Callable() { 207 | public Object call() throws Exception { 208 | parent.onPreExecute(); 209 | return null; 210 | } 211 | }); 212 | } 213 | 214 | protected ResultT doCall() throws Exception { 215 | return parent.call(); 216 | } 217 | 218 | protected void doSuccess(final ResultT r) throws Exception { 219 | postToUiThreadAndWait(new Callable() { 220 | public Object call() throws Exception { 221 | parent.onSuccess(r); 222 | return null; 223 | } 224 | }); 225 | } 226 | 227 | protected void doException(final Exception e) throws Exception { 228 | if (parent.launchLocation != null) { 229 | final ArrayList stack = new ArrayList(Arrays.asList(e.getStackTrace())); 230 | stack.addAll(Arrays.asList(parent.launchLocation)); 231 | e.setStackTrace(stack.toArray(new StackTraceElement[stack.size()])); 232 | } 233 | postToUiThreadAndWait(new Callable() { 234 | public Object call() throws Exception { 235 | if (e instanceof InterruptedException || e instanceof InterruptedIOException) 236 | parent.onInterrupted(e); 237 | else 238 | parent.onException(e); 239 | return null; 240 | } 241 | }); 242 | } 243 | 244 | protected void doThrowable(final Throwable e) throws Exception { 245 | if (parent.launchLocation != null) { 246 | final ArrayList stack = new ArrayList(Arrays.asList(e.getStackTrace())); 247 | stack.addAll(Arrays.asList(parent.launchLocation)); 248 | e.setStackTrace(stack.toArray(new StackTraceElement[stack.size()])); 249 | } 250 | postToUiThreadAndWait(new Callable() { 251 | public Object call() throws Exception { 252 | parent.onThrowable(e); 253 | return null; 254 | } 255 | }); 256 | } 257 | 258 | protected void doFinally() throws Exception { 259 | postToUiThreadAndWait(new Callable() { 260 | public Object call() throws Exception { 261 | parent.onFinally(); 262 | return null; 263 | } 264 | }); 265 | } 266 | 267 | 268 | /** 269 | * Posts the specified runnable to the UI thread using a handler, 270 | * and waits for operation to finish. If there's an exception, 271 | * it captures it and rethrows it. 272 | * 273 | * @param c the callable to post 274 | * @throws Exception on error 275 | */ 276 | protected void postToUiThreadAndWait(final Callable c) throws Exception { 277 | final CountDownLatch latch = new CountDownLatch(1); 278 | final Exception[] exceptions = new Exception[1]; 279 | 280 | // Execute onSuccess in the UI thread, but wait 281 | // for it to complete. 282 | // If it throws an exception, capture that exception 283 | // and rethrow it later. 284 | handler.post(new Runnable() { 285 | public void run() { 286 | try { 287 | c.call(); 288 | } catch (Exception e) { 289 | exceptions[0] = e; 290 | } finally { 291 | latch.countDown(); 292 | } 293 | } 294 | }); 295 | 296 | // Wait for onSuccess to finish 297 | latch.await(); 298 | 299 | if (exceptions[0] != null) 300 | throw exceptions[0]; 301 | 302 | } 303 | 304 | } 305 | 306 | } --------------------------------------------------------------------------------