Tinker加載流程

Application代理

前面只是從TinkerInstaller的兩個api去分析了流程,但是分析完畢了,仍然有一些我們還沒有涉及到的內容:

  • 記得我們使用Tinker時自定義過一個ApplicationLike,并使用了DefaultLifeCycle注解。
  • 在Manifest中,我們設置了application為注解參數的application。

注解幫我們自動生成了application,這其中發生了什么?

1. DefaultLifeCycle注解生成Application

@DefaultLifeCycle(application = ".MyTinkerApplication",
        flags = ShareConstants.TINKER_ENABLE_ALL,
        loadVerifyFlag = false)
public class CustomApplicationLike extends ApplicationLike {
    // ......
}

注解對應的AbstractProcessor是AnnotationProcessor。

1-1 AnnotationProcessor # getSupportedAnnotationTypes()

getSupportedAnnotationTypes()會返回該Processor處理的所有注解集合,只有DefaultLifeCycle。

@Override
public Set<String> getSupportedAnnotationTypes() {
    final Set<String> supportedAnnotationTypes = new LinkedHashSet<>();
    supportedAnnotationTypes.add(DefaultLifeCycle.class.getName());
    return supportedAnnotationTypes;
}
1-2 AnnotationProcessor # process()

這個方法才是編譯時檢測到注解而執行的工作。調用了processDefaultLifeCycle()。

@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
    processDefaultLifeCycle(roundEnv.getElementsAnnotatedWith(DefaultLifeCycle.class));
    return true;
}
1-3 AnnotationProcessor # processDefaultLifeCycle()

讀取了一個模板文件,去匹配要修改的字符串,修改成注解的參數設置。就這樣為我們自動生成了Application文件。

private void processDefaultLifeCycle(Set<? extends Element> elements) {
    // DefaultLifeCycle
    for (Element e : elements) {
        // 從注解參數得到下面需要替換為的值
        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);
        String applicationClassName = ca.application();
        if (applicationClassName.startsWith(".")) {
            applicationClassName = lifeCyclePackageName + applicationClassName;
        }
        String applicationPackageName = applicationClassName.substring(0, applicationClassName.lastIndexOf('.'));
        applicationClassName = applicationClassName.substring(applicationClassName.lastIndexOf('.') + 1);
        String loaderClassName = ca.loaderClass();
        if (loaderClassName.startsWith(".")) {
            loaderClassName = lifeCyclePackageName + loaderClassName;
        }
        
        System.out.println("*");
        // 讀取該模板文件"/TinkerAnnoApplication.tmpl";
        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());
        // 再將文件寫出去
        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-4 生成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%);
    }

}

可以看到模板中生成的Application是繼承了TinkerApplication的。模板很簡單所以大部分邏輯都在TinkerApplication中。super()傳入了CustomApplicationLike這個自定義的ApplicationLike,想必TinkerApplication和ApplicationLike關系密切。

public class MyTinkerApplication extends TinkerApplication {
    public MyTinkerApplication() {
        super(7, "com.utte.tinkertest.tinker.module.CustomApplicationLike", "com.tencent.tinker.loader.TinkerLoader", false);
    }
}

2. ApplicationLike

在我們的CustomApplicationLike的onBaseContextAttached()方法中主要是需要調用Tinker初始化的方法,這是官方建議的。

@DefaultLifeCycle(application = ".MyTinkerApplication",
        flags = ShareConstants.TINKER_ENABLE_ALL,
        loadVerifyFlag = false)
public class CustomApplicationLike extends ApplicationLike {

    // ......構造器

    /**
     * 需要在這個方法中完成Tinker的初始化
     * @param base
     */
    @Override
    public void onBaseContextAttached(Context base) {
        super.onBaseContextAttached(base);
        // 使應用支持分包
        MultiDex.install(base);
        // 初始化Tinker
        TinkerManager.installTinker(this);
    }
}
2-1 生命周期

