Android 熱修復方案Tinker(一) Application改造


基於Tinker V1.7.5

    Android 熱修復方案Tinker(一) Application改造
    Android 熱修復方案Tinker(二) 補丁加載流程
    Android 熱修復方案Tinker(三) Dex補丁加載
    Android 熱修復方案Tinker(四) 資源補丁加載
    Android 熱修復方案Tinker(五) SO補丁加載
    Android 熱修復方案Tinker(六) Gradle插件實現
    Android 熱修復方案Tinker(七) 插樁實現
    帶注釋的源碼

這篇文章主要分析一下Tinker隔離Application.至於為什么要隔離Application?可以參考上一篇 Android 熱修復方案分析文章中說到的Qzone方案,要給除了Application子類所有的類注入一個獨立dex中的類引用,來避免class被打上CLASS_ISPREVERIFIED標記.而這個獨立的dex是在Application啟動之后加載的,所以Application子類就不能插入獨立dex的引用也就不能進行修復.不僅如此,由於Application子類被打上CLASS_ISPREVERIFIED標記,那么Application子類直接引用的對象就無法打補丁包,會拋出pre-verified異常.

在這種前提下還是想修復盡可能多的類怎么辦?對於Qzone方案的解決方法就是把Application子類中的邏輯都抽離到一個獨立的類例如ApplicationProxy中,Application只引用這個ApplicationProxy類從而減小影響范圍.Tinker使用的是全量更新,補丁也是在Application中加載的,所以Tinker的Application也是不能修改的.並且由於Tinker的全量更新不能兼容加固方案,如果app使用了加固的話可以使用usePreGeneratedPatchDex模式回退到Qzone方案上,這樣的話就需要面臨同樣的問題.

同時還因為Android N混合編譯與對熱補丁影響解析,這會造成要修復的class被緩存在App image中,App image中的class會插入PathClassLoader中,而PathClassLoader 加載補丁的時候不會替換緩存的class,最終會導致在全量更新的情況下部分類是從base.apk中加載,部分類是從patch.dex中加載,拋出IllegalAccessError.Tinker的解決方案是在運行時改寫PathClassLoader來加載類,讓App image中的緩存失效.而Application這個入口類還是只能用系統的PathClassLoader來加載,從這個角度來說也需要Application解耦出來.
Application 隔離

Tinker提供了兩種方式來隔離Application,分別是DefaultLifeCycle注解和繼承TinkerApplication,DefaultApplicationLike.推薦使用DefaultLifeCycle注解來隔離Application,這種方式會編譯自動生成Application,減少一些誤操作.而繼承TinkerApplication,DefaultApplicationLike其實就是自己寫出注解生成的代碼,所以這篇文章就只介紹注解的方式.

@DefaultLifeCycle(
        application = "com.test.MyApplication",
        flags = ShareConstants.TINKER_ENABLE_ALL,
        loadVerifyFlag = false
)
public class ApplicationLike extends DefaultApplicationLike {
    public MyApplicationLike(Application application, int tinkerFlags, boolean tinkerLoadVerifyFlag,
                                 long applicationStartElapsedTime, long applicationStartMillisTime, Intent tinkerResultIntent,
                                 Resources[] resources, ClassLoader[] classLoader, AssetManager[] assetManager) {
        super(application, tinkerFlags, tinkerLoadVerifyFlag,
                applicationStartElapsedTime, applicationStartMillisTime, tinkerResultIntent,
                resources, classLoader, assetManager);
    }
}

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14

在編譯的過程時注解執行完畢之后就會在設定的包路徑下生成出真實的Application java文件,默認繼承TinkerApplication.將注解中配置的數據傳遞給構造函數的參數.然后被編譯成.class打包到dex文件中.

public class MyApplication extends TinkerApplication {

    public MyApplication() {
        super(7, "cn.jesse.patchersample.ApplicationLike", "com.tencent.tinker.loader.TinkerLoader", false);
    }

}

    1
    2
    3
    4
    5
    6
    7

