Diycode开源项目 BaseApplication分析+LeakCanary第三方+CrashHandler自定义异常处理
1.BaseApplication整个应用的开始
1.1.看一下代码
/*
* Copyright 2017 GcsSloop
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Last modified 2017-03-11 22:24:54
*
* GitHub: https://github.com/GcsSloop
* Website: http://www.gcssloop.com
* Weibo: http://weibo.com/GcsSloop
*/ package com.gcssloop.diycode.base.app; import android.app.Application; import com.gcssloop.diycode.utils.Config;
import com.gcssloop.diycode.utils.CrashHandler;
import com.gcssloop.diycode_sdk.api.Diycode;
import com.squareup.leakcanary.LeakCanary; public class BaseApplication extends Application { public static final String client_id = "7024a413";
public static final String client_secret = "8404fa33ae48d3014cfa89deaa674e4cbe6ec894a57dbef4e40d083dbbaa5cf4"; @Override
public void onCreate() {
super.onCreate();
if (LeakCanary.isInAnalyzerProcess(this)) {
return;
}
LeakCanary.install(this); CrashHandler.getInstance().init(this); Diycode.init(this, client_id, client_secret); Config.init(this);
}
}
1.2.代码预览

首先是两个静态变量,是客户的的id和密钥。
然后是一个onCreate函数,进行一些必要的初始化。
1.3.然后调用Application的onCreate函数

这里用了一个第三方库LeakCanary。
首先install这个application
这里的CrashHandler的作用是崩溃处理办法。
然后就用将上面的客户的id和客户的密码来初始化了。
用户设置Config初始化也是这里进行。
2.LeakCanary内存泄露检测
2.1.首先了解一下什么是LeakCanary。
LeakCanary是检测APP内存泄露的工具,内存泄漏是Android开发中常用的问题,导致程序的稳定性下降。
2.2.在build.gradle中加入:


2.3.在Application中初始化

配置非常简单,会增加一个附属应用,去掉Application的引用,就可以移除这个附属应用了。
建议在开发模式不要去掉这个引用,在发布版本一定就要移除这个引用了。
这个开源项目的效果是这样的(这是开发模式)

2.4.注意在清单中定义这个Application

2.5.什么情况会发生内存泄露?(参考文章:使用LeakCanary检测内存泄露)
假设我们有一个MainActivity,它的布局很简单,里面只有一个TextView。

现在我们写一个单例xxxHelper之类的业务类==>用来给主活动中的TextView固定设一个值。但是这个值要从res
中读取,所以我们得用到Context。

现在我们回到MainActivity中来使用这个单例:

然后附属应用直接发出内存泄漏的提示。

为什么会发生内存泄漏呢?
==>LeakCanary已经把问题很明显地带到我们面前。这是一个典型的单例导致的Context泄漏问题。我们知道
Android的Context分为Activity Context和Application Context。
关于他们的区别,请参考一下这篇文章。如
果没时间看也没关系,其实一个返回的是当前Activity的实例,另一个是项目的Application的实例。Context的
应用场景参考一下下方的图片。

从程序的角度上来理解:Context是个抽象类,而Activity,Service,Application等都是该类的一个实现。
在上方那段简单的代码中,我们的xxxHelper的静态实例ourInstance由于有一个对mTextView的引用,而
mTextView由于要setText(),所以持有了一个对Context的引用,而我们在MainActivity里获取xxxHelper
实例时因为传入了MainActivity和Context,这使得一旦这个Activity不在了之后,xxxHelper依然会hold住
它的Context不放,而这个时候因为Activity已经不在了,所以内存泄漏自然就产生了。
尝试解决==>

这种写法治标不治本。尽管我们的Context已经是Application层级的Context了,但是这种写法依然会导致
mTextView在退出后依旧hold住整个Application的Context,最终还是导致内存泄漏。
正确解决方案==>