Application實現了ApplicationLifeCycle

public abstract class ApplicationLike implements ApplicationLifeCycle

ApplicationLifeCycle其實就相當于Application生命周期回調和其他一些特殊情況回調的接口,自然ApplicationLike中就實現了這些了。

public interface ApplicationLifeCycle {

    void onCreate();

    void onLowMemory();

    void onTrimMemory(int level);

    void onTerminate();

    void onConfigurationChanged(Configuration newConfig);

    void onBaseContextAttached(Context base);
}

但是再看ApplicationLike,發現是空實現。

@Override
public void onCreate() {
}
@Override
public void onLowMemory() {
}
......

這就只有一種解釋,這些回調方法是讓我們自定義的ApplicationLike去實現的。似乎能夠想到,Application是注解為我們自動生成的,我們不好去編寫Application中的代碼,而如果Application調用ApplicationLike的這些方法的話,我們去在ApplicationLike編寫代碼,就達到效果了。

3. TinkerApplicatoin

TinkerApplication自然是繼承了Application的。我們看一看Application回調代碼,果然驗證了之前的猜想。

3-1 猜想驗證

onCreate()中調用了applicationLike的onCreate(),其他的回調方法也是這樣,都調用了applicationLike的對應方法。這其實就是用到了代理模式。

@Override
public void onCreate() {
    // ......
    applicationLike.onCreate();
}
3-2 TinkerApplication # attachBaseContext()

天真的以為第一步去看onCreate()嗎,那就錯了,先看attachBaseContext(),這個方法在onCreate()前就會被調用,如果有需求需要將初始化工作更提前,就可以在這個方法中進行。在這里調用了onBaseContextAttached()。

@Override
protected void attachBaseContext(Context base) {
    super.attachBaseContext(base);
    Thread.setDefaultUncaughtExceptionHandler(new TinkerUncaughtHandler(this));
    onBaseContextAttached(base);
}

這里很關鍵,主要做了三個工作:

  1. 反射獲取TinkerLoader,反射拿到其tryLoad()并執行。
  2. 初始化ApplicationLike。
  3. 調用ApplicationLike對應方法。

這樣的調用順序,先tryLoad()再加載ApplicationLike,實際上就保證了Application也能被修復。但是這里說的并不是Application自身,而是它的代理ApplicationLike,如果將Application中的代碼完全交給代理AplicationLike,就能做到這一點了。

private void onBaseContextAttached(Context base) {
    // ......賦值時間相關成員變量
    
    // 加載TinkerLoad,執行tryLoad()
    loadTinker();
    
    // 加載applicationLike并賦值
    ensureDelegate();
    
    // 調用代理的onBaseContextAttached()
    applicationLike.onBaseContextAttached(base);
    
    // ......SharedPreference

}

見識少的我覺得這里的設計好棒。如果沒有ApplicationLike,那么就算tryLoad()再早,也早不過Application加載,那樣的話就不支持Application的修復了,而利用了代理模式,將代碼轉移到代理中,代理就能夠在Tinker之后加載,就可以解決這個問題了。

3-3 TinkerApplication # loadTinker()

再來具體的看一下這方法,就是反射去執行tryLoad()。

private void loadTinker() {
    //reflect tinker loader, because loaderClass may be define by user!
    Class<?> tinkerLoadClass = Class.forName(loaderClassName, false, getClassLoader());
    
    Method loadMethod = tinkerLoadClass.getMethod(TINKER_LOADER_METHOD, TinkerApplication.class);
    Constructor<?> constructor = tinkerLoadClass.getConstructor();
    
    tinkerResultIntent = (Intent) loadMethod.invoke(constructor.newInstance(), this);
}

tryLoad()又調用了tryLoadPatchFilesInternal(),又是一個好長的方法,但是大部分都是檢查判斷,最重要的部分如下代碼,去加載patch中的Dex文件和加載patch中的資源文件,這些內容我們在后序再開展。

