美文网首页
性能优化-稳定性优化

性能优化-稳定性优化

作者: Vinson武 | 来源:发表于2020-02-23 23:46 被阅读0次

    稳定性优化

    稳定性中两个常见场景:Crash和ANR

    提高代码质量

    代码审查

    1. 何时审查
      分两个方面:一是这个模块是否需要审查,明确代码审查的必要性,二是在开发阶段的哪个时间点代码审查,确定审查合理时间点。

    一般审查:底层公共模块、重大特性业务代码、与其他模块有耦合、新手、应用即将发布前的紧急修改。

    1. 谁来审查

    代码审查分为三种方式

    • 团队审查:底层通用模块
    • 模块负责人审查:某些模块化的功能和业务
    • 结对审查:两人结对互相审查
    1. 审查内容

    审查的流程:先审查设计实现思路,然后审查设计模式,接着审查形成的骨干代码,然后审查完成的代码。

    审查内容:

    • 实现思路和设计思想
    • 代码设计
    • 设计逻辑
    • 代码风格
    • 需求理解

    代码静态扫描工具

    4种常用Java代码分析工具对比如下


    image.png
    1. Checkstyle:通过对代码编码格式、命名约定、Javadoc、类she j设计等方面进行代码规范和风格的检查。
    2. FindBugs:通过检测类文件或JAR文件,将字节码与一组缺陷模式进行对比从而发现代码缺陷,完成静态代码分析。
    3. PMD:通过其内置的编码规则对Java代码进行静态检查,主要检查潜在的bug、未使用的代码、重复的代码、循环体创建新对象等问题。
    4. Android Lint:除了代码缺陷,还能检测代码布局的合理性。

    Crash监控

    Android应用中发生的crash有两种类型,Java层的Crash和Native层的Crash

    Java层的Crash监控

    Android中,Java虚拟机为每个进程都设置类一个默认的UncaughtExeptionHandler,用于处理本进程中未被try catch的Exception。因此只有实现UncaughtExeptionHandler接口,并进程启动时调用Thread.setDefaultUncaughtExceptionHandler(...)传入自定义的UncaughtExeptionHandler,发生未捕获异常时就会回调uncaughtException(Thread thread, Throwable ex) 方法。

    demo

    public class ABLCrashHandler implements UncaughtExceptionHandler {
    
        public static final String TAG = "ABLCrashHandler";
        // ABLCrashHandler 实例
        private static ABLCrashHandler INSTANCE = null;
        // 程序的 Context 对象
        private Context mContext;
        // 系统默认的 UncaughtException 处理类
        private UncaughtExceptionHandler mDefaultHandler;
        // 用来存储设备信息和异常信息
        private Map<String, String> infos = new HashMap<String, String>();
    
        //保证只有一个 ABLCrashHandler 实例
        private ABLCrashHandler(Context context) {
            this.init(context);
        }
    
        //获取 ABLCrashHandler 实例 ,单例模式
        public static ABLCrashHandler getInstance(Context context) {
            if (INSTANCE == null) {
                INSTANCE = new ABLCrashHandler(context);
            }
            return INSTANCE;
        }
    
        /**
         * 初始化
         *
         * @param context
         */
        public void init(Context context) {
            mContext = context;
            // 获取系统默认的 UncaughtException 处理器
            mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
            // 设置该 ABLCrashHandler 为程序的默认处理器
            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(2000);
                } 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(final Throwable ex) {
            if (ex == null) {
                return false;
            }
    
            ex.printStackTrace();
    
            //使用 Toast 来显示异常信息
            new Thread() {
                @Override
                public void run() {
                    Looper.prepare();
    
                    try {
                        ABLApplication.getApplication().getServicesManager().stopScan();
                        ABLApplication.getApplication().getServicesManager().destroy();
    
                        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 message = writer.toString();
    
                        Log.i(TAG + ".handleException.message", message + " |");
    
                        String exceptionCode = ABLExceptionCoder.getExceptionCode(message);
    
                        Log.i(TAG + ".handleException.exceptionCode", exceptionCode + " |");
    
                        Toast.makeText(mContext, "很抱歉,程序出现异常,即将退出,错误码:" + exceptionCode, Toast.LENGTH_LONG).show();
    
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
    
                    Looper.loop();
                }
            }.start();
            // 收集设备参数信息
            collectDeviceInfo(mContext);
            // 保存日志文件
            saveCrashInfo2File(ex);
            return true;
        }
    
        /**
         * 收集设备参数信息
         * @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);
                    infos.put("deviceID", Utils.getDeviceID_B());
                }
            } catch (NameNotFoundException e) {
                Log.e(TAG, "an error occured when collect package info", e);
            } catch (Exception 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  返回文件名称,便于将文件传送到服务器
         */
        public String saveCrashInfo2File(Throwable ex) {
            try {
                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();
    
                String exceptionCode = ABLExceptionCoder.getExceptionCode(result);
    
                sb.append("exceptionCode=" + exceptionCode + "\n");
    
                sb.append(result);
                try {
                    String fileName = "crash-" + DateUtil.getNowTime3() + ".log";
                    if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
                        String path = FileUtils.getSDCardPath() + ABLConfig.SAVE_FOLDER + "/Bug/";
                        File dir = new File(path);
                        if (!dir.exists()) {
                            dir.mkdirs();
                        }
                        File file = new File(path + fileName);
                        if  (!file.exists()){
                            file.createNewFile();
                        }
    
                        //增加异常信息
                        FileWriter fw = new FileWriter(file, true);
                        fw.write("time:" + DateUtil.getNowTime());
                        fw.write("\r\n");
                        fw.write(sb.toString());
                        fw.write("\r\n\r\n");
                        fw.flush();
                        fw.close();
                    }
                    return fileName;
                } catch (Exception e) {
                    e.printStackTrace();
                    Log.e(TAG, "an error occured while writing file...", e);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
    
            return null;
        }
    }
    

    Nativa 层Crash监控

    当应用程序发生异常时,Linux内核会生成错误信号并通知当前进程。应用进程接受到错误信号后,可以捕获该信号并执行对应的信号处理函数。而当应用发生验证错误时后发生Crash,Linux有一类专门用于描述Crash的信号。只要在应用程序注册了这些信号的处理函数,当JNI crash时,我们的处理函数就会被调用到,然后获取dump文件再上传,后续的工作就和Java异常逻辑一致了。

    ANR剖析

    ANR介绍

    类型:

    1. KeyDispatchTimeout:对输入事件5秒内无响应
    2. BroadcaseTimeout:在指定时间(默认10秒)无法处理完毕,并且没有结束执行onReceive
    3. ServiceTimeout:指Service在特定时间(默认20秒)内无法处理完成。

    ANR分析

    如果发送ANR, Logcat会产生对应的日志和一个traces文件,这个文件保存在/data/anr/traces.txt。
    可以直接用adb工具获取traces.txt文件:

    adb pull /data/anr/traces.txt
    

    可以通过分析log和traces文件信息定位到anr发生的位置和分析原因。

    AS提供了一个分析trace文件的工具:Analyze Stacktrace。打开Analyze Stacktrace工具,将traces.txt文件内容复制到窗口,单击Normalize按钮,生成Thread Dump列表。如果某个线程被标红,说明此线程被堵塞了。

    提高后台进程存活率

    当内存紧张时,优先级低、占用内存大的app进程会优先被杀死。可以提高提高进程优先级使应用在后台的存活时间更长,一般使用以下几种方法实现:

    1. 网络连接

    通过长连接心跳和进程保持通信,使进程保持活动状态,但如果系统内存非常紧张,也有可能被杀。

    1. 利用系统现有机制

    一般可以注册系统消息(AlarmReceive、BootReceive等),通过系统消息响应挂起进程。

    1. SyncAdapter

    利用Android系统提供的账号同步机制实现进程优先级提高。

    SyncAdapter是一个系统服务,通过系统的定时器更新应用程序数据ContentProvider,因为Sync服务工作在独立进程,并由系统调度,属于核心进程级别,系统不会杀掉,而使用了SyncAdapter的进程优先级也会提高,优先级变为1,仅低于前台正在运行的进程,因此可以降低应用被系统杀掉概率。

    相关文章

      网友评论

          本文标题:性能优化-稳定性优化

          本文链接:https://www.haomeiwen.com/subject/sytyqhtx.html