[學習筆記]Android開發藝術探索:綜合技術(Crash、Multidex、動態加載、反編譯)

使用CrashHandler來獲取應用的crash信息

檢測崩潰並瞭解詳細的crash信息:

首先需實現一個uncaughtExceptionHandler對象,在它的uncaughtException方法中獲取異常信息並將其存儲到SD卡或者上傳到服務器中,然後調用Thread的setDefaultUncaughtExceptionHandler爲當前進程的所有線程設置異常處理器。

public class CrashHandler implements Thread.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 Thread.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();
    }

    /**
     * 這個是最關鍵的函數,當程序中有未被捕獲的異常,系統將會自動調用#uncaughtException方法
     * thread爲出現未捕獲異常的線程,ex爲未捕獲的異常,有了這個ex,我們就可以得到異常信息。
     */
    @Override
    public void uncaughtException(Thread thread, Throwable ex) {
        try {
            //導出異常信息到SD卡中
            dumpExceptionToSDCard(ex);
            uploadExceptionToServer();
            //這裏可以通過網絡上傳異常信息到服務器,便於開發人員分析日誌從而解決bug
        } 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 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.println(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() {
        //TODO 本方法用於將錯誤信息上傳至服務器
    }
}

然後在Application初始化的時候爲線程設置CrashHandler,這樣之後,Crash就會通過我們自己的異常處理器來處理異常了。

public class BaseApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        CrashHandler crashHandler = CrashHandler.getInstance();
        crashHandler.init(this);
    } 
}

使用multidex來解決方法數越界

在Android中單個dex文件所能包含的最大方法數爲65536,這個是包含Android FrameWork、依賴jar包以及應用本身代碼中的所有方法。達到這個65536後,編譯器編譯時會拋出DexIndexOverflowException異常。

Google提供了multidex解決方案。在Android5.0之前需要引入Google提供的android-support-multidex.jar;從5.0開始系統默認支持了multidex,它可以從apk文件中加載多個dex文件。

使用步驟

  1. 修改對應工程目錄下的build.gradle文件,在defaultConfig中添加multiDexEnabled true這個配置項。

  2. 在build.gradle的dependencies中添加multidex的依賴:

    'compile ‘com.android.support:multidex:1.0.0’

android {
    compileSdkVersion 23
    buildToolsVersion "23.0.3"
    defaultConfig {
        applicationId "cn.hudp.androiddevartnote"
        minSdkVersion 14
        targetSdkVersion 23
        versionCode 1
        versionName "1.0"
        multiDexEnabled true  //關鍵部分
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}
dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:23.4.0'
    compile 'com.android.support:multidex:1.0.0' //關鍵部分
}

代碼中加入支持multidex功能。

  • 第一種方案,在manifest文件中指定Application爲MultiDexApplication。
  • 第二種方案,讓應用的Application繼承MultiDexApplication。
  • 第三種方案,重寫attachBaseContext方法,這個方法比onCreate還要先執行。
public class BaseApplication extends Application {
  @Override
  protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        MultiDex.install(this);
    }
}

採用上面的配置項後,如果這個應用方法數沒有越界,那麼Gradle是不會生成多個dex文件的,當方法數越界後,Gradle就會在apk中打包2個或多個dex文件。當需要指定主dex文件中所包含的類,這時候就需要通過–multi-dex-list來選項來實現這個功能。

//在對應工程目錄下的build.gradle文件,加入
afterEvaluate {
    println "afterEvaluate"
    tasks.matching {
        it.name.startsWith('dex')
    }.each { dx ->
        def listFile = project.rootDir.absolutePath + '/app/maindexlist.txt'
        println "root dir:" + project.rootDir.absolutePath
        println "dex task found: " + dx.name
        if (dx.additionalParameters == null) {
            dx.additionalParameters = []
        }
        dx.additionalParameters += '--multi-dex'
        dx.additionalParameters += '--main-dex-list=' + listFile
        dx.additionalParameters += '--minimal-main-dex'
    }
} 

maindexlist.txt

com/ryg/multidextest/TestApplication.class
com/ryg/multidextest/MainActivity.class
  
// multidex 這9個類必須在主Dex中
android/support/multidex/MultiDex.class
android/support/multidex/MultiDexApplication.class
android/support/multidex/MultiDexExtractor.class
android/support/multidex/MultiDexExtractor$1.class
android/support/multidex/MultiDex$V4.class
android/support/multidex/MultiDex$V14.class
android/support/multidex/MultiDex$V19.class
android/support/multidex/ZipUtil.class
android/support/multidex/ZipUtil$CentralDirectory.class

需要注意multidex的jar中的9個類必須要打包到主dex中,因爲Application的attachBaseContext方法中需要用到MultiDex.install(this)需要用到MultiDex。

Multidex的缺點:

  1. 啓動速度會降低,由於應用啓動時會加載額外的dex文件,這將導致應用的啓動速度降低,甚至產生ANR現象。
  2. 因爲Dalvik linearAlloc的bug,可以導致使用multidex的應用無法在Android4.0之前的手機上運行,需要做大量兼容性測試。

Android動態加載

各種插件化方案都需要解決3個基礎性問題:

  1. 資源訪問,因爲插件中凡是以R開頭的資源文件都不能訪問了。
  2. Activity的生命週期管理,因爲宿主動態將Activity.java加載到內存的時候,是不具備Activity的任何特性的,只是一個普通的java類。
  3. ClassLoader的管理,爲了避免多個ClassLoader加載了同一個類所引發的類型轉換錯誤。

反編譯初步

  1. 使用dex2jar和jd-gui反編譯apk
  2. 使用apktool對apk進行二次打包
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章