private void tryLoadPatchFilesInternal(TinkerApplication app, Intent resultIntent) {

    // ......各種檢查和準備下面方法調用參數的工作
    
    //now we can load patch jar
    if (isEnabledForDex) {
        // 加載DEX文件
        boolean loadTinkerJars = TinkerDexLoader.loadTinkerJars(app, patchVersionDirectory, oatDex, resultIntent, isSystemOTA);
        // ......
        if (!loadTinkerJars) {
            Log.w(TAG, "tryLoadPatchFiles:onPatchLoadDexesFail");
            return;
        }
    }
    //now we can load patch resource
    if (isEnabledForResource) {
        // 加載資源文件
        boolean loadTinkerResources = TinkerResourceLoader.loadTinkerResources(app, patchVersionDirectory, resultIntent);
        if (!loadTinkerResources) {
            Log.w(TAG, "tryLoadPatchFiles:onPatchLoadResourcesFail");
            return;
        }
    }
    // 各種后續工作......
}
3-4 TinkerApplication # ensureDelegate()

在調用完tryLoad()后,通過反射拿到applicationLike對象并賦值全局變量。對象名delegateClassName是自動生成的application傳入的,就是我們加注解的類。

private synchronized void ensureDelegate() {
    if (applicationLike == null) {
        applicationLike = createDelegate();
    }
}

private ApplicationLike createDelegate() {
    try {
        // Use reflection to create the delegate so it doesn't need to go into the primary dex.
        // And we can also patch it
        Class<?> delegateClass = Class.forName(delegateClassName, false, getClassLoader());
        Constructor<?> constructor = delegateClass.getConstructor(Application.class, int.class, boolean.class,
            long.class, long.class, Intent.class);
        return (ApplicationLike) constructor.newInstance(this, tinkerFlags, tinkerLoadVerifyFlag,
            applicationStartElapsedTime, applicationStartMillisTime, tinkerResultIntent);
    } catch (Throwable e) {
        throw new TinkerRuntimeException("createDelegate failed", e);
    }
}

初始化applicationLike完畢,就接著調用其對應的回調。

applicationLike.onBaseContextAttached(base);

之后繼續,onCreate()......等等

合成與加載調用回顧

經過前兩篇的分析,大致調用執行流程已經清楚了,剩下就是真正的修復算法了,遺留了兩處,一處是我們自己調用api后調用到的UpgradePatch的tryPatch(),一處是app開始時調用的tryLoad()。這兩個地方就對應著patch合成和加載兩個過程。

分別簡單回顧一下這兩個過程:

a. 合成過程

  1. Tinker收到服務端獲取的patch文件。
  2. 開啟TinkerPatchService去執行UpgradePatch的tryPatch()。
  3. tryPatch()中會依次進行dex文件合成、.so文件合成和資源文件合成。
  4. 將合成后的新文件放在tinker加載的文件路徑中。

b. 加載過程

  1. TinkerApplication的onBaseContextAttached()中反射調用了TinkerLoader的tryLoad()。
  2. 之后分別調用loadTinkerJars()和loadTinkerResources()去加載dex文件和資源文件。

與AndFix的native層進行方法替換不同的是,Tinker采用的是Java層的實現。類加載過程中會去一個Element數組中尋找該類信息,Tinker的思路就是在該類被加載之前去將patch修復后的該類包裝到Element放到數組的前端,這樣在加載的時候,先找到的就是修復后的信息了,從而達到代替的效果。

具體加載合并算法實現部分由于我能力不夠就暫時放一放,但是找到了幾篇比較好的博客,我也有看,先記下來,以后再仔細研究研究。

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,401評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,011評論 3 413
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,263評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,543評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,323評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,874評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,968評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,095評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,605評論 1 331
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,551評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,720評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,242評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,961評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,358評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,612評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,330評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,690評論 2 370

推薦閱讀更多精彩內容