使用CrashHandler來(lái)獲取應(yīng)用的crash信息
首先需要實(shí)現(xiàn)一個(gè)UncaughtExceptionHandler對(duì)象,在它的uncaughtException方法中獲取異常信息并將其存儲(chǔ)在SD卡中或者上傳到服務(wù)器供開(kāi)發(fā)人員分析,然后調(diào)用Thread的setDefaultUncaughtExceptionHandler方法將它設(shè)置為線程默認(rèn)的異常處理器,由于默認(rèn)異常處理器是Thread類的靜態(tài)成員,因此它的作用對(duì)象是當(dāng)前進(jìn)程的所有線程。
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 {
//導(dǎo)出異常信息到SD卡中
dumpExceptionToSDCard(ex);
//這里可以上傳異常信息到服務(wù)器,便于開(kāi)發(fā)人員分析日志從而解決bug
uploadExceptionToServer();
} catch(IOException e){
e.printStackTrace();
}
ex.printStackTrace();
//如果系統(tǒng)提供了默認(rèn)的異常處理器,則交給系統(tǒng)去結(jié)束程序,否則就由自己結(jié)束自己
if(mDefaultCrashHandler != null) {
mDefaultCrashHandler.uncaughtException(thread,ex);
} else {
Process.killProcess(Process.myPid());
}
}
private void dumpExceptionToSDCard(Throwable ex) throws IOException {
//如果SD卡不存在或無(wú)法使用,則無(wú)法把異常信息寫入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版本號(hào)
pw.print("OS Version:" );
pw.print(Build.VERSION.RELEASE);
pw.print("_");
pw.println(Build.VERSION.SDK_INT);
//手機(jī)制造商
pw.print("Vendor: ");
pw.println(Build.MANUFACTURER);
//手機(jī)型號(hào)
pw.print("Model: ");
pw.println(Build.MODEL);
//CPU架構(gòu)
pw.print("CPU ABI:");
pw.println(Build.CPU_ABI);
}
private void uploadExceptionToServer() {
}
}
64K方法數(shù)限制原理與解決方案
64k方法數(shù)問(wèn)題本質(zhì)上是指Android Dalvik可執(zhí)行文件.dex中的Java方法數(shù)引用超過(guò)65536,64k計(jì)算方法是65536除以1024。
64K限制的原因
Android APK文件本質(zhì)上是一個(gè)壓縮文件,它里面包含的classes.dex文件是可執(zhí)行的Dalvik字節(jié)碼文件,這個(gè).dex文件中存放的時(shí)所有編譯后的Java代碼。Dalvik可執(zhí)行文件規(guī)范限制了單個(gè).dex文件最多能引用的方法數(shù)是65536個(gè),這其中包含了Android Framework,APP引用的第三方函數(shù)庫(kù)以及APP自身的方法。
使用multidex解決64K限制的問(wèn)題:
Google推出了一個(gè)名為MultiDex Support Library的函數(shù)庫(kù),當(dāng)我們下載了Android
Support Libraries之后,可以在<sdk>/extras/android/support/multidex/目錄中找到這個(gè)函數(shù)庫(kù)。
將應(yīng)用的方法數(shù)降低到64K以下:
- 檢查應(yīng)用的直接和間接第三方依賴
- 使用Proguard移除無(wú)用的代碼
配置MultiDex
首先需要配置Application Module的build.gradle文件,在defaultConfig中添加multiDexEnabled true這個(gè)配置項(xiàng),接著在dependencies中添加multidex依賴:compile 'com.android.support:multidex:1.0.0'
在代碼中加入支持multidex的功能,三選一
- 在manifest文件中指定Application為MultiDexApplication
- 讓應(yīng)用的Application繼承MultiDexApplication
- 重寫Application的attachBaseContext方法,在該方法中加入MultiDex.install(this);
//這個(gè)方法是在onCreate之前執(zhí)行的,是開(kāi)發(fā)者可以控制的應(yīng)用最早執(zhí)行的方法。
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
MultiDex.install(this);
}
在開(kāi)發(fā)階段優(yōu)化MultiDex的構(gòu)建
MultiDex會(huì)增加構(gòu)建APK的時(shí)間,原因在于構(gòu)建系統(tǒng)需要經(jīng)過(guò)復(fù)雜的計(jì)算決定哪些類要包含在主dex文件中,哪些類可以包含在從dex文件中。
在工程主模塊的build.gradle文件中使用productFlavors來(lái)創(chuàng)建兩個(gè)flavor:一個(gè)是開(kāi)發(fā)階段使用的,一個(gè)是生產(chǎn)階段使用的。開(kāi)發(fā)階段的flavor中,設(shè)置minSdkVersion為21,這使得構(gòu)建系統(tǒng)使用ART支持的格式更快的生成MultiDex的輸出,生成階段的flavor中,設(shè)置minSdkVersion為應(yīng)用實(shí)際支持的最低版本號(hào)。
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文件,接著通過(guò)dex2jar反編譯classes.dex,然后通過(guò)jd-gui來(lái)打開(kāi)反編譯后的jar包。
《Android開(kāi)發(fā)藝術(shù)探討》綜合技術(shù)
《Android高級(jí)進(jìn)階》第21章 64K方法數(shù)限制原理與解決方案