30、Application(待完善)

一、全局异常捕捉

a) 首先创建全局异常捕捉类CrashHander

/**
 * UncaughtException处理类,当程序发生Uncaught异常的时候,有该类来接管程序,并记录发送错误报告.
 * @author user
 */
public class CrashHander implements UncaughtExceptionHandler {
    
    public static final String TAG = "CrashHandler";
    
    //系统默认的UncaughtException处理类 
    private Thread.UncaughtExceptionHandler mDefaultHandler;
    //CrashHandler实例
    private static CrashHander INSTANCE = new CrashHander();
    //程序的Context对象
    private Context mContext;
    //用来存储设备信息和异常信息
    private Map<String, String> infos = new HashMap<String, String>();
    //用于格式化日期,作为日志文件名的一部分
    private DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
    /** 保证只有一个CrashHandler实例 */
    private CrashHander() {
    }
    /** 获取CrashHandler实例 ,单例模式 */
    public static CrashHander getInstance() {
        return INSTANCE;
    }
    /**
     * 初始化
     * @param context
     */
    public void init(Context context) {
        mContext = context;
        //获取系统默认的UncaughtException处理器
        mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
        //设置该CrashHandler为程序的默认处理器
        Thread.setDefaultUncaughtExceptionHandler(this);
    }
    /**
     * 当UncaughtException发生时会转入该函数来处理
     */
    @Override
    public void uncaughtException(Thread thread, Throwable ex) {
        if (!handleException(ex) && mDefaultHandler != null) {
            //如果用户没有处理则让系统默认的异常处理器来处理
            mDefaultHandler.uncaughtException(thread, ex);
        } else {
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                Log.e(TAG, "error : ", e);
            }
            //退出程序
            android.os.Process.killProcess(android.os.Process.myPid());
            System.exit(1);
        }
    }
    /**
     * 自定义错误处理,收集错误信息 发送错误报告等操作均在此完成.
     * @param ex
     * @return true:如果处理了该异常信息;否则返回false.
     */
    private boolean handleException(Throwable ex) {
        if (ex == null) {
            return false;
        }
        //使用Toast来显示异常信息
        new Thread() {
            @Override
            public void run() {
                Looper.prepare();
                Toast.makeText(mContext, "很抱歉,程序出现异常,即将退出.", Toast.LENGTH_LONG).show();
                Looper.loop();
            }
        }.start();
        //收集设备参数信息 
        collectDeviceInfo(mContext);
        //保存日志文件 
        saveCrashInfo2File(ex);
        return true;
    }
}

其中使用到的收集设备参数的方法collectDeviceInfo

/**
 * 收集设备参数信息
 * @param ctx
 */
public void collectDeviceInfo(Context ctx) {
    try {
        PackageManager pm = ctx.getPackageManager();
        PackageInfo pi = pm.getPackageInfo(ctx.getPackageName(), PackageManager.GET_ACTIVITIES);
        if (pi != null) {
            String versionName = pi.versionName == null ? "null" : pi.versionName;
            String versionCode = pi.versionCode + "";
            infos.put("versionName", versionName);
            infos.put("versionCode", versionCode);
        }
    } catch (NameNotFoundException e) {
        Log.e(TAG, "an error occured when collect package info", e);
    }
    Field[] fields = Build.class.getDeclaredFields();
    for (Field field : fields) {
        try {
            field.setAccessible(true);
            infos.put(field.getName(), field.get(null).toString());
            Log.d(TAG, field.getName() + " : " + field.get(null));
        } catch (Exception e) {
            Log.e(TAG, "an error occured when collect crash info", e);
        }
    }
}

保存错误信息到文件中的方法

/**
 * 保存错误信息到文件中
 * @param ex
 * @return    返回文件名称,便于将文件传送到服务器
 */
