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开发中常用的问题,导致程序的稳定性下降。

  github原地址点我。

  参考文章:使用Leak Canary检测应用的内存泄漏。

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,每次异常,它都会给我发送一封邮件,告诉我哪里崩溃,

  哪里异常,每天早上也会统计今日崩溃次数等。非常简单实用的第三方库。

  这里提供一下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自定义异常处理的更多相关文章

  1. DiyCode开源项目 BaseActivity 分析

    1.首先将这个项目的BaseActivity源码拷贝过来. /* * Copyright 2017 GcsSloop * * Licensed under the Apache License, Ve ...

  2. Diycode开源项目 ImageActivity分析

    1.首先看一下效果 1.1做成了一个GIF 1.2.我用格式工厂有点问题,大小无法调到手机这样的大小,目前还没有解决方案. 1.3.网上有免费的MP4->GIF,参考一下这个网站吧. 1.4.讲 ...

  3. Diycode开源项目 UserActivity分析

    1.效果预览 1.1.实际界面预览 1.2. 这是MainActivity中的代码 这里执行了跳转到自己的用户界面的功能. 1.3.点击头像或者用户名跳转到别人的页面 UserActivity的结构由 ...

  4. Diycode开源项目 TopicContentActivity分析

    1.效果预览以及布局分析 1.1.实际效果预览 左侧话题列表的布局是通过TopicProvider来实现的,所以当初分析话题列表就没有看到布局. 这里的话题内容不是一个ListView,故要自己布局. ...

  5. Diycode开源项目 LoginActivity分析

    1.首先看一下效果 1.1.预览一下真实页面 1.2.分析一下: 要求输入Email或者用户名,点击编辑框,弹出键盘,默认先进入输入Email或用户名编辑框. 点击密码后,密码字样网上浮动一段距离,E ...

  6. Diycode开源项目 MainActivity分析

    1.分析MainActivity整体结构 1.1.首先看一下这个界面的整体效果. 1.2.活动源代码如下 /* * Copyright 2017 GcsSloop * * Licensed under ...

  7. DiyCode开源项目 AboutActivity分析

    1.首先看一下效果 这是手机上显示的效果: 1.1首先是一个标题栏,左侧一个左箭头,然后一个图标. 1.2然后下方是一个可以滑动的页面. 1.3分成了7个部分. 1.4DiyCode的图标. 1.5然 ...

  8. DiyCode开源项目 TopicActivity 分析

    1.首先看看TopActivity效果.    2.TopicActivity是一个继承BaseActivity的.前面分析过BaseActivity了.主要有一个标题栏,有返回的图标. 3.贴一下T ...

  9. Diycode开源项目 SitesListFragment分析

    1.效果预览 1.1.网站列表实际界面 1.2.注意这个界面没有继承SimpleRefreshRecycleFragment 前面的话题和新闻继承了SimpleRefreshRecyclerFragm ...

随机推荐

  1. D2 前端会议

    D2 前端会议 时间 2019年1月6日 图片

  2. SpringBoot2.0之三 优雅整合Spring Data JPA

      在我们的实际开发的过程中,无论多复杂的业务逻辑到达持久层都回归到了"增删改查"的基本操作,可能会存在关联多张表的复杂sql,但是对于单表的"增删改查"也是不 ...

  3. hibernate自动建表技术_采用数据库反向生成技术

    1.首先使用oracle创建一个用户: 登陆sqlplus,并以sysdba登陆到数据库: 2.创建一个用户,并对此用户授予connect,resource两个角色的权限: 3.连接到hibernat ...

  4. enable orgmode latex preview to support retina on mac

    Table of Contents 1. enable orgmode latex preview to support retina on mac 1.1. get the proper versi ...

  5. logback的configuration

    logback的<configuration>只有三个属性: 1.scan[boolean]:当scan被设置为true时,当配置文件发生改变,将会被重新加载.默认值为true. 2.sc ...

  6. 有些其他程序设置为从 Outlook 下载并删除邮件。为防止发生此意外情况,我们将这些邮件放入一个特殊的 POP 文件夹中

    最近使用FOXMAIL接收MSN邮件时,发现有一些邮件收取不到,进到WEB页面,页面下方提示“你的邮件位于 POP 文件夹中!有些其他程序设置为从 Outlook 下载并删除邮件.为防止发生此意外情况 ...

  7. 在SAP云平台的CloudFoundry环境下消费ABAP On-Premise OData服务

    我的前一篇文章 使用Java+SAP云平台+SAP Cloud Connector调用ABAP On-Premise系统里的函数介绍了在SAP云平台的Neo环境下如何通过SAP Cloud Conne ...

  8. 定义多个属性 Object.defineProperties()

    var book = {} Object.defineProperties(book,{ _year:{ value:2004 }, editable:{ value:1 }, year:{ get: ...

  9. [转]Cannot deserialize the current JSON array (e.g. [1,2,3]) into type

    string content =[{"id": 3636, "is_default": true, "name": "Unit&q ...

  10. constraint的一些用法总结

    主要就是增加约束的 以下几种约束 .并 一一列举: 1.主键约束: 要对一个列加主键约束的话,这列就必须要满足的条件就是分空 因为主键约束:就是对一个列进行了约束,约束为(非空.不重复) 以下是代码  ...