DefaultLifeCycle注解包含四個屬性,分別是真實的application路徑(application), 補丁loader的路徑(loaderClass), 補丁支持不同文件格式flag(flags), 是否每次都校驗補丁包MD5的flag(loadVerifyFlag).

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
@Inherited
public @interface DefaultLifeCycle {

    /**
     * 真實的Application
     */
    String application();

    /**
     * patch loader
     */
    String loaderClass() default "com.tencent.tinker.loader.TinkerLoader";

    /**
     * 支持的文件類型
     * ShareConstants.TINKERDISABLE:不支持任何類型的文件
     * ShareConstants.TINKERDEXONLY:只支持dex文件
     * ShareConstants.TINKERLIBRARYONLY:只支持library文件
     * ShareConstants.TINKERDEXANDLIBRARY:只支持dex與res的修改
     * ShareConstants.TINKERENABLEALL:支持任何類型的文件,也是我們通常的設置的模式
     */
    int flags();

    /**
     * 是否每次都校驗補丁包的MD5
     */
    boolean loadVerifyFlag() default false;
}

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30

在注解處理器AnnotationProcessor中,根據注解的參數和真實的Application模板,生成真正的Application.

在Android studio中 annotation module必須是java library否則在處理anno的時候會找不到AbstractProcessor.如果需要調試這部分代碼的話可以通過配置gradle --deamon和加斷點來debug.

DefaultLifeCycle ca = e.getAnnotation(DefaultLifeCycle.class);

//拿到代理類的名稱和包名
String lifeCycleClassName = ((TypeElement) e).getQualifiedName().toString();
String lifeCyclePackageName = lifeCycleClassName.substring(0, lifeCycleClassName.lastIndexOf('.'));
lifeCycleClassName = lifeCycleClassName.substring(lifeCycleClassName.lastIndexOf('.') + 1);

//拼裝出真實的Application類名稱
String applicationClassName = ca.application();
if (applicationClassName.startsWith(".")) {
    applicationClassName = lifeCyclePackageName + applicationClassName;
}

//拆分出真實的Application類名稱和包名
String applicationPackageName = applicationClassName.substring(0, applicationClassName.lastIndexOf('.'));
applicationClassName = applicationClassName.substring(applicationClassName.lastIndexOf('.') + 1);

//拿到patch loader的名稱
String loaderClassName = ca.loaderClass();
if (loaderClassName.startsWith(".")) {
    loaderClassName = lifeCyclePackageName + loaderClassName;
}

//讀取Application模板文件,將模板中的%KEY%占位符全部替換成真實的數據
final InputStream is = AnnotationProcessor.class.getResourceAsStream(APPLICATION_TEMPLATE_PATH);
final Scanner scanner = new Scanner(is);
final String template = scanner.useDelimiter("\\A").next();
final String fileContent = template
    .replaceAll("%PACKAGE%", applicationPackageName)
    .replaceAll("%APPLICATION%", applicationClassName)
    .replaceAll("%APPLICATION_LIFE_CYCLE%", lifeCyclePackageName + "." + lifeCycleClassName)
    .replaceAll("%TINKER_FLAGS%", "" + ca.flags())
    .replaceAll("%TINKER_LOADER_CLASS%", "" + loaderClassName)
    .replaceAll("%TINKER_LOAD_VERIFY_FLAG%", "" + ca.loadVerifyFlag());

//將完整的Application代碼寫入到跟代理Application的相同路徑下的文件中
// 至此注解生成真實Application的工作就完成了
try {
    JavaFileObject fileObject = processingEnv.getFiler().createSourceFile(applicationPackageName + "." + applicationClassName);
    processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "Creating " + fileObject.toUri());
    Writer writer = fileObject.openWriter();
    try {
        PrintWriter pw = new PrintWriter(writer);
        pw.print(fileContent);
        pw.flush();

    } finally {
        writer.close();
    }
} catch (IOException x) {
    processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, x.toString());
}

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52

