├── .gitignore ├── README.md ├── SafeLooper.java ├── SafeLooper.png ├── Sample ├── AndroidManifest.xml ├── proguard-project.txt ├── project.properties ├── res │ ├── layout │ │ └── activity_main.xml │ └── values │ │ └── strings.xml └── src │ └── com │ └── github │ └── mmin18 │ └── safelooper │ ├── MainActivity.java │ └── SafeLooper.java └── safelooper.apk /.gitignore: -------------------------------------------------------------------------------- 1 | # built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # files for the dex VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # generated files 12 | bin/ 13 | gen/ 14 | 15 | # Local configuration file (sdk path, etc) 16 | local.properties 17 | 18 | # Eclipse project files 19 | .classpath 20 | .project 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | SafeLooper 2 | ========== 3 | 4 | SafeLooper catches unexpected exceptions in Android applications to avoid showing force close dialog. 5 | 6 | A normal Android application (except games) is driven by event loop, known as android.os.Looper. When you throw a uncaught exception in the main thread, the main thread's looper will stop and Android will show a force close dialog. You won't have a chance to recover the main thread's looper since it has already stopped. 7 | 8 | If you can create a sub looper, pull message from event queue and process it with a try-catch block, you can catch the unexpected exceptions and avoid the application from crash. 9 | 10 | Here is how a SafeLooper works: 11 | 12 | ![SafeLooper](https://raw.github.com/mmin18/SafeLooper/master/SafeLooper.png) 13 | 14 | All you need to do is import SafeLooper.java into your project and call SafeLooper.install() when your application start. 15 | 16 | 17 | // install SafeLooper in main thread 18 | SafeLooper.install(); 19 | 20 | // receive the uncaught exception if you don't want to ignore it 21 | SafeLooper.setUncaughtExceptionHandler(...); 22 | 23 | You can also test the on your device. 24 | -------------------------------------------------------------------------------- /SafeLooper.java: -------------------------------------------------------------------------------- 1 | // The MIT License Header 2 | // 3 | // Copyright (c) 2013 Tu Yimin 4 | // 5 | // http://github.com/mmin18 6 | // 7 | // Permission is hereby granted, free of charge, to any person obtaining a copy 8 | // of this software and associated documentation files (the "Software"), to deal 9 | // in the Software without restriction, including without limitation the rights 10 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | // copies of the Software, and to permit persons to whom the Software is 12 | // furnished to do so, subject to the following conditions: 13 | // 14 | // The above copyright notice and this permission notice shall be included in 15 | // all copies or substantial portions of the Software. 16 | // 17 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | // THE SOFTWARE. 24 | 25 | import java.lang.reflect.Field; 26 | import java.lang.reflect.InvocationTargetException; 27 | import java.lang.reflect.Method; 28 | 29 | import android.os.Binder; 30 | import android.os.Handler; 31 | import android.os.Looper; 32 | import android.os.Message; 33 | import android.os.MessageQueue; 34 | 35 | /** 36 | * SafeLooper catches unexpected exceptions in Looper to avoid showing force 37 | * close dialog. 38 | *

