android代碼保護永不閃退

在我們開發的過程中,再厲害的程序員也無法保證寫的代碼沒有錯誤,而這裏面最嚴重的錯誤,對於android開發來說,毫無疑問就是app閃退了.特別是在開發第三方SDK的時候,假設因爲SDK裏面報的問題,導致對方App崩潰了,這對SDK而言的打擊是非常嚴重的,有的時候我們甚至希望SDK即使無法很好的工作,也不要引發對接方App的崩潰.

下面我就來介紹兩種,可以根據代碼判斷,只會代碼所在線程死掉,但不會引發崩潰的方法:

  1. hook Thread.UncaughtExceptionHandler 的方式
  2. 代理 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;
            }
        });
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章