private String saveCrashInfo2File(Throwable ex) {
    
    StringBuffer sb = new StringBuffer();
    for (Map.Entry<String, String> entry : infos.entrySet()) {
        String key = entry.getKey();
        String value = entry.getValue();
        sb.append(key + "=" + value + "\n");
    }
    
    Writer writer = new StringWriter();
    PrintWriter printWriter = new PrintWriter(writer);
    ex.printStackTrace(printWriter);
    Throwable cause = ex.getCause();
    while (cause != null) {
        cause.printStackTrace(printWriter);
        cause = cause.getCause();
    }
    printWriter.close();
    String result = writer.toString();
    sb.append(result);
    try {
        long timestamp = System.currentTimeMillis();
        String time = formatter.format(new Date());
        String fileName = "crash-" + time + "-" + timestamp + ".log";
        if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
            String path = "/sdcard/crash/";
            File dir = new File(path);
            if (!dir.exists()) {
                dir.mkdirs();
            }
            FileOutputStream fos = new FileOutputStream(path + fileName);
            fos.write(sb.toString().getBytes());
            fos.close();
        }
        return fileName;
    } catch (Exception e) {
        Log.e(TAG, "an error occured while writing file...", e);
    }
    return null;
}

b) 然后创建CrashApplication类继承自Application,这里是程序的入口,在此处设置异常捕捉。

public class CrashApplication extends Application {

    private static CrashApplication mApplication;
    public static CrashApplication getInstance(){
        return mApplication;
    }   
    @Override
    public void onCreate() {
        super.onCreate();
        
        CrashHandler catchHandler = CrashHandler.getInstance();
        catchHandler.init(getApplicationContext());
        mApplication = this;
    }
}

设置全局异常捕捉后,出现异常提示的是自定义的信息,无法看到堆栈信息,可以在此处注释掉异常捕捉类来进行调试。

c) 最后在清单文件中配置name属性指向自定义的捕捉类,让程序使用该类来捕获全局异常。

<application
    android:name="com.hll.phoneserver.CrashApplication"
    android:allowBackup="true"
    android:icon="@drawable/ic_launcher"
    android:label="@string/app_name"
    android:theme="@style/AppTheme" >
    <activity
        android:name=".MainActivity"
        android:label="@string/app_name">
        <meta-data
            android:name="android.support.UI_OPTIONS"
            android:value="splitActionBarWhenNarrow" />
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>
</application>

二、日志框架

a) 首先我们来创建日志过滤工具类,通过TAG判断是哪个应用的日志信息,且方便项目中打印日志使用

public class LogUtils {
    public static final String TAG = "PhoneServer";
    public LogUtils() {
    }
    /**
     * Send a VERBOSE log message.
     * 
     * @param msg
     *            The message you would like logged.
     */
    public static void v(String msg) {
        android.util.Log.v(TAG, buildMessage(msg));
    }
    /**
     * Send a VERBOSE log message and log the exception.
     * @param msg
     *            The message you would like logged.
     * @param thr
     *            An exception to log
     */
    public static void v(String msg, Throwable thr) {
        android.util.Log.v(TAG, buildMessage(msg), thr);
    }
    /**
     * Send a DEBUG log message.
     * @param msg
     */
    public static void d(String msg) {
        android.util.Log.d(TAG, buildMessage(msg));
    }
    /**
     * Send a DEBUG log message and log the exception.
     * @param msg
     *            The message you would like logged.
     * @param tr
     *            An exception to log
     */
    public static void d(String msg, Throwable thr) {
        android.util.Log.d(TAG, buildMessage(msg), thr);
    }
    /**
     * Send an INFO log message.
     * @param msg
     *            The message you would like logged.
     */
    public static void i(String msg) {
        android.util.Log.i(TAG, buildMessage(msg));
    }
    /**
     * Send a INFO log message and log the exception.
     * @param msg
     *            The message you would like logged.
     * @param thr
     *            An exception to log
     */
    public static void i(String msg, Throwable thr) {
        android.util.Log.i(TAG, buildMessage(msg), thr);
    }
    /**
     * Send an ERROR log message.
     * 
     * @param msg
     *            The message you would like logged.
     */
    public static void e(String msg) {
        android.util.Log.e(TAG, buildMessage(msg));
    }
    /**
     * Send a WARN log message
     * @param msg
     *            The message you would like logged.
     */
    public static void w(String msg) {
        android.util.Log.w(TAG, buildMessage(msg));
    }
    /**
     * Send a WARN log message and log the exception.
     * @param msg
     *            The message you would like logged.
     * @param thr
     *            An exception to log
     */
    public static void w(String msg, Throwable thr) {
        android.util.Log.w(TAG, buildMessage(msg), thr);
    }
    /**
     * Send an empty WARN log message and log the exception.
     * @param thr
     *            An exception to log
     */
    public static void w(Throwable thr) {
        android.util.Log.w(TAG, buildMessage(""), thr);
    }
    /**
     * Send an ERROR log message and log the exception.
     * 
     * @param msg
     *            The message you would like logged.
     * @param thr
     *            An exception to log
     */
    public static void e(String msg, Throwable thr) {
        android.util.Log.e(TAG, buildMessage(msg), thr);
    }
    /**
     * Building Message
     * @param msg
     *            The message you would like logged.
     * @return Message String
     */
    protected static String buildMessage(String msg) {
        StackTraceElement caller = new Throwable().fillInStackTrace()
                .getStackTrace()[2];
        return new StringBuilder().append(caller.getClassName()).append(".")
                .append(caller.getMethodName()).append("(): ").append(msg)
                .toString();
    }
}

