性能优化-稳定性优化

稳定性优化

稳定性中两个常见场景: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 infos = new HashMap();

    //保证只有一个 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 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 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,仅低于前台正在运行的进程,因此可以降低应用被系统杀掉概率。

你可能感兴趣的:(性能优化-稳定性优化)