综合技术

使用CrashHandler来获取应用的crash信息

首先需要实现一个UncaughtExceptionHandler对象,在它的uncaughtException方法中获取异常信息并将其存储在SD卡中或者上传到服务器供开发人员分析,然后调用Thread的setDefaultUncaughtExceptionHandler方法将它设置为线程默认的异常处理器,由于默认异常处理器是Thread类的静态成员,因此它的作用对象是当前进程的所有线程。

public class CrashHandler implements UncaughtExceptionHandler {
    private static final String TAG = "CrashHandler";
    private static final boolean DEBUG = true;

    private static final String PATH = Environment.getExternalStorageDirectory().getPath() + "/CrashTest/log/";
    private static final String FILE_NAME = "crash";
    private static final String FILE_NAME_SUFFIX = ".trace";

    private static CrashHandler sInstance = new CrashHandler();
    private UncaughtExceptionHandler mDefaultCrashHandler;
    private Context mContext;

    private CrashHandler() {
    }

    public static CrashHandler getInstance() {
         return sInstance;
    }

    public void init(Context context) {
        mDefaultCrashHandler = Thread.getDefaultUncaughtExceptionHandler();
        Thread.setDefaultUncaughtExceptionHandler(this);
        mContext = context.getApplicationContext();
    }

    @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));
        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.print(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() {
        
    }
}

64K方法数限制原理与解决方案

64k方法数问题本质上是指Android Dalvik可执行文件.dex中的Java方法数引用超过65536,64k计算方法是65536除以1024。

64K限制的原因

Android APK文件本质上是一个压缩文件,它里面包含的classes.dex文件是可执行的Dalvik字节码文件,这个.dex文件中存放的时所有编译后的Java代码。Dalvik可执行文件规范限制了单个.dex文件最多能引用的方法数是65536个,这其中包含了Android Framework,APP引用的第三方函数库以及APP自身的方法。

使用multidex解决64K限制的问题:

Google推出了一个名为MultiDex Support Library的函数库,当我们下载了Android
Support Libraries之后,可以在/extras/android/support/multidex/目录中找到这个函数库。
将应用的方法数降低到64K以下:

  • 检查应用的直接和间接第三方依赖
  • 使用Proguard移除无用的代码
配置MultiDex

首先需要配置Application Module的build.gradle文件,在defaultConfig中添加multiDexEnabled true这个配置项,接着在dependencies中添加multidex依赖:compile 'com.android.support:multidex:1.0.0'
在代码中加入支持multidex的功能,三选一

  • 在manifest文件中指定Application为MultiDexApplication
  • 让应用的Application继承MultiDexApplication
  • 重写Application的attachBaseContext方法,在该方法中加入MultiDex.install(this);
//这个方法是在onCreate之前执行的,是开发者可以控制的应用最早执行的方法。
protected void attachBaseContext(Context base) {
    super.attachBaseContext(base);
    MultiDex.install(this);
}
在开发阶段优化MultiDex的构建

MultiDex会增加构建APK的时间,原因在于构建系统需要经过复杂的计算决定哪些类要包含在主dex文件中,哪些类可以包含在从dex文件中。
在工程主模块的build.gradle文件中使用productFlavors来创建两个flavor:一个是开发阶段使用的,一个是生产阶段使用的。开发阶段的flavor中,设置minSdkVersion为21,这使得构建系统使用ART支持的格式更快的生成MultiDex的输出,生成阶段的flavor中,设置minSdkVersion为应用实际支持的最低版本号。

android {
    productFlavors {
        dev {
            minSdkVersion 21
        }  
        prod {
            minSdkVersion 14
        }
    }
    ...
    buildTypes {
        release {
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android.txt'),'proguard-rules.pro'
        }
    }
}
反编译初步

apktool
dex2jar
jd-gui

  • 使用dex2jar和jd-gui反编译apk
    首先将apk解压后提取classes.dex文件,接着通过dex2jar反编译classes.dex,然后通过jd-gui来打开反编译后的jar包。

《Android开发艺术探讨》综合技术
《Android高级进阶》第21章 64K方法数限制原理与解决方案

你可能感兴趣的:(综合技术)