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.
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 |
--------------------------------------------------------------------------------
/Sample/src/com/github/mmin18/safelooper/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 | package com.github.mmin18.safelooper;
26 |
27 | import java.lang.reflect.Field;
28 | import java.lang.reflect.InvocationTargetException;
29 | import java.lang.reflect.Method;
30 |
31 | import android.os.Binder;
32 | import android.os.Handler;
33 | import android.os.Looper;
34 | import android.os.Message;
35 | import android.os.MessageQueue;
36 |
37 | /**
38 | * SafeLooper catches unexpected exceptions in Looper to avoid showing force
39 | * close dialog.
40 | *
41 | * After call SafeLooper.install(), the main looper will be take over. Uncaught
42 | * exceptions will be send to uncaughtExceptionHandler and the looper will
43 | * continues.
57 | * Notice the action will take effect in the next event loop
58 | */
59 | public static void install() {
60 | handler.removeMessages(0, EXIT);
61 | handler.post(new SafeLooper());
62 | }
63 |
64 | /**
65 | * Exit SafeLooper after millis in the main thread
66 | *
67 | * Notice the action will take effect in the next event loop
68 | */
69 | public static void uninstallDelay(long millis) {
70 | handler.removeMessages(0, EXIT);
71 | handler.sendMessageDelayed(handler.obtainMessage(0, EXIT), millis);
72 | }
73 |
74 | /**
75 | * Exit SafeLooper in the main thread
76 | *
77 | * Notice the action will take effect in the next event loop
78 | */
79 | public static void uninstall() {
80 | uninstallDelay(0);
81 | }
82 |
83 | /**
84 | * Tell if the SafeLooper is running in the current thread
85 | */
86 | public static boolean isSafe() {
87 | return RUNNINGS.get() != null;
88 | }
89 |
90 | /**
91 | * The same as Thread.setDefaultUncaughtExceptionHandler
92 | */
93 | public static void setUncaughtExceptionHandler(
94 | Thread.UncaughtExceptionHandler h) {
95 | uncaughtExceptionHandler = h;
96 | }
97 |
98 | @Override
99 | public void run() {
100 | if (RUNNINGS.get() != null)
101 | return;
102 |
103 | Method next;
104 | Field target;
105 | try {
106 | Method m = MessageQueue.class.getDeclaredMethod("next");
107 | m.setAccessible(true);
108 | next = m;
109 | Field f = Message.class.getDeclaredField("target");
110 | f.setAccessible(true);
111 | target = f;
112 | } catch (Exception e) {
113 | return;
114 | }
115 |
116 | RUNNINGS.set(this);
117 | MessageQueue queue = Looper.myQueue();
118 | Binder.clearCallingIdentity();
119 | final long ident = Binder.clearCallingIdentity();
120 |
121 | while (true) {
122 | try {
123 | Message msg = (Message) next.invoke(queue);
124 | if (msg == null || msg.obj == EXIT)
125 | break;
126 |
127 | Handler h = (Handler) target.get(msg);
128 | h.dispatchMessage(msg);
129 | final long newIdent = Binder.clearCallingIdentity();
130 | if (newIdent != ident) {
131 | }
132 | msg.recycle();
133 | } catch (Exception e) {
134 | Thread.UncaughtExceptionHandler h = uncaughtExceptionHandler;
135 | Throwable ex = e;
136 | if (e instanceof InvocationTargetException) {
137 | ex = ((InvocationTargetException) e).getCause();
138 | if (ex == null) {
139 | ex = e;
140 | }
141 | }
142 | // e.printStackTrace(System.err);
143 | if (h != null) {
144 | h.uncaughtException(Thread.currentThread(), ex);
145 | }
146 | new Handler().post(this);
147 | break;
148 | }
149 | }
150 |
151 | RUNNINGS.set(null);
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
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
44 | *
45 | * @author yimin.tu
46 | *
47 | */
48 | public class SafeLooper implements Runnable {
49 | private static final Object EXIT = new Object();
50 | private static final ThreadLocal