采用Application级别的Context+增加一个移除TextView的引用。在onDestroy调用移除函数即可。
3.CrashHandler异常处理
3.1.这个类是一个异常处理类。
有点类似腾讯的Bugly==>也是一个异常处理的一个强大第三方库。它可以知道每次APP崩溃的原因,以及提示
解决方案。之前的“大学喵”项目我就是用的Bugly,每次异常,它都会给我发送一封邮件,告诉我哪里崩溃,
哪里异常,每天早上也会统计今日崩溃次数等。非常简单实用的第三方库。
3.2.这里提供一下开源项目Diycode的异常处理类的源码。
/*
* Copyright 2017 GcsSloop
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Last modified 2017-03-08 01:01:18
*
* GitHub: https://github.com/GcsSloop
* Website: http://www.gcssloop.com
* Weibo: http://weibo.com/GcsSloop
*/ package com.gcssloop.diycode.utils; import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Environment;
import android.os.Process;
import android.util.Log; import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.Date; public class CrashHandler implements Thread.UncaughtExceptionHandler {
private static final String TAG = "CrashHandler";
private static final boolean DEBUG = true; private static final String PATH = Environment.getExternalStorageDirectory().getPath() +
"/ryg_test/log/";
private static final String FILE_NAME = "crash"; //log文件的后缀名
private static final String FILE_NAME_SUFFIX = ".trace"; private static CrashHandler sInstance = new CrashHandler(); //系统默认的异常处理(默认情况下,系统会终止当前的异常程序)
private Thread.UncaughtExceptionHandler mDefaultCrashHandler; private Context mContext; //构造方法私有,防止外部构造多个实例,即采用单例模式
private CrashHandler() {
} public static CrashHandler getInstance() {
return sInstance;
} //这里主要完成初始化工作
public void init(Context context) {
//获取系统默认的异常处理器
mDefaultCrashHandler = Thread.getDefaultUncaughtExceptionHandler();
//将当前实例设为系统默认的异常处理器
Thread.setDefaultUncaughtExceptionHandler(this);
//获取Context,方便内部使用
mContext = context.getApplicationContext();
} /**
* 这个是最关键的函数,当程序中有未被捕获的异常,系统将会自动调用#uncaughtException方法
* thread为出现未捕获异常的线程,ex为未捕获的异常,有了这个ex,我们就可以得到异常信息。
*/
@Override
public void uncaughtException(Thread thread, Throwable ex) {
try {
//导出异常信息到SD卡中
dumpExceptionToSDCard(ex);
//这里可以通过网络上传异常信息到服务器,便于开发人员分析日志从而解决bug
uploadExceptionToServer();
} catch (IOException e) {
e.printStackTrace();
} //打印出当前调用栈信息
ex.printStackTrace(); //如果系统提供了默认的异常处理器,则交给系统去结束我们的程序,否则就由我们自己结束自己
if (mDefaultCrashHandler != null) {
mDefaultCrashHandler.uncaughtException(thread, ex);
} else {
Process.killProcess(Process.myPid());
} } private void dumpExceptionToSDCard(Throwable ex) throws IOException {
//如果SD卡不存在或无法使用,则无法把异常信息写入SD卡
if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
if (DEBUG) {
Log.w(TAG, "sdcard unmounted,skip dump exception");
return;
}
} 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));
//以当前时间创建log文件
File file = new File(PATH + FILE_NAME + time + FILE_NAME_SUFFIX); try {
PrintWriter pw = new PrintWriter(new BufferedWriter(new FileWriter(file)));
//导出发生异常的时间
pw.println(time); //导出手机信息
dumpPhoneInfo(pw); pw.println();
//导出异常的调用栈信息
ex.printStackTrace(pw); pw.close();
} catch (Exception e) {
Log.e(TAG, "dump crash info failed");
}
} private void dumpPhoneInfo(PrintWriter pw) throws PackageManager.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); //android版本号
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); //cpu架构
pw.print("CPU ABI: ");
pw.println(Build.CPU_ABI);
} private void uploadExceptionToServer() {
//TODO Upload Exception Message To Your Web Server
} }
3.3.首先看一下成员变量。

首先是一个TAG,方便在logcat中进行输出。
然后是一个本项目是debug模式还是release模式,然后做出相应的改变。
然后是一个确定将异常信息输出到哪个地址,这里放在一个log中的。
然后输出的文件名为“crash”
然后确定log文件的后缀名为.trace
然后是系统默认的异常处理,默认情况下,系统会终止当前的异常程序。
当然,Context也是不能少的,这里用来获取真实的上下文的。
注意这里已经new了一个CrashHandler()了。
3.4.构造函数+单例模式