b) 我们创建LogcatHelper类来过滤掉一些无用的日志,只收集当前程序的日志(目前无法多次上传,有时间进行改造)

public class LogcatHelper {
    private static final String LOG_FILE_NAME = "phoneserver.log";
    private static LogcatHelper INSTANCE = null;
    private static String mPathLogcat;
    private LogFilter mLogFilter = null;
    private Context mContext;
    private int mPId;
    private static String mStartTime = "";
    // 用来存储设备信息和异常信息
    // private Map<String, String> infos = new HashMap<String, String>();
    /**
     * 拼接本地日志存放路径,创建日志文件,得到当前时间
     * @param context 上下文
     */
    public static void init(Context context) {
        // 拼接日志存放路径
        StringBuffer LogPath = new StringBuffer();
        LogPath.append(Environment.getExternalStorageDirectory());
        LogPath.append("/Android/data/");
        LogPath.append(context.getPackageName()).append("/");
        LogPath.append("logs").append("/");
        mPathLogcat = LogPath.toString();
        
        // 文件不存在则创建
        File file = new File(mPathLogcat);
        if (!file.exists()) {
            file.mkdirs();
        }
        
        long currentTime = System.currentTimeMillis() / 1000;
        mStartTime = String.valueOf(currentTime);
    }
    
    /**
     * 创建LogcatHelper实例
     * @param context 上下文
     * @return LogcatHelper实例
     */
    public static LogcatHelper getInstance(Context context) {
        if (INSTANCE == null) {
            INSTANCE = new LogcatHelper(context);
        }
        return INSTANCE;
    }
    
    /**
     * 得到当前进程的唯一标识符
     * @param context    上下文
     */
    private LogcatHelper(Context context) {
        mContext = context;
        mPId = android.os.Process.myPid();
    }
    public void start() {
        if (mLogFilter == null) {
            mLogFilter = new LogFilter(String.valueOf(mPId), mPathLogcat);
            mLogFilter.start();
        }
    }
    public void stop() {
        if (mLogFilter != null) {
            mLogFilter.stopLogs();
            mLogFilter = null;
        }
    }
    
