崩溃监测 Java崩溃 CheckedException 编译时异常,在编译阶段就可以检测到异常
UnCheckedException RuntimeException及其子类的异常,在程序运行阶段的异常,导致程序出现崩溃。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 --------- beginning of crash AndroidRuntime: FATAL EXCEPTION: main Process: com.android.developer.crashsample, PID: 3686 java.lang.NullPointerException: crash sample at com.android.developer.crashsample.MainActivity$1 .onClick(MainActivity.java:27) at android.view.View.performClick(View.java:6134) at android.view.View$PerformClick .run(View.java:23965) at android.os.Handler.handleCallback(Handler.java:751) at android.os.Handler.dispatchMessage(Handler.java:95) at android.os.Looper.loop(Looper.java:156) at android.app.ActivityThread.main(ActivityThread.java:6440) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.Zygote$MethodAndArgsCaller .run(Zygote.java:240) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:746) --------- beginning of system
堆栈轨迹包含两条重要的信息:
每行显示的调用点称为堆栈帧 (a stack frame)
java.lang.NullPointerException kotlin.UninitializedPropertyAccessException
OutOfMemoryError:内存错误 UnknownHostException:网络异常
异常采集 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 public class CrashHandler implements UncaughtExceptionHandler { private static final String TAG = "CrashHandler" ; private static final boolean DEBUG = true ; private static final String PATH = Environment .getExternalStorageDirectory() + "/CrashDemo/log/" ; private static final String FILE_NAME = "crash" ; private static final String FILE_NAME_SUFFIX = ".trace" ; private static final String ABOLUTE_PATH = PATH + FILE_NAME + FILE_NAME_SUFFIX; private String deviceToken; private static CrashHandler sInstance = new CrashHandler(); private UncaughtExceptionHandler mDefaultCrashHandler; private Context mContext; private CrashHandler () { } public static CrashHandler getInstance () { return sInstance; } public void init (Context context) { mDefaultCrashHandler = Thread.getDefaultUncaughtExceptionHandler(); Thread.setDefaultUncaughtExceptionHandler(this ); mContext = context.getApplicationContext(); } @Override public void uncaughtException (Thread thread, Throwable ex) { try { dumpExceptionToSDCard(ex); } catch (IOException e) { e.printStackTrace(); } ex.printStackTrace(); if (mDefaultCrashHandler != null ) { mDefaultCrashHandler.uncaughtException(thread, ex); } else { Process.killProcess(Process.myPid()); } } private File dumpExceptionToSDCard (Throwable ex) throws IOException { if (!Environment.getExternalStorageState().equals( Environment.MEDIA_MOUNTED)) { if (DEBUG) { Log.w(TAG, "sdcard unmounted,skip dump exception" ); return null ; } } File dir = new File(PATH); if (!dir.exists()) { dir.mkdirs(); } long current = System.currentTimeMillis(); String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss" ) .format(new Date(current)); File file = new File(PATH + FILE_NAME + FILE_NAME_SUFFIX); if (!file.exists()) { file.createNewFile(); } else { try { PrintWriter pw = new PrintWriter(new BufferedWriter( new FileWriter(file, true ))); pw.println(time); dumpPhoneInfo(pw); pw.println(); ex.printStackTrace(pw); pw.println("---------------------------------分割线----------------------------------" ); pw.println(); pw.close(); } catch (Exception e) { Log.e(TAG, "dump crash info failed" ); } } return file; } private void dumpPhoneInfo (PrintWriter pw) throws NameNotFoundException { PackageManager pm = mContext.getPackageManager(); PackageInfo pi = pm.getPackageInfo(mContext.getPackageName(), PackageManager.GET_ACTIVITIES); pw.print("App Version: " ); pw.print(pi.versionName); pw.print('_' ); pw.println(pi.versionCode); pw.print("OS Version: " ); pw.print(Build.VERSION.RELEASE); pw.print("_" ); pw.println(Build.VERSION.SDK_INT); pw.print("Vendor: " ); pw.println(Build.MANUFACTURER); pw.print("Model: " ); pw.println(Build.MODEL); pw.print("CPU ABI: " ); pw.println(Build.CPU_ABI); } private void uploadExceptionToServer (File log) { } }
1 2 3 4 5 6 7 8 9 public class MyApplication extends Application { @Override public void onCreate () { super .onCreate(); CrashHandler crashHandler = CrashHandler.getInstance(); crashHandler.init(this ); } }
native崩溃 Signal 崩溃时会输出到logcat
ANR 概述 Android 应用的界面线程处于阻塞状态的时间过长,会触发“应用无响应”(ANR Application Not Responding) 错误。
触发情况:
Activity位于前台,应用在 5 秒钟内未响应输入事件(Input Event)
Service,前台进程20s,后台进程200s
Broadcast,前台10s,后台60s
诊断 ANR 诊断 ANR 时需要考虑以下几种常见模式:
应用在主线程上非常缓慢地执行涉及 I/O 的操作。
应用在主线程上进行长时间的计算。
主线程在对另一个进程进行同步 binder 调用,而后者需要很长时间才能返回。
主线程处于阻塞状态,为发生在另一个线程上的长操作等待同步的块。
主线程在进程中或通过 binder 调用与另一个线程之间发生死锁。主线程不只是在等待长操作执行完毕,而且处于死锁状态。
TraceView TraceView 是 Android SDK 中内置的一个工具,它可以加载 trace 文件,用图形的形式展示代码的执行时间、次数及调用栈 ,便于我们分析
生成 trace 文件有三种方法:
使用代码生成:
1 2 Debug.startMethodTracing("intrace" ); Debug.stopMethodTracing();
Android Studio 内置的 Android Monitor 可以很方便的生成 trace 文件到电脑
DDMS 即 Dalvik Debug Monitor Server 生成
官网参考
监测方案 相关原理
主要原理是利用Loop.loop()
中的log打印
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public static void loop () { ... for (;;) { Message msg = queue.next(); ... Printer logging = me.mLogging; if (logging != null ) { logging.println(">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what); } msg.target.dispatchMessage(msg); if (logging != null ) { logging.println("<<<<< Finished to " + msg.target + " " + msg.callback); } ... } }
log 打印逻辑正是在 Message 消息分发前后,大部分的性能卡顿问题都是在这里发生的,监控这两个逻辑之间的时间差就可以得到当前主线程的卡顿状态,如果超时则获取 trace 信息并上报。
主要是通过添加自己的Printer来实现的:
1 Looper.getMainLooper().setMessageLogging(mainLooperPrinter);
优点 灵活配置,可监控常见 APP 应用性能,且可以准确定位 ANR 和耗时调用栈。
缺点
1、 代码中已标注 This must be in a local variable, in case a UI event sets the logger
这个 looger 对象是可以被更改的,已经有开发者遇到在使用 WebView 时 logger 被 set 为 Null 导致 BlockCanary 失效,只能让 BlockCanary 在 WebView 初始化之后调用 start。 2、 如果 dispatchMessage 执行的非常久是无法触发 BlockCanary 的逻辑。 3、 queue.next()
出还有个标注might block
,如果input事件导致ANR时,框架无法适用。
4、 无法监控 CPU 资源紧张造成系统卡顿,无法响应的 ANR
ANR-WatchDog ANR-WatchDog 是参考 Android WatchDog 机制(com.android.server.WatchDog.java)起个单独线程向主线程发送一个变量 +1 操作,自我休眠自定义 ANR 的阈值,休眠过后判断变量是否 +1 完成,如果未完成则告警。
优点 1、 兼容性好,各个机型版本通用 2、 无需修改 APP 逻辑代码,非侵入式 3、 逻辑简单,性能影响不大
缺点 无法保证能捕捉所有 ANR,对阈值的设置直接影响捕获概率
如果对线程的堵塞大于 10s 则设置监控阈值 5s 能捕获所有 ANR,堵塞时间在 5s~10s,则可能出现无法捕获场景。
SafeLooper 内存泄漏 常见的内存泄漏 单例造成 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 private static ScrollHelper mInstance; private ScrollHelper () { } public static ScrollHelper getInstance () { if (mInstance == null ) { synchronized (ScrollHelper.class ) { if (mInstance == null ) { mInstance = new ScrollHelper(); } } } return mInstance; } private View mScrolledView = null ; public void setScrolledView (View scrolledView) { mScrolledView = scrolledView; }
解决方案:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 private static ScrollHelper mInstance; private ScrollHelper () { } public static ScrollHelper getInstance () { if (mInstance == null ) { synchronized (ScrollHelper.class ) { if (mInstance == null ) { mInstance = new ScrollHelper(); } } } return mInstance; } private WeakReference<View> mScrolledViewWeakRef = null ; public void setScrolledView (View scrolledView) { mScrolledViewWeakRef = new WeakReference<View>(scrolledView); }
匿名内部类 在Java中,非静态内部类 和 匿名类 都会潜在的引用它们所属的外部类,但是,静态内部类却不会。如果这个非静态内部类实例做了一些耗时的操作,就会造成外围对象不会被回收,从而导致内存泄漏。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public class LeakAct extends Activity { @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.aty_leak); test(); } public void test () { new Thread(new Runnable() { @Override public void run () { while (true ) { try { Thread.sleep(1000 ); } catch (InterruptedException e) { e.printStackTrace(); } } } }).start(); } }
解决方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public class LeakAct extends Activity { @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.aty_leak); test(); } public static void test () { new Thread(new Runnable() { @Override public void run () { while (true ) { try { Thread.sleep(1000 ); } catch (InterruptedException e) { e.printStackTrace(); } } } }).start(); } }
Activity Context 的不正确使用 1 2 3 4 5 6 7 8 9 10 11 12 private static Drawable sBackground;@Override protected void onCreate (Bundle state) { super .onCreate(state); TextView label = new TextView(this ); label.setText("Leaks are bad" ); if (sBackground == null ) { sBackground = getDrawable(R.drawable.large_bitmap); } label.setBackgroundDrawable(sBackground); setContentView(label); }
解决方法:
使用ApplicationContext代替ActivityContext
对Context的引用不要超过它本身的生命周期,慎重的对Context使用“static”关键字
Context里如果有线程,一定要在onDestroy()里及时停掉
1 2 3 4 5 6 7 8 9 10 11 12 private static Drawable sBackground; @Override protected void onCreate (Bundle state) { super .onCreate(state); TextView label = new TextView(this ); label.setText("Leaks are bad" ); if (sBackground == null ) { sBackground = getApplicationContext().getDrawable(R.drawable.large_bitmap); } label.setBackgroundDrawable(sBackground); setContentView(label); }
Handler引起的内存泄漏 当Handler中有延迟的的任务或是等待执行的任务队列过长,由于消息持有对Handler的引用,而Handler又持有对其外部类的潜在引用,这条引用关系会一直保持到消息得到处理,而导致了Activity无法被垃圾回收器回收,而导致了内存泄露。
解决方法:
把Handler类放在单独的类文件中,或者使用静态内部类便可以避免泄露
想在Handler内部去调用所在的Activity,那么可以在handler内部使用弱引用的方式去指向所在Activity.使用Static + WeakReference的方式来达到断开Handler与Activity之间存在引用关系
1 2 3 4 5 6 7 8 9 @Override protected void doOnDestroy () { super .doOnDestroy(); if (mHandler != null ) { mHandler.removeCallbacksAndMessages(null ); } mHandler = null ; mRenderCallback = null ; }
注册监听器的泄漏 系统服务可以通过Context.getSystemService 获取,它们负责执行某些后台任务,或者为硬件访问提供接口。如果Context 对象想要在服务内部的事件发生时被通知,那就需要把自己注册到服务的监听器中。然而,这会让服务持有Activity 的引用,如果在Activity onDestory时没有释放掉引用就会内存泄漏。
1 mSensorManager = (SensorManager) this .getSystemService(Context.SENSOR_SERVICE);
1 InputMethodManager imm = (InputMethodManager) context.getApplicationContext().getSystemService(Context.INPUT_METHOD_SERVICE);
解决方法:
使用ApplicationContext代替ActivityContext
在Activity执行onDestory时,调用反注册;
1 mSensorManager = (SensorManager) getApplicationContext().getSystemService(Context.SENSOR_SERVICE);
1 2 3 4 5 6 7 8 9 10 11 12 protected void onDetachedFromWindow () { if (this .mActionShell != null ) { this .mActionShell.setOnClickListener((OnAreaClickListener)null ); } if (this .mButtonShell != null ) { this .mButtonShell.setOnClickListener((OnAreaClickListener)null ); } if (this .mCountShell != this .mCountShell) { this .mCountShell.setOnClickListener((OnAreaClickListener)null ); } super .onDetachedFromWindow(); }
Cursor,Stream没有close,View没有recyle 资源性对象比如(Cursor,File文件等)往往都用了一些缓冲,我们在不使用的时候,应该及时关闭它们,以便它们的缓冲及时回收内存。它们的缓冲不仅存在于 java虚拟机内,还存在于java虚拟机外。如果我们仅仅是把它的引用设置为null,而不关闭它们,往往会造成内存泄漏。因为有些资源性对象,比如SQLiteCursor(在析构函数finalize(),如果我们没有关闭它,它自己会调close()关闭),如果我们没有关闭它,系统在回收它时也会关闭它,但是这样的效率太低了。因此对于资源性对象在不使用的时候,应该调用它的close()函数,将其关闭掉,然后才置为null. 在我们的程序退出时一定要确保我们的资源性对象已经关闭。
解决方法:调用onRecycled()
1 2 3 4 5 @Override public void onRecycled () { reset(); mSinglePicArea.onRecycled(); }
在View中调用reset()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public void reset () { if (mHasRecyled) { return ; } ... SubAreaShell.recycle(mActionBtnShell); mActionBtnShell = null ; ... mIsDoingAvatartRedPocketAnim = false ; if (mAvatarArea != null ) { mAvatarArea.reset(); } if (mNickNameArea != null ) { mNickNameArea.reset(); } }
WebView造成的泄露 当我们不要使用WebView对象时,应该调用它的destory()函数来销毁它,并释放其占用的内存,否则其占用的内存长期也不能被回收,从而造成内存泄露。
为webView开启另外一个进程,通过AIDL与主线程进行通信,WebView所在的进程可以根据业务的需要选择合适的时机进行销毁,从而达到内存的完整释放。
内存泄漏检测 LeakCanary Activity finish 后是否被回收, LeakCanary 是通过记录所有的 Activity,在 Application 中注册 LeakCanary 时,通过 registerActivityLifecycleCallbacks 监听 Activity 的生命周期,然后在 Activity 执行 onDestroy 时看 Activity 是否被回收,如果没有没回收,触发 gc, 再看有没有被回收,如果还没有被回收,那么就是有内存泄漏了,就收集内存泄漏相关日志信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public void watch (Object watchedReference, String referenceName) { ... if (!this .debuggerControl.isDebuggerAttached()) { final long watchStartNanoTime = System.nanoTime(); String key = UUID.randomUUID().toString(); this .retainedKeys.add(key); final KeyedWeakReference reference = new KeyedWeakReference(watchedReference, key, referenceName, this .queue); this .watchExecutor.execute(new Runnable() { public void run () { RefWatcher.this .ensureGone(reference, watchStartNanoTime); } }); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 void ensureGone (KeyedWeakReference reference, long watchStartNanoTime) { long gcStartNanoTime = System.nanoTime(); long watchDurationMs = TimeUnit.NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime); this .removeWeaklyReachableReferences(); if (!this .gone(reference) && !this .debuggerControl.isDebuggerAttached()) { this .gcTrigger.runGc(); this .removeWeaklyReachableReferences(); if (!this .gone(reference)) { long startDumpHeap = System.nanoTime(); long gcDurationMs = TimeUnit.NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime); File heapDumpFile = this .heapDumper.dumpHeap(); if (heapDumpFile == HeapDumper.NO_DUMP) { return ; } long heapDumpDurationMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap); this .heapdumpListener.analyze(new HeapDump(heapDumpFile, reference.key, reference.name, this .excludedRefs, watchDurationMs, gcDurationMs, heapDumpDurationMs)); } } }
参考:
Android ANR 监测方案解析
Android 内存泄漏分析心得