这里的单例模式,直接返回在成员变量中new的一个CrashHandler。
3.5.初始化工作==>在Application中调用

可以获取系统默认的异常处理器,然后可以将当前实例设为系统默认的异常处理器。
这里获得了一个全局的上下文。
3.6.关键Override的函数

异常信息调用函数dumpExceptionToSDCard来输入SD卡中。
还可以自己写一个函数uploadExceptionToServer上次到服务器中,方便调试。
然后打印出当前调用栈信息。
如果系统提供了默认的异常处理器,则交给系统去结束我们的程序,否则就由我们自己结束自己。
如何自己结束自己呢?
Process.killProcess(Process.myPid())==>有时候,关闭APP,也会采用这种极端的方法。
3.7.如何输入SD卡中?

首先判断SD卡是否存在而且是否可用,还要判断是否是DEBUG模式。
然后判断路径是否存在。
然后获取系统当前时间,文件名有3部分组成:路径+名称+后缀
然后调用系统的io类PrintWriter来导出时间,手机信息,调用栈新消息。
3.8.如何导出手机信息?

这里利用上下文,获得包管理器getPackageManager。
利用包管理器再得到包信息,pm.getPackageInfo
然后可以得到android版本号,手机制造商,手机型号,cpu架构。
4.Config自定义通用类-用户设置
4.1.首先看一下Config类的源代码。
/*
* Copyright 2017 GcsSloop
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Last modified 2017-03-28 04:48:02
*
* GitHub: https://github.com/GcsSloop
* Website: http://www.gcssloop.com
* Weibo: http://weibo.com/GcsSloop
*/ package com.gcssloop.diycode.utils; import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.LruCache; import com.gcssloop.diycode_sdk.utils.ACache; import java.io.Serializable; /**
* 用户设置
*/
public class Config {
private static int M = 1024 * 1024;
private volatile static Config mConfig;
private static LruCache<String, Object> mLruCache = new LruCache<>(1 * M);
private static ACache mDiskCache; private Config(Context context) {
mDiskCache = ACache.get(context, "config");
} public static Config init(Context context) {
if (null == mConfig) {
synchronized (Config.class) {
if (null == mConfig) {
mConfig = new Config(context);
}
}
}
return mConfig;
} public static Config getSingleInstance() {
return mConfig;
} //--- 基础 ----------------------------------------------------------------------------------- public <T extends Serializable> void saveData(@NonNull String key, @NonNull T value) {
mLruCache.put(key, value);
mDiskCache.put(key, value);
} public <T extends Serializable> T getData(@NonNull String key, @Nullable T defaultValue) {
T result = (T) mLruCache.get(key);
if (result != null) {
return result;
}
result = (T) mDiskCache.getAsObject(key);
if (result != null) {
mLruCache.put(key, result);
return result;
}
return defaultValue;
} //--- 浏览器 --------------------------------------------------------------------------------- private static String Key_Browser = "UseInsideBrowser_"; public void setUesInsideBrowser(@NonNull Boolean bool) {
saveData(Key_Browser, bool);
} public Boolean isUseInsideBrowser() {
return getData(Key_Browser, Boolean.TRUE);
} //--- 首页状态 ------------------------------------------------------------------------------- private String Key_MainViewPager_Position = "Key_MainViewPager_Position"; public void saveMainViewPagerPosition(Integer position) {
mLruCache.put(Key_MainViewPager_Position, position);
} public Integer getMainViewPagerPosition() {
return getData(Key_MainViewPager_Position, 0);
} //--- Topic状态 ------------------------------------------------------------------------------ private String Key_TopicList_LastPosition = "Key_TopicList_LastPosition";
private String Key_TopicList_LastOffset = "Key_TopicList_LastOffset"; public void saveTopicListState(Integer lastPosition, Integer lastOffset) {
saveData(Key_TopicList_LastPosition, lastPosition);
saveData(Key_TopicList_LastOffset, lastOffset);
} public Integer getTopicListLastPosition() {
return getData(Key_TopicList_LastPosition, 0);
} public Integer getTopicListLastOffset() {
return getData(Key_TopicList_LastOffset, 0);
} private String Key_TopicList_PageIndex = "Key_TopicList_PageIndex"; public void saveTopicListPageIndex(Integer pageIndex) {
saveData(Key_TopicList_PageIndex, pageIndex);
} public Integer getTopicListPageIndex() {
return getData(Key_TopicList_PageIndex, 0);
} //--- News状态 ------------------------------------------------------------------------------ private String Key_NewsList_LastScroll = "Key_NewsList_LastScroll"; public void saveNewsListScroll(Integer lastScrollY) {
saveData(Key_NewsList_LastScroll, lastScrollY);
} public Integer getNewsLastScroll() {
return getData(Key_NewsList_LastScroll, 0);
} private String Key_NewsList_LastPosition = "Key_NewsList_LastPosition"; public void saveNewsListPosition(Integer lastPosition) {
saveData(Key_NewsList_LastPosition, lastPosition);
} public Integer getNewsListLastPosition() {
return getData(Key_NewsList_LastPosition, 0);
} private String Key_NewsList_PageIndex = "Key_NewsList_PageIndex"; public void saveNewsListPageIndex(Integer pageIndex) {
saveData(Key_NewsList_PageIndex, pageIndex);
} public Integer getNewsListPageIndex() {
return getData(Key_NewsList_PageIndex, 0);
}
}
4.2.定义的成员变量

