zoukankan      html  css  js  c++  java
  • 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);
        }
    }
    View Code

    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
        }
    
    }
    View Code

    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);
        }
    }
    View Code

    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的初始化等。这个内存泄漏用的是第三方开源库,

      记住就好,对于内存泄漏也有了更深的理解。无非就是在活动中添加一个回收即可。


     

    既然选择了,便不顾风雨兼程。Just follow yourself.
  • 相关阅读:
    【总结】编写自己的JDBC框架
    笔者带你剖析淘宝TDDL(TAOBAO DISTRIBUTE DATA LAYER)
    高性能jdbc封装工具 Apache Commons DbUtils 1.6(转载)
    简单通用JDBC辅助类封装
    word2010中怎样快速修改同级标题格式
    怎么批量修改Word表格的宽度
    MS WORD 表格自动调整列宽,自动变漂亮,根据内容自动调整 .
    PowerDesiger 15逆向生成工程E-R图及导出word表格
    PowerDesigner-自定义生成WORD
    PowerDesigner如何自定义报表模板
  • 原文地址:https://www.cnblogs.com/Jason-Jan/p/7879236.html
Copyright © 2011-2022 走看看