使用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之后,可以在<sdk>/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'
}
}
}
反编译初步
- 使用dex2jar和jd-gui反编译apk
首先将apk解压后提取classes.dex文件,接着通过dex2jar反编译classes.dex,然后通过jd-gui来打开反编译后的jar包。
《Android开发艺术探讨》综合技术
《Android高级进阶》第21章 64K方法数限制原理与解决方案