M是数据单位的意思。
volatile关键字:

LruCache是android系统的通用类,存放类似map类型的缓存数据。
ACache是这个项目的SDK中定义最底层的缓存类。
4.3.构造函数+初始化+获取单例

4.4.基础是保存数据和获取数据,泛型好处理任何类型

在自定义的mLruCache和SDK包中的mDiskCache都要保存key,value之间的对应关系。
4.5.是否保存浏览器中用户数据+首页是否保存第几个碎片

估计应该就是配置是否保存一些东西吧。
4.6.话题的页码和上一个位置

获取上一个话题的位置和上一个话题的offset。
4.7.news的页码和上一个位置

获取上次滑动的位置,上一个news列表位置,上一个页面索引。
5.总结一下
5.1.以前不知道内存泄漏的严重性,当初学良让我注意android的内存泄漏的问题,我不以为然,现在看到别的项目无一
不在解决内存泄漏的问题,而且这个名字听起来内心就起疙瘩。还好LeakCanary出来了,可以解决这个大问题,也
是大难点,就不用到处寻找bug了,只要发生了内存泄漏,那个源头必然出现。
5.2.CrashHandler同样也是找bug能手,类似于腾讯的bugly,当然这个没有腾讯bugly强大。这个只能将异常输入到
一个日志,而bugly可以直接在网上输出,并且会发送邮箱,当然这个CrashHandler很简单,不用配置什么东西,
也许bugly就是根据这个来做出来的吧。
5.3.现在明白了Config是什么意思了,其实就是类似于我曾经定义的常量吧。不过这个不是常量,是一个记录的变量,
比如记录当前滑动的位置,那么下次进来就能记住了。从某个角度看,有点类似于sharePerference记录一样,
总之就是存储一些信息,然后之后来调用。
5.4.今天看的这个是Application,这是项目的大门,所以很多初始化的工作都是这里完成的。包括开启内存泄漏的检测
,开启异常日志的输出,用户设置的配置,Diycode项目SDK的初始化等。这个内存泄漏用的是第三方开源库,
记住就好,对于内存泄漏也有了更深的理解。无非就是在活动中添加一个回收即可。
Diycode开源项目 BaseApplication分析+LeakCanary第三方+CrashHandler自定义异常处理的更多相关文章
- DiyCode开源项目 BaseActivity 分析
1.首先将这个项目的BaseActivity源码拷贝过来. /* * Copyright 2017 GcsSloop * * Licensed under the Apache License, Ve ...
- Diycode开源项目 ImageActivity分析
1.首先看一下效果 1.1做成了一个GIF 1.2.我用格式工厂有点问题,大小无法调到手机这样的大小,目前还没有解决方案. 1.3.网上有免费的MP4->GIF,参考一下这个网站吧. 1.4.讲 ...
- Diycode开源项目 UserActivity分析
1.效果预览 1.1.实际界面预览 1.2. 这是MainActivity中的代码 这里执行了跳转到自己的用户界面的功能. 1.3.点击头像或者用户名跳转到别人的页面 UserActivity的结构由 ...
- Diycode开源项目 TopicContentActivity分析
1.效果预览以及布局分析 1.1.实际效果预览 左侧话题列表的布局是通过TopicProvider来实现的,所以当初分析话题列表就没有看到布局. 这里的话题内容不是一个ListView,故要自己布局. ...
- Diycode开源项目 LoginActivity分析
1.首先看一下效果 1.1.预览一下真实页面 1.2.分析一下: 要求输入Email或者用户名,点击编辑框,弹出键盘,默认先进入输入Email或用户名编辑框. 点击密码后,密码字样网上浮动一段距离,E ...
- Diycode开源项目 MainActivity分析
1.分析MainActivity整体结构 1.1.首先看一下这个界面的整体效果. 1.2.活动源代码如下 /* * Copyright 2017 GcsSloop * * Licensed under ...
- DiyCode开源项目 AboutActivity分析
1.首先看一下效果 这是手机上显示的效果: 1.1首先是一个标题栏,左侧一个左箭头,然后一个图标. 1.2然后下方是一个可以滑动的页面. 1.3分成了7个部分. 1.4DiyCode的图标. 1.5然 ...
- DiyCode开源项目 TopicActivity 分析
1.首先看看TopActivity效果. 2.TopicActivity是一个继承BaseActivity的.前面分析过BaseActivity了.主要有一个标题栏,有返回的图标. 3.贴一下T ...
- Diycode开源项目 SitesListFragment分析
1.效果预览 1.1.网站列表实际界面 1.2.注意这个界面没有继承SimpleRefreshRecycleFragment 前面的话题和新闻继承了SimpleRefreshRecyclerFragm ...
随机推荐
- iOS 本地缓存实现 方案借鉴
在手机应用程序开发中,为了减少与服务端的交互次数,加快用户的响应速度,一般都会在iOS设备中加一个缓存的机制,前面一篇文章介绍了iOS设备的内存缓存,这篇文章将设计一个本地缓存的机制. 功能需求 这个 ...
- 安卓usb数据接收
之前在论坛里面求助了关于监听数据接收的问题,因为第一次做这方面,可能我提的问题太简单了,大神都不愿意回答我,(之前的帖子)晚上FQ浏览网站发现问题的解决办法, 原文是:最近老板让弄安卓和一块板子通信, ...
- 使用Set去除String中的重复字符
使用Set去除String中的重复字符 public class test14 { public static void main(String[] args) { String str = &quo ...
- Photoshop之切图
基本(繁琐)操作: 切JPG图(即带背景的图): 1. 选切片工具(另外,切片选择工具能选择切片和删除切片),切 2. 存储为Web所用格式(快捷键Ctrl + Shi ...
- 一、基础知识 React API 一览
1.10 Hooks 参考文章:https://juejin.im/post/5be3ea136fb9a049f9121014 demo: /** * 必须要react和react-dom 16.7以 ...
- oracle 清空数据库缓存
oracle 清除数据库缓存: alter system flush shared_pool ; alter system flush BUFFER_CACHE ;
- C#cmd执行命令隐藏窗口,并保持程序一直运行
把要执行的cmd命令放入一个bat文件里,然后执行: //Process p = Process.Start(bPath); Process pro = new Process();pro.Start ...
- uvm_tlm——TLM1事务级建模方法(一)
TLM(事务级建模方法,Transaction-level modeling)是一种高级的数字系统模型化方法,它将模型间的通信细节与函数单元或通信架构的细节分离开来.通信机制(如总线或者FIFO)被建 ...
- zabbix-3.4 触发器
3 触发器 概述 触发器是"评估"由项目采集的数据并表示当前系统状况的逻辑表达式. 当监控项用于采集系统的数据时,始终遵循这些数据是非常不切合实际的,因为这些数据始终在等待一个令人 ...
- File 与 Log #3--动态加入控件,[图片版]访客计数器(用.txt档案来记录)
File 与 Log #3--动态加入控件,[图片版]访客计数器(用.txt档案来记录) 以前的两篇文章(收录在书本「上集」的第十七章) 请看「ASP.NET专题实务」,松岗出版 File 与 Log ...