├── .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