    /**日志文件大小超过1.5M自动上传*/
    public void sendMaxLogMessage() {
        if (mLogFilter != null) {
            mLogFilter.setLogFileLock(true);
            String file = mLogFilter.getLogFileName();
            File sendFile = new File(file);
            if (sendFile.exists() && sendFile.length() > 2000) {
                uploadLog();
                File newFile = new File(file);
                try {
                    newFile.createNewFile();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            mLogFilter.setLogFileLock(false);
        }
    }
    
    /**随时向后台上传日志*/
    public void sendLogMessage() {
        
        if (mLogFilter == null) {
            mLogFilter = new LogFilter(String.valueOf(mPId), mPathLogcat);
            // 开启线程过滤收集
            mLogFilter.start();
        }
        
        if (mLogFilter != null) {
            mLogFilter.setLogFileLock(true);
            String file = mLogFilter.getLogFileName();
            File sendFile = new File(file);
            if (sendFile.exists() && sendFile.length() > 2000) {
                uploadLog();
                File newFile = new File(file);
                try {
                    newFile.createNewFile();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            mLogFilter.setLogFileLock(false);
        }
    }
    
    /**
     * 收集设备参数信息
     * @param ctx
     */
    public void collectDeviceInfo() {
        try {
            PackageManager pm = mContext.getPackageManager();
            PackageInfo pi = pm.getPackageInfo(mContext.getPackageName(),PackageManager.GET_ACTIVITIES);
            if (pi != null) {
                String versionName = pi.versionName == null ? "null": pi.versionName;
                String versionCode = pi.versionCode + "";
                Log.e(LogUtils.TAG, "versionName : " + versionName);
                Log.e(LogUtils.TAG, "versionCode : " + versionCode);
            }
        } catch (NameNotFoundException e) {
            Log.e(LogUtils.TAG, "an error occured when collect package info", e);
        }
        
        // 暴力反射,获取所有成员
        Field[] fields = Build.class.getDeclaredFields();
        for (Field field : fields) {
            try {
                field.setAccessible(true);
                Log.e(LogUtils.TAG, field.getName() + " : " + field.get(null));
            } catch (Exception e) {
                Log.e(LogUtils.TAG,"an error occured when collect crash info: ", e);
            }
        }
    }
    
    public String getLogInfo() {
        StringBuilder sb = new StringBuilder("");
        String logContent = "";
        if (mLogFilter != null) {
            try {
                String file = mLogFilter.getLogFileName();
                File f = new File(file);
                if (!f.exists()) {
                    return "";
                }
                FileInputStream fin = new FileInputStream(file);
                int length = fin.available();
                byte[] buffer = new byte[length];
                fin.read(buffer);
                logContent = EncodingUtils.getString(buffer, "UTF-8");
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return logContent;
    }
    
    /**
     * 回收宝工程师、用户app向后台上传崩溃日志
     */
    public void uploadLog() {
        LogUtils.w("upload log");
        String logInfo = getLogInfo();
        if ("".equals(logInfo)) {
            return;
        }
        String uid = (String) SharedPreferencesUtils.getParam(mContext, Constant.UID, "-1");
        String userkey = (String) SharedPreferencesUtils.getParam(mContext, Constant.UKEY, "-1");
        String utype = (String) SharedPreferencesUtils.getParam(mContext, Constant.UTYPE, "-1");
        String username = (String) SharedPreferencesUtils.getParam(mContext, Constant.USERNAME, "-1");
        
        HttpUtils httpUtils = new HttpUtils();
        httpUtils.configCurrentHttpCacheExpiry(0);
        httpUtils.configDefaultHttpCacheExpiry(0);
        JSONObject data = new JSONObject();
        try {
            data.put("uid", uid);
            data.put("ukey", userkey);
            data.put("time", mStartTime);
            data.put("type", "0");
            data.put("systype", "1");
            data.put("uname", username);
            data.put("logdata", logInfo);
            data.put("utype", utype);
        } catch (JSONException e) {
            e.printStackTrace();
        }
        RequestParams params = new RequestParams();
        params.addBodyParameter("data", data.toString());
        httpUtils.send(HttpMethod.POST, Constant.UPLOADE_LOG, params,
                new RequestCallBack<String>() {
                    @Override
                    public void onFailure(HttpException arg0, String arg1) {
                        LogUtils.w("员工版向后台上传日志失败");
                    }
                    @Override
                    public void onSuccess(ResponseInfo<String> arg0) {
                        try {
                            JSONObject json = new JSONObject(arg0.result);
                            if ("0".equals(json.getString("ret"))) {
                                LogUtils.w("员工版向后台上传日志成功");
                            } else {
                                LogUtils.w(Constant.UPLOADE_LOG + json.toString());
                            }
                        } catch (JSONException e) {
                            e.printStackTrace();
                        }
                    }
                });
        stop();
    }
    private class LogFilter extends Thread {
        private String mLogFileDir;
        private Process logcatProc;
        private BufferedReader mReader = null;
        private boolean mRunning = false;
        private String cmds = null;
        private final String mPID;
        private FileOutputStream out = null;
        private List<String> logsMessage = new ArrayList<String>();
        private boolean mLogFileLock = false;
        private String mLogFileName;
        
        public void setLogFileLock(boolean lock) {
            mLogFileLock = lock;
        }
        public boolean isLogFileLock() {
            return mLogFileLock;
        }
        public LogFilter(String pid, String file) {
            mPID = String.valueOf(pid);
            mLogFileDir = file;
            initLogFile();
            // 日志等级:*:v , *:d , *:w , *:e , *:f , *:s 显示当前mPID程序的 E和W等级的日志.
            cmds = "logcat -v time *:e *:w | grep \"(" + mPID + ")\"";
        }
        private void initLogFile() {
            try {
                Runtime.getRuntime().exec("logcat -c");
            } catch (IOException e1) {
                e1.printStackTrace();
            }
            File logFile = new File(mLogFileDir, LOG_FILE_NAME);
            if (logFile.exists()) {
                logFile.delete();
            }
            try {
                logFile.createNewFile();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                mLogFileName = logFile.toString();
                out = new FileOutputStream(logFile, true);
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }
        }
        public String getLogFileName() {
            return mLogFileName;
        }
        public void stopLogs() {
            mRunning = false;
        }
        private boolean checkFileMaxSize(String file) {
            File sizefile = new File(file);
            if (sizefile.exists()) {
                // 1.5MB
                if (sizefile.length() > 1572864) {
                    return true;
                } else {
                    return false;
                }
            } else {
                return false;
            }
        }
        @Override
        public void run() {
            System.out.println("LogCatHelper'");
            mRunning = true;
            try {
                // 收集设备信息
                collectDeviceInfo();
                logcatProc = Runtime.getRuntime().exec(cmds);
                mReader = new BufferedReader(new InputStreamReader(
                        logcatProc.getInputStream()), 1024);
                String line = null;
                while (mRunning && (line = mReader.readLine()) != null) {
                    if (!mRunning) {
                        break;
                    }
                    if (line.length() == 0) {
                        continue;
                    }
                    synchronized (out) {
                        if (out != null) {
                            boolean maxSize = checkFileMaxSize(getLogFileName());
                            if (maxSize) {
                                // 文件大小超过1.5mb
                                sendMaxLogMessage();
                            }
                            if (isLogFileLock()) {
                                if (line.contains(mPID)) {
                                    logsMessage.add(line.getBytes() + "\n");
                                }
                            } else {
                                if (logsMessage.size() > 0) {
                                    for (String _log : logsMessage) {
                                        LogUtils.w("write: " + logsMessage);
                                        out.write(_log.getBytes());
                                    }
                                    logsMessage.clear();
                                }
                                // 再次过滤日志,筛选当前日志中有 mPID 则是当前程序的日志.
                                if (line.contains(mPID)) {
                                    out.write(line.getBytes());
                                    out.write("\n".getBytes());
                                }
                            }
                        }
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
                return;
            } finally {
                if (logcatProc != null) {
                    logcatProc.destroy();
                    logcatProc = null;
                }
                if (mReader != null) {
                    try {
                        mReader.close();
                        mReader = null;
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (out != null) {
                    try {
                        out.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    out = null;
                }
            }
        }
    }
}

c) 当然不要忘记添加权限

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

使用方法:

LogcatHelper helper = LogcatHelper.getInstance(this);
helper.init(this);
helper.start();

需要停止收集日志则直接调用stop即可。

三、内存管理

/**内存管理*/
@Override
public void onTrimMemory(int level) {
    switch (level) {
    case TRIM_MEMORY_COMPLETE:// 即将清理后台进程列表最后一个
        LogUtils.w("onTrimMemory:即将清理后台进程列表最后一个" + level);
        break;
    case TRIM_MEMORY_MODERATE:// 即将清理后台进程列表中部
        LogUtils.w("onTrimMemory:即将清理后台进程列表中部" + level);
        break;
    case TRIM_MEMORY_BACKGROUND:// 内存不足,切该进程是后台进程
        LogUtils.w("onTrimMemory:内存不足,切该进程是后台进程" + level);
        break;
    case TRIM_MEMORY_UI_HIDDEN:// 内存不足,并且该进程ui已不可见
        LogUtils.w("onTrimMemory:内存不足,并且该进程ui已不可见" + level);
        break;
    }
}

################################################

猜你喜欢

转载自www.cnblogs.com/pengjingya/p/5510110.html