Application模板是把一些變化的屬性通過%KEY%的形式代替其中包含文件包名,類名,構造函數名,tinker_flag, 代理Application, patcher loader, 和補丁校驗位.在注解編譯時就可以根據key對包名,Application名和構造函數的參數進行填充.

package %PACKAGE%;

import com.tencent.tinker.loader.app.TinkerApplication;

/**
 *
 * Generated application for tinker life cycle
 *
 */
public class %APPLICATION% extends TinkerApplication {

    public %APPLICATION%() {
        super(%TINKER_FLAGS%, "%APPLICATION_LIFE_CYCLE%", "%TINKER_LOADER_CLASS%", %TINKER_LOAD_VERIFY_FLAG%);
    }

}

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16

Application 相關依賴

用了DefaultLifeCycle注解之后就把真實的Application和代理Application隔離開了.至於兩者之間是如何同步聲明周期,如何建立依賴關系,以及他們的職責是什么?可以看一下這部分的類圖.

真實Application

在最早能獲取context的方法attachBaseContext處做相關的初始化工作.像時間統計,利用反射將TinkerLoader對象構建出來並調用tryLoad方法(校驗環境后加載補丁),反射創建出代理Application對象,同步Application生命周期和重置safe mode計數.

//記錄系統啟動時間和App啟動時刻
applicationStartElapsedTime = SystemClock.elapsedRealtime();
applicationStartMillisTime = System.currentTimeMillis();

//初始化patch loader, 並且調用tryLoad方法
loadTinker();
//確保代理Application對象已經被創建出來了
ensureDelegate();

//反射調用代理Application的`onBaseContextAttached`方法同步生命周期
try {
    Method method = ShareReflectUtil.findMethod(delegate, "onBaseContextAttached", Context.class);
    method.invoke(delegate, base);
} catch (Throwable t) {
    throw new TinkerRuntimeException("onBaseContextAttached method not found", t);
}

//重置safe mode計數, 當safe mode計數不小於三次時PatcherResultIntent會記錄patch失敗
if (useSafeMode) {
    String processName = ShareTinkerInternals.getProcessName(this);
    String preferName = ShareConstants.TINKER_OWN_PREFERENCE_CONFIG + processName;
    SharedPreferences sp = getSharedPreferences(preferName, Context.MODE_PRIVATE);
    sp.edit().putInt(ShareConstants.TINKER_SAFE_MODE_COUNT, 0).commit();
}

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24

代理Application

因為要用ApplicationLike代理真實的Application,所以ApplicationLike就要持有Application, resources, classLoader, assetManager的引用.其中tinkerResultIntent屬性中記錄着TinkerLoader啟動和加載patch的狀態.

//Application,resource,assetManager,classLoader的引用
private final Application application;
private Resources[]    resources;
private ClassLoader[]  classLoader;
private AssetManager[] assetManager;

//用於記錄啟動加載patcher loader的狀態
private final Intent tinkerResultIntent;

//系統的存活時間和app啟動時刻
private final long applicationStartElapsedTime;
private final long applicationStartMillisTime;

//注解中的兩個flags
private final int tinkerFlags;
private final boolean tinkerLoadVerifyFlag;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16

同時還要暴露出常用的Application共有方法,供外部使用.

void onCreate();
void onLowMemory();
void onTrimMemory(int level);
void onTerminate();
void onConfigurationChanged(Configuration newConfig);
void onBaseContextAttached(Context base);

 

至此Application改造的分析就完成了,上面提到了在Application的attachBaseContext中通過反射調起了TinkerLoader的tryLoad方法.tryLoad方法是加載最終補丁包的入口,下篇文章會沿着這條線繼續分析下去.


https://blog.csdn.net/l2show/article/details/53187548


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM