在我們開發的過程中,再厲害的程序員也無法保證寫的代碼沒有錯誤,而這裏面最嚴重的錯誤,對於android開發來說,毫無疑問就是app閃退了.特別是在開發第三方SDK的時候,假設因爲SDK裏面報的問題,導致對方App崩潰了,這對SDK而言的打擊是非常嚴重的,有的時候我們甚至希望SDK即使無法很好的工作,也不要引發對接方App的崩潰.
下面我就來介紹兩種,可以根據代碼判斷,只會代碼所在線程死掉,但不會引發崩潰的方法:
- hook Thread.UncaughtExceptionHandler 的方式
- 代理 Thread.UncaughtExceptionHandler 的方式
hook Thread.UncaughtExceptionHandler 的方式
第一種方式就是,我們在程序啓動的時候,先使用Thread.getDefaultUncaughtExceptionHandler();
拿到系統的錯誤處理Handler對象,再次之上,我們自己創建一個 Thread.UncaughtExceptionHandler 的繼承類,包裹住系統的錯誤處理Handler對象之後,把我們自己創建的Thread.UncaughtExceptionHandler 傳遞回去.
系統的Thread.UncaughtExceptionHandler的實現類是com.android.internal.os.RuntimeInit$UncaughtHandler
hook代碼如下:
public void hook() {
Thread.UncaughtExceptionHandler ueh = Thread.getDefaultUncaughtExceptionHandler();
if (ueh == null) {
mSystemUeh = null;
mOtherUeh = null;
Thread.setDefaultUncaughtExceptionHandler(CrashHookImpl2.this);
return;
}
String androidUeh = "com.android.internal.os.RuntimeInit$UncaughtHandler";
String selfUeh = getClass().getName();
String currUeh = ueh.getClass().getName();
//系統的
if (androidUeh.equals(currUeh)) {
mSystemUeh = ueh;
mOtherUeh = null;
Thread.setDefaultUncaughtExceptionHandler(CrashHookImpl2.this);
} else if (selfUeh.equals(currUeh)) {
//自己人 不處理
} else {
// bugly的 -> currUeh.contains("com.tencent.bugly.crashreport.crash")
mSystemUeh = null;
mOtherUeh = ueh;
Thread.setDefaultUncaughtExceptionHandler(CrashHookImpl2.this);
}
return;
}
其中,CrashHookImpl2是我們繼承自Thread.UncaughtExceptionHandler 自己的實現類, 其中uncaughtException
實現如下:
@Override
public void uncaughtException(Thread t, Throwable e) {
//-----------自己的處理,攔截除0異常-------------------------------
StackTraceElement[] elements = e.getStackTrace();
for (int i = 0; i < elements.length; i++) {
if (e.getClass().getName().equals("java.lang.ArithmeticException")) {
return;
}
}
//-----------下面的系統的uncaughtException不走,既不會崩潰---------
if (mOtherUeh != null) {
mOtherUeh.uncaughtException(t, e);
}
if (mSystemUeh != null) {
mSystemUeh.uncaughtException(t, e);
}
}
這時候, 我們可以在程序中寫一個除以0 的異常,則是不會引發崩潰的,只會引發線程的停止:
CrashHookImpl.getInstance().hook();
CrashHookImpl.getInstance().addCutOffExceptionHandler(new CrashHook.CutOffExceptionHandler() {
@Override
public boolean onCutOffException(Thread t, Throwable e) {
StackTraceElement[] elements = e.getStackTrace();
for (int i = 0; i < elements.length; i++) {
if (e.getClass().getName().equals("java.lang.ArithmeticException")) {
return true;
}
}
return false;
}
});
new Thread() {
@Override
public void run() {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(100 / 0 + "hook");
String nullp = null;
int a = nullp.length();
}
}
}.start();
實現類全部代碼:
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import java.util.ArrayList;
import java.util.List;
public class CrashHookImpl2 implements Thread.UncaughtExceptionHandler, CrashHook {
private static volatile CrashHookImpl2 instance = null;
private Thread.UncaughtExceptionHandler mSystemUeh;
private Thread.UncaughtExceptionHandler mOtherUeh;
private List<CutOffExceptionHandler> exceptionHandlers;
private CrashHookImpl2() {
exceptionHandlers = new ArrayList<>();
}
@Override
public void hook() {
Thread.UncaughtExceptionHandler ueh = Thread.getDefaultUncaughtExceptionHandler();
if (ueh == null) {
mSystemUeh = null;
mOtherUeh = null;
Thread.setDefaultUncaughtExceptionHandler(CrashHookImpl2.this);
return;
}
String androidUeh = "com.android.internal.os.RuntimeInit$UncaughtHandler";
String selfUeh = getClass().getName();
String currUeh = ueh.getClass().getName();
//系統的
if (androidUeh.equals(currUeh)) {
mSystemUeh = ueh;
mOtherUeh = null;
Thread.setDefaultUncaughtExceptionHandler(CrashHookImpl2.this);
} else if (selfUeh.equals(currUeh)) {
//自己人 不處理
} else {
// bugly的 -> currUeh.contains("com.tencent.bugly.crashreport.crash")
mSystemUeh = null;
mOtherUeh = ueh;
Thread.setDefaultUncaughtExceptionHandler(CrashHookImpl2.this);
}
return;
}
public static CrashHookImpl2 getInstance() {
if (instance == null) {
synchronized (CrashHookImpl2.class) {
if (instance == null) {
instance = new CrashHookImpl2();
}
}
}
return instance;
}
@Override
public void unHook() {
if (mSystemUeh != null) {
Thread.setDefaultUncaughtExceptionHandler(mSystemUeh);
return;
}
if (mOtherUeh != null) {
Thread.setDefaultUncaughtExceptionHandler(mSystemUeh);
return;
}
Thread.setDefaultUncaughtExceptionHandler(null);
return;
}
@Override
public void addCutOffExceptionHandler(CrashHook.CutOffExceptionHandler cutOffExceptionHandler) {
if (cutOffExceptionHandler == null) {
return;
}
exceptionHandlers.add(cutOffExceptionHandler);
}
@Override
public void removeCutOffExceptionHandler(CrashHook.CutOffExceptionHandler cutOffExceptionHandler) {
if (cutOffExceptionHandler == null) {
return;
}
exceptionHandlers.remove(cutOffExceptionHandler);
}
public void tryHook(final long time) {
if (time < 0) {
return;
}
final HandlerThread thread = new HandlerThread("hook_ch");
thread.start();
final Handler handler = new Handler(thread.getLooper());
handler.post(new Runnable() {
private int size = 0;
@Override
public void run() {
if (size <= (time / 200)) {
size++;
hook();
handler.postDelayed(this, 200);
} else {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
thread.quitSafely();
} else {
thread.quit();
}
}
}
});
return;
}
@Override
public void uncaughtException(Thread t, Throwable e) {
for (int i = 0; i < exceptionHandlers.size(); i++) {
boolean consumed = exceptionHandlers.get(i).onCutOffException(t, e);
if (consumed) {
return;
}
}
if (mOtherUeh != null) {
mOtherUeh.uncaughtException(t, e);
}
if (mSystemUeh != null) {
mSystemUeh.uncaughtException(t, e);
}
}
}
代理 Thread.UncaughtExceptionHandler 的方式
實際上也是Hook默認的 Thread.UncaughtExceptionHandler , 但這種方式的好處是,我們自己無需創建實現Thread.UncaughtExceptionHandler接口的實現類,而只需要使用Proxy.newProxyInstance
代理系統默認的Thread.UncaughtExceptionHandler
就好了,這種方式比起上面一種來說,除了效果一致以外,好處可能就是可以避免一些代碼的檢查.
代理方式的hook代碼:
public void hook() {
if (hooked) {
return;
}
Thread.UncaughtExceptionHandler ueh = Thread.getDefaultUncaughtExceptionHandler();
if (ueh == null) {
return;
}
//代理邏輯
class Px implements InvocationHandler {
private Thread.UncaughtExceptionHandler handler;
private Method uncaughtExceptionMethod;
private Px(Thread.UncaughtExceptionHandler handler) {
this.handler = handler;
try {
uncaughtExceptionMethod = Thread.UncaughtExceptionHandler.class.getMethod("uncaughtException", Thread.class, Throwable.class);
} catch (Throwable e) {
}
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) {
//先走自己的
try {
if (uncaughtExceptionMethod != null && uncaughtExceptionMethod.equals(method)) {
if (args != null && args.length == 2) {
Thread thread = (Thread) args[0];
Throwable throwable = (Throwable) args[1];
for (int i = 0; i < exceptionHandlers.size(); i++) {
boolean cutoff = exceptionHandlers.get(i).onCutOffException(thread, throwable);
if (cutoff) {
//截斷
return null;
}
}
}
}
} catch (Throwable ignored) {
}
try {
//原來的
method.invoke(handler, args);
} catch (Throwable ignored) {
}
return null;
}
}
Thread.UncaughtExceptionHandler hookedUeh = (Thread.UncaughtExceptionHandler) Proxy.newProxyInstance(
ueh.getClass().getClassLoader(),
ueh.getClass().getInterfaces(),
new Px(ueh));
Thread.setDefaultUncaughtExceptionHandler(hookedUeh);
hooked = true;
}
本種方式的全部實現代碼:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.List;
public class CrashHookImpl implements CrashHook {
private List<CutOffExceptionHandler> exceptionHandlers;
private static volatile CrashHookImpl instance = null;
private boolean hooked = false;
private CrashHookImpl() {
exceptionHandlers = new ArrayList<>();
}
public static CrashHookImpl getInstance() {
if (instance == null) {
synchronized (CrashHookImpl.class) {
if (instance == null) {
instance = new CrashHookImpl();
}
}
}
return instance;
}
@Override
public void hook() {
if (hooked) {
return;
}
Thread.UncaughtExceptionHandler ueh = Thread.getDefaultUncaughtExceptionHandler();
if (ueh == null) {
return;
}
class Px implements InvocationHandler {
private Thread.UncaughtExceptionHandler handler;
private Method uncaughtExceptionMethod;
private Px(Thread.UncaughtExceptionHandler handler) {
this.handler = handler;
try {
uncaughtExceptionMethod = Thread.UncaughtExceptionHandler.class.getMethod("uncaughtException", Thread.class, Throwable.class);
} catch (Throwable e) {
}
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) {
try {
if (uncaughtExceptionMethod != null && uncaughtExceptionMethod.equals(method)) {
if (args != null && args.length == 2) {
Thread thread = (Thread) args[0];
Throwable throwable = (Throwable) args[1];
for (int i = 0; i < exceptionHandlers.size(); i++) {
boolean cutoff = exceptionHandlers.get(i).onCutOffException(thread, throwable);
if (cutoff) {
//截斷
return null;
}
}
}
}
} catch (Throwable ignored) {
}
try {
//原來的
method.invoke(handler, args);
} catch (Throwable ignored) {
}
return null;
}
}
Thread.UncaughtExceptionHandler hookedUeh = (Thread.UncaughtExceptionHandler) Proxy.newProxyInstance(
ueh.getClass().getClassLoader(),
ueh.getClass().getInterfaces(),
new Px(ueh));
Thread.setDefaultUncaughtExceptionHandler(hookedUeh);
hooked = true;
}
@Override
public void unHook() {
if (!hooked) {
return;
}
Thread.UncaughtExceptionHandler ueh = Thread.getDefaultUncaughtExceptionHandler();
if (ueh == null) {
return;
}
Thread.setDefaultUncaughtExceptionHandler(ueh);
hooked = false;
}
@Override
public void addCutOffExceptionHandler(CutOffExceptionHandler cutOffExceptionHandler) {
if (cutOffExceptionHandler == null) {
return;
}
exceptionHandlers.add(cutOffExceptionHandler);
}
@Override
public void removeCutOffExceptionHandler(CutOffExceptionHandler cutOffExceptionHandler) {
if (cutOffExceptionHandler == null) {
return;
}
exceptionHandlers.remove(cutOffExceptionHandler);
}
}
結語
對於崩潰,這兩種方法只是避險的手段,而在我們的開發中,通過嚴謹的代碼審查和測試,在上線之前解決問題,纔是最好的選擇.以上方法除了保護崩潰意外,還可以統計錯誤上報:
使用方法:
CrashHookImpl.getInstance().hook();
//保護所有的除零崩潰
CrashHookImpl.getInstance().addCutOffExceptionHandler(new CrashHook.CutOffExceptionHandler() {
@Override
public boolean onCutOffException(Thread t, Throwable e) {
if (e.getClass().getName().equals("java.lang.ArithmeticException")) {
//返回true,截斷崩潰
return true;
}
return false;
}
});
//統計上報
CrashHookImpl.getInstance().addCutOffExceptionHandler(new CrashHook.CutOffExceptionHandler() {
@Override
public boolean onCutOffException(Thread t, Throwable e) {
StackTraceElement[] elements = e.getStackTrace();
for (int i = 0; i < elements.length; i++) {
String msg= elements[i].getClassName()+elements[i].getMethodName()+elements[i].getLineNumber();
//上報錯誤
}
//返回false 不截斷
return false;
}
});