39 | * After call SafeLooper.install(), the main looper will be take over. Uncaught 40 | * exceptions will be send to uncaughtExceptionHandler and the looper will 41 | * continues.
42 | * 43 | * @author yimin.tu 44 | * 45 | */ 46 | public class SafeLooper implements Runnable { 47 | private static final Object EXIT = new Object(); 48 | private static final ThreadLocal RUNNINGS = new ThreadLocal(); 49 | private static Thread.UncaughtExceptionHandler uncaughtExceptionHandler; 50 | private static Handler handler = new Handler(Looper.getMainLooper()); 51 | 52 | /** 53 | * Install SafeLooper in the main thread 54 | *

55 | * Notice the action will take effect in the next event loop 56 | */ 57 | public static void install() { 58 | handler.removeMessages(0, EXIT); 59 | handler.post(new SafeLooper()); 60 | } 61 | 62 | /** 63 | * Exit SafeLooper after millis in the main thread 64 | *

65 | * Notice the action will take effect in the next event loop 66 | */ 67 | public static void uninstallDelay(long millis) { 68 | handler.removeMessages(0, EXIT); 69 | handler.sendMessageDelayed(handler.obtainMessage(0, EXIT), millis); 70 | } 71 | 72 | /** 73 | * Exit SafeLooper in the main thread 74 | *

75 | * Notice the action will take effect in the next event loop 76 | */ 77 | public static void uninstall() { 78 | uninstallDelay(0); 79 | } 80 | 81 | /** 82 | * Tell if the SafeLooper is running in the current thread 83 | */ 84 | public static boolean isSafe() { 85 | return RUNNINGS.get() != null; 86 | } 87 | 88 | /** 89 | * The same as Thread.setDefaultUncaughtExceptionHandler 90 | */ 91 | public static void setUncaughtExceptionHandler( 92 | Thread.UncaughtExceptionHandler h) { 93 | uncaughtExceptionHandler = h; 94 | } 95 | 96 | @Override 97 | public void run() { 98 | if (RUNNINGS.get() != null) 99 | return; 100 | 101 | Method next; 102 | Field target; 103 | try { 104 | Method m = MessageQueue.class.getDeclaredMethod("next"); 105 | m.setAccessible(true); 106 | next = m; 107 | Field f = Message.class.getDeclaredField("target"); 108 | f.setAccessible(true); 109 | target = f; 110 | } catch (Exception e) { 111 | return; 112 | } 113 | 114 | RUNNINGS.set(this); 115 | MessageQueue queue = Looper.myQueue(); 116 | Binder.clearCallingIdentity(); 117 | final long ident = Binder.clearCallingIdentity(); 118 | 119 | while (true) { 120 | try { 121 | Message msg = (Message) next.invoke(queue); 122 | if (msg == null || msg.obj == EXIT) 123 | break; 124 | 125 | Handler h = (Handler) target.get(msg); 126 | h.dispatchMessage(msg); 127 | final long newIdent = Binder.clearCallingIdentity(); 128 | if (newIdent != ident) { 129 | } 130 | msg.recycle(); 131 | } catch (Exception e) { 132 | Thread.UncaughtExceptionHandler h = uncaughtExceptionHandler; 133 | Throwable ex = e; 134 | if (e instanceof InvocationTargetException) { 135 | ex = ((InvocationTargetException) e).getCause(); 136 | if (ex == null) { 137 | ex = e; 138 | } 139 | } 140 | // e.printStackTrace(System.err); 141 | if (h != null) { 142 | h.uncaughtException(Thread.currentThread(), ex); 143 | } 144 | new Handler().post(this); 145 | break; 146 | } 147 | } 148 | 149 | RUNNINGS.set(null); 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /SafeLooper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmin18/SafeLooper/44f1b77f2b9aa1c8348b5ee6724acaf960ebd394/SafeLooper.png -------------------------------------------------------------------------------- /Sample/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 13 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Sample/proguard-project.txt: -------------------------------------------------------------------------------- 1 | # To enable ProGuard in your project, edit project.properties 2 | # to define the proguard.config property as described in that file. 3 | # 4 | # Add project specific ProGuard rules here. 5 | # By default, the flags in this file are appended to flags specified 6 | # in ${sdk.dir}/tools/proguard/proguard-android.txt 7 | # You can edit the include path and order by changing the ProGuard 8 | # include property in project.properties. 9 | # 10 | # For more details, see 11 | # http://developer.android.com/guide/developing/tools/proguard.html 12 | 13 | # Add any project specific keep options here: 14 | 15 | # If your project uses WebView with JS, uncomment the following 16 | # and specify the fully qualified class name to the JavaScript interface 17 | # class: 18 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 19 | # public *; 20 | #} 21 | -------------------------------------------------------------------------------- /Sample/project.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system edit 7 | # "ant.properties", and override values to adapt the script to your 8 | # project structure. 9 | # 10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): 11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt 12 | 13 | # Project target. 14 | target=android-8 15 | -------------------------------------------------------------------------------- /Sample/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 15 | 16 |