InstantRun從2.0到3.0微驶,歷史解毒

InstantRun從2.0到3.0哮翘,歷史解毒

前言

Instant Run已經(jīng)出來(lái)3年了奋献,為什么現(xiàn)在會(huì)想寫這篇文章乖订。從Instant Run 發(fā)布就已經(jīng)有文章做了詳細(xì)的介紹眠饮,但主要分為兩類:一類是講其主要實(shí)現(xiàn)原理或是講 Instant Run2.0中的 ApplicationClassLoader 的替換,另一類就是兩者結(jié)合。但是在Instant Run2.0 以后包括(2.3和3.0)雖然主要的實(shí)現(xiàn)原理沒(méi)做改變弛作,但都不再有 ApplicationClassLoader 的替換了涕蜂。

而恰恰我是從 Instant Run3.0 開始分析的,所以在讀完網(wǎng)上相關(guān)文章后感覺(jué)和自己實(shí)踐結(jié)果偏差映琳,使我產(chǎn)生一系列的為什么机隙。本文章就是認(rèn)真尋找其原理之后的產(chǎn)物。

文章會(huì)將相同的代碼分別在 gradle:2.0 ~ 2.3 ~ 3.0 上的運(yùn)行結(jié)果做對(duì)比萨西,以及從源碼的角度分析其結(jié)果的形成的原理有鹿。

關(guān)于 Instant Run

Android Studio 2.0 中引入的 Instant Run 是 RunDebug 命令的行為,可以大幅縮短應(yīng)用更新的時(shí)間谎脯。盡管首次構(gòu)建可能需要花費(fèi)較長(zhǎng)的時(shí)間葱跋,Instant Run 在向應(yīng)用推送后續(xù)更新時(shí)則無(wú)需構(gòu)建新的 APK,因此,這樣可以更快地看到更改年局。原文關(guān)于 Instant Run 际看。

相關(guān)概念

普通構(gòu)建整個(gè)apk → 部署appapp重啟 → 重啟Activity
InstantRun只構(gòu)建修改的部分 → 部署修改的dex或資源 → 部署(熱部署|溫部署|*冷部署)

  • 熱部署:更改現(xiàn)有方法的實(shí)現(xiàn)代碼,無(wú)需重啟app和Activity
  • 溫部署:app無(wú)需重啟矢否,但是activity需要重啟仲闽,更改或移除現(xiàn)有資源。
  • 冷部署:app需要重啟僵朗,結(jié)構(gòu)性的代碼更改赖欣。

相關(guān)代碼:

下邊為整個(gè)文章中所涉及的源代碼:

地址

源碼以及反編譯文件鏈接:https://github.com/stven0king/InstantRun-ApkParse.git

文件目錄

file:2.2.3//apk-debug2.2.3.apk反編譯源碼
file:2.3.0//apk-debug2.3.0.apk反編譯源碼
file:3.0.0//apk-debug3.0.0.apk反編譯源碼
file:reload0x0000//reload0x0000-dex2jar.jar反編譯源代碼
file:slice_4_2.2.3//apk-debug2.2.3.apk分裂出的slice_4_2.3.0.apk的反編譯文件
file:slice_6_3.0.0//apk-debug3.0.0.apk分裂出的split_lib_slice_6_apk的反編譯文件

Gradle2.2.3版本

環(huán)境

反編譯結(jié)果

bootstrapApplication.png

目錄結(jié)構(gòu)

解壓完apk之后,可以看到比以往多了一個(gè) instant-run.zip 的文件验庙。

 2.2.3-zip git:(master) ? unzip instant-run.zip -d ./instant-run
Archive:  instant-run.zip
  inflating: ./instant-run/com.android.support-support-v4-25.2.0_5d13af6de4318d640c0f4476df51368768e7d685-classes.dex
    inflating: ./instant-run/com.android.support-support-core-ui-25.2.0_ea8d7df920d33201f106dcb84c43d1eacaec7dd0-classes.dex
  inflating: ./instant-run/support-annotations-25.2.0_a33da78f501c6f0028ab51cf7a4a072129b233d3-classes.dex
  inflating: ./instant-run/com.android.support-support-vector-drawable-25.2.0_b68552cc8e884ffe451a9050485a027fab5221b6-classes.dex
  inflating: ./instant-run/com.android.support-support-compat-25.2.0_a30aa766fa9eb8931d774a9877ee62e7922594ea-classes.dex
  inflating: ./instant-run/com.android.support-support-core-utils-25.2.0_597f4e658a91af8509465a83255097ea293c4b0d-classes.dex
  inflating: ./instant-run/com.android.support-animated-vector-drawable-25.2.0_cee6b6e2c44697f52b0c7ae341d1e644be24f407-classes.dex
    inflating: ./instant-run/com.android.support-support-fragment-25.2.0_0b0115a9ee4c324b81c551aa7f986769013e71f8-classes.dex
      inflating: ./instant-run/com.android.support-support-media-compat-25.2.0_d4100160e6cd1e6586fd37853817402c8b1324dd-classes.dex
  inflating: ./instant-run/com.android.support-appcompat-v7-25.2.0_8ee0a5e1aed6951764492197789f674e0e0e9e51-classes.dex
  inflating: ./instant-run/slice_9-classes.dex
  inflating: ./instant-run/slice_8-classes.dex
  inflating: ./instant-run/slice_7-classes.dex
  inflating: ./instant-run/slice_6-classes.dex
  inflating: ./instant-run/slice_5-classes.dex
  inflating: ./instant-run/slice_4-classes.dex
  inflating: ./instant-run/slice_3-classes.dex
  inflating: ./instant-run/slice_2-classes.dex
  inflating: ./instant-run/slice_1-classes.dex
  inflating: ./instant-run/slice_0-classes.dex

instant-run.zip 文件解壓之后為一些support包和slice的dex文件顶吮。

整體的包結(jié)構(gòu)發(fā)生了一下變化:

com.android.
------.---------.tools.fd.
------.---------.-------.--.common.*
------.---------.-------.--.dummy.*
------.---------.-------.--.runtime.*
------.---------.-------.ir.api.Disable.InstantRun
------.tanzx.
------.-------.instantrun
------.-------.-------------.BuildConfig
------.-------.-------------.InstantRunActivity
------.-------.-------------.MyApplication
------.-------.-------------.R
------.-------.-------------.Utils

多出了一個(gè)com.android.tools.*的目錄結(jié)構(gòu),里面全部都是關(guān)于instant-run相關(guān)的代碼粪薛。

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="com.tanzx.instantrun" platformBuildVersionCode="25" platformBuildVersionName="7.1.1">
    <uses-sdk android:minSdkVersion="15" android:targetSdkVersion="25"/>
    <application android:theme="@style/AppTheme" 
        android:label="@string/app_name" 
        android:icon="@mipmap/ic_launcher" 
        android:name="com.android.tools.fd.runtime.BootstrapApplication" 
        android:debuggable="true" 
        android:allowBackup="true" 
        android:supportsRtl="true" 
        name="com.tanzx.instantrun.MyAppication">
        <activity android:name="com.tanzx.instantrun.InstantRunActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
    </application>
</manifest>

可以看到應(yīng)用程序的自定義 MyApplicationBootstrapApplication 替代了悴了。

java類的修改

public class InstantRunActivity extends AppCompatActivity {
    //給所有的類增加IncrementalChange類型的$change靜態(tài)變量
    public static volatile transient /* synthetic */ IncrementalChange $change;
    public static final long serialVersionUID = 0;
    private int num;
    private TextView textView;
    //增加類的構(gòu)造方法,方便于修改類的任何構(gòu)造方法
    InstantRunActivity(Object[] objArr, InstantReloadException instantReloadException) {
        switch (((String) objArr[1]).hashCode()) {
            case -2089128195:
                return;
            case 1403409127:
                this();
                return;
            default:
                throw new InstantReloadException(String.format("String switch could not find '%s' with hashcode %s in %s", new Object[]{(String) objArr[1], Integer.valueOf(((String) objArr[1]).hashCode()), "com/tanzx/instantrun/InstantRunActivity"}));
        }
    }
    //增加類的super方法調(diào)用违寿,方便于修改類的任何super方法
    public static /* synthetic */ Object access$super(InstantRunActivity instantRunActivity, String str, Object... objArr) {
        switch (str.hashCode()) {
            case -2147180915:
                super.onSaveInstanceState((Bundle) objArr[0]);
                return null;
            case -2146661417:
                super.showDialog(((Number) objArr[0]).intValue());
                return null;
            case -2128160755:
                return super.toString();
            /***各種父類方法的調(diào)用湃交,與-2147180915類似***/
            default:
                throw new InstantReloadException(String.format("String switch could not find '%s' with hashcode %s in %s", new Object[]{str, Integer.valueOf(str.hashCode()), "com/tanzx/instantrun/InstantRunActivity"}));
        }
    }

    public void onCreate(Bundle savedInstanceState) {
        IncrementalChange incrementalChange = $change;
        //如果$change變量不為null,則執(zhí)行$change中的對(duì)于的方法
        if (incrementalChange != null) {
            incrementalChange.access$dispatch("onCreate.(Landroid/os/Bundle;)V", this, savedInstanceState);
            return;
        }
        super.onCreate(savedInstanceState);
        setContentView((int) R.layout.activity_main);
        this.textView = (TextView) findViewById(R.id.text);
        this.textView.setBackgroundResource(R.drawable.bg);
        this.textView.setText(BootstrapApplication.LOG_TAG);
    }

    public InstantRunActivity() {
        IncrementalChange incrementalChange = $change;
        //如果$change變量不為null藤巢,則執(zhí)行$change中的對(duì)于的方法
        if (incrementalChange != null) {
            Object[] objArr = (Object[]) incrementalChange.access$dispatch("init$args.([Lcom/tanzx/instantrun/InstantRunActivity;[Ljava/lang/Object;)Ljava/lang/Object;", null, new Object[0]);
            Object[] objArr2 = (Object[]) objArr[0];
            this(objArr, null);
            objArr2[0] = this;
            incrementalChange.access$dispatch("init$body.(Lcom/tanzx/instantrun/InstantRunActivity;[Ljava/lang/Object;)V", objArr2);
        }
    }
}
public interface IncrementalChange {
    Object access$dispatch(String str, Object... objArr);
}

上面我只舉例了一個(gè)空的 Activity 的修改搞莺,Instant-run 實(shí)際會(huì)對(duì)每個(gè)類都進(jìn)行修改,包括匿名內(nèi)部類掂咒。

  • 增加一種構(gòu)造方法才沧,方便于替換(或者修改)任何構(gòu)造方法。
  • 重寫空構(gòu)造方法绍刮,方便于替換該方法的實(shí)現(xiàn)温圆。
  • 增加 access$super 方法,方便于替換(或者修改)任何父類的方法孩革。
  • 所有的類(包括匿名內(nèi)部類)都增加 public static volatile transient IncrementalChange $change; 成員變量岁歉。
  • 在類的所有實(shí)現(xiàn)的方法都先判斷 $change 是否為 null ,否則調(diào)用的是修復(fù)的類的方法嫉戚。

PS:具體修復(fù)的過(guò)程看 <u>handleHotSwapPatch</u> 部分。

從Application替換開始

AndroidManifest.xml 中我們看到了MyApplicationBootstrapApplication 替代澈圈,那么我們可以想象當(dāng) ApplicationInstant-run 自己的時(shí)彬檀,那么它至少可以像加載插件一樣在應(yīng)用啟動(dòng)的時(shí)候(程序入口)加載替換自己的dex和資源文件,從而達(dá)到修改運(yùn)行程序的目的瞬女。

接下來(lái)我們分析一下通過(guò)替換 Application 怎么加載自己的dex和資源文件窍帝。

BootstrapApplication

BootstrapApplication 重寫了兩個(gè) Application 方法,分別為 attachBaseContextonCreate 诽偷。

Context.jpg

Application 的基類為 ContextWrapper 坤学,其真真的 Context 實(shí)現(xiàn)類為 ContextImpl 疯坤。

attachBaseContext 方法就是將 ContextImplApplication 綁定在一起。

關(guān)于分裂a(bǔ)pk以及安裝參考:Android Studio 配置構(gòu)建變體

@Override
protected void attachBaseContext(Context context) {
    // As of Marshmallow, we use APK splits and don't need to rely on
    // reflection to inject classes and resources for coldswap
    //noinspection PointlessBooleanExpression
    //是否使用了apk分裂安裝
    if (!AppInfo.usingApkSplits) {
        String apkFile = context.getApplicationInfo().sourceDir;
        long apkModified = apkFile != null ? new File(apkFile).lastModified() : 0L;
        //判斷資源resource.ap_是否進(jìn)行了修改深浮,將其路徑保存在externalResourcePath
        createResources(apkModified);
        //創(chuàng)建classloade
        //delegateClassLoader->PathClassLoader->IncrementalClassLoader->BootClassLoader
        setupClassLoaders(context, context.getCacheDir().getPath(), apkModified);
    }
    //創(chuàng)建正真的Application
    createRealApplication();

    // This is called from ActivityThread#handleBindApplication() -> LoadedApk#makeApplication().
    // Application#mApplication is changed right after this call, so we cannot do the monkey
    // patching here. So just forward this method to the real Application instance.
    super.attachBaseContext(context);

    if (realApplication != null) {
        try {
            Method attachBaseContext =
                    ContextWrapper.class.getDeclaredMethod("attachBaseContext", Context.class);
            attachBaseContext.setAccessible(true);
            //執(zhí)行自己的Application的attachBaseContext方法
            attachBaseContext.invoke(realApplication, context);
        } catch (Exception e) {
            throw new IllegalStateException(e);
        }
    }
}

該方法的主要目的在于压怠,創(chuàng)建自定義的 ClassLoader 和真真的 Application 實(shí)例。而 BootstrapApplication 只起到一個(gè)殼子的作用飞苇。

@Override
    public void onCreate() {
        // As of Marshmallow, we use APK splits and don't need to rely on
        // reflection to inject classes and resources for coldswap
        //noinspection PointlessBooleanExpression
        if (!AppInfo.usingApkSplits) {
            //將BootstartApplication替換為realApplication
            MonkeyPatcher.monkeyPatchApplication(
                    BootstrapApplication.this, BootstrapApplication.this,
                    realApplication, externalResourcePath);
            MonkeyPatcher.monkeyPatchExistingResources(BootstrapApplication.this,
                    externalResourcePath, null);
        } else {
            // We still need to set the application instance in the LoadedApk etc
            // such that getApplication() returns the new application
            MonkeyPatcher.monkeyPatchApplication(
                    BootstrapApplication.this, BootstrapApplication.this,
                    realApplication, null);
        }
        super.onCreate();

        // Start server, unless we're in a multiprocess scenario and this isn't the
        // primary process
        if (AppInfo.applicationId != null) {
            try {
                boolean foundPackage = false;
                int pid = Process.myPid();
                ActivityManager manager = (ActivityManager) getSystemService(
                        Context.ACTIVITY_SERVICE);
                List<RunningAppProcessInfo> processes = manager.getRunningAppProcesses();

                boolean startServer;
                if (processes != null && processes.size() > 1) {
                    // Multiple processes: look at each, and if the process name matches
                    // the package name (for the current pid), it's the main process.
                    startServer = false;
                    for (RunningAppProcessInfo processInfo : processes) {
                        if (AppInfo.applicationId.equals(processInfo.processName)) {
                            foundPackage = true;
                            if (processInfo.pid == pid) {
                                startServer = true;
                                break;
                            }
                        }
                    }
                    if (!startServer && !foundPackage) {
                        // Safety check: If for some reason we didn't even find the main package,
                        // start the server anyway. This safeguards against apps doing strange
                        // things with the process name.
                        startServer = true;
                        if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
                            Log.v(LOG_TAG, "Multiprocess but didn't find process with package: "
                                    + "starting server anyway");
                        }
                    }
                } else {
                    // If there is only one process, start the server.
                    startServer = true;
                }
                //開啟sokcet監(jiān)聽(tīng)
                if (startServer) {
                    Server.create(AppInfo.applicationId, BootstrapApplication.this);
                }
            } catch (Throwable t) {
                if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
                    Log.v(LOG_TAG, "Failed during multi process check", t);
                }
                Server.create(AppInfo.applicationId, BootstrapApplication.this);
            }
        }
        //調(diào)用真正的Application的onCreate方法
        if (realApplication != null) {
            realApplication.onCreate();
        }
    }

該方法的主要功能為菌瘫,將運(yùn)行時(shí)環(huán)境中的Application都替換為 realApplication 。然后開啟 Socket 監(jiān)聽(tīng) AndroidStudio 是否有新的修改push給應(yīng)用程序布卡。

創(chuàng)建資源文件
private void createResources(long apkModified) {
    // Look for changes stashed in the inbox folder while the server was not running
    //校驗(yàn)是否有新的資源文件存儲(chǔ)在inbox中
    FileManager.checkInbox();

    File file = FileManager.getExternalResourceFile();
    //外部導(dǎo)入的資源文件路徑
    externalResourcePath = file != null ? file.getPath() : null;

    if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
        Log.v(LOG_TAG, "Resource override is " + externalResourcePath);
    }

    if (file != null) {
        try {
            //獲取文件的修改時(shí)間
            long resourceModified = file.lastModified();
            if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
                Log.v(LOG_TAG, "Resource patch last modified: " + resourceModified);
                Log.v(LOG_TAG, "APK last modified: " + apkModified + " " +
                        (apkModified > resourceModified ? ">" : "<") + " resource patch");
            }
            //如果沒(méi)有修改雨让,或者修改時(shí)間不大于apk的修改時(shí)間,那么相當(dāng)于沒(méi)有改變資源
            if (apkModified == 0L || resourceModified <= apkModified) {
                if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
                    Log.v(LOG_TAG, "Ignoring resource file, older than APK");
                }
                externalResourcePath = null;
            }
        } catch (Throwable t) {
            Log.e(LOG_TAG, "Failed to check patch timestamps", t);
        }
    }
}
創(chuàng)建ClassLoader

關(guān)于JVM的ClassLoader我們都是知道只在雙親委派機(jī)制的基礎(chǔ)上實(shí)現(xiàn)的忿等,那么 instant-run 也正是利用這一特點(diǎn)來(lái)創(chuàng)建加載自己指定的 dexClassLoader 栖忠。PS:忘掉的同學(xué)可以參數(shù)文章: 自定義ClassLoader和雙親委派機(jī)制

private static void setupClassLoaders(Context context, String codeCacheDir, long apkModified) {
    //目錄"/data/data/" + applicationId + "/files/instant-run/dex"下的文件列表
    List<String> dexList = FileManager.getDexList(context, apkModified);

    // Make sure class loader finds these
    @SuppressWarnings("unused") Class<Server> server = Server.class;
    @SuppressWarnings("unused") Class<MonkeyPatcher> patcher = MonkeyPatcher.class;

    if (!dexList.isEmpty()) {
        if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
            Log.v(LOG_TAG, "Bootstrapping class loader with dex list " + join('\n', dexList));
        }
        //PathClassLoader
        ClassLoader classLoader = BootstrapApplication.class.getClassLoader();
        String nativeLibraryPath;
        try {
            nativeLibraryPath = (String) classLoader.getClass().getMethod("getLdLibraryPath")
                            .invoke(classLoader);
            if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
                Log.v(LOG_TAG, "Native library path: " + nativeLibraryPath);
            }
        } catch (Throwable t) {
            Log.e(LOG_TAG, "Failed to determine native library path " + t.getMessage());
            //目錄"/data/data/" + applicationId + "/lib"
            nativeLibraryPath = FileManager.getNativeLibraryFolder().getPath();
        }
        IncrementalClassLoader.inject(
                classLoader,
                nativeLibraryPath,
                codeCacheDir,
                dexList);
    }
}

可以看到主要是獲取 classloader 贸街、 nativeLibraryPathdexList 再調(diào)用 IncrementalClassLoader.inject 方法庵寞。

我們接下來(lái)看創(chuàng)建的 IncrementalClassLoader :

public class IncrementalClassLoader extends ClassLoader {
    public static final boolean DEBUG_CLASS_LOADING = false;
    private final DelegateClassLoader delegateClassLoader;

    private static class DelegateClassLoader extends BaseDexClassLoader {
        private DelegateClassLoader(String dexPath, File optimizedDirectory, String libraryPath, ClassLoader parent) {
            super(dexPath, optimizedDirectory, libraryPath, parent);
        }

        public Class<?> findClass(String name) throws ClassNotFoundException {
            try {
                return super.findClass(name);
            } catch (ClassNotFoundException e) {
                throw e;
            }
        }
    }

    public IncrementalClassLoader(ClassLoader original, String nativeLibraryPath, String codeCacheDir, List<String> dexes) {
        //設(shè)置為original的parent為IncrementalClassLoader的parent
        //即:IncrementalClassLoader為BootClassLoader
        super(original.getParent());
        //delegateClassLoader的parent為PathClassLoader
        this.delegateClassLoader = createDelegateClassLoader(nativeLibraryPath, codeCacheDir, dexes, original);
    }

    public Class<?> findClass(String className) throws ClassNotFoundException {
        try {
            return this.delegateClassLoader.findClass(className);
        } catch (ClassNotFoundException e) {
            throw e;
        }
    }
    /***部分代碼省略***/
    public static ClassLoader inject(ClassLoader classLoader, String nativeLibraryPath, String codeCacheDir, List<String> dexes) {
        IncrementalClassLoader incrementalClassLoader = new IncrementalClassLoader(classLoader, nativeLibraryPath, codeCacheDir, dexes);
        //設(shè)置incrementalClassLoader為PathClassLoader的parent
        setParent(classLoader, incrementalClassLoader);
        return incrementalClassLoader;
    }
}

IncrementalClassLoader 繼承 ClassLoader ,但從其 findClass 的實(shí)現(xiàn)看出 IncrementalClassLoader 在進(jìn)行 class 的加載時(shí)用的是內(nèi)部類 DelegateClassLoader 匾浪。同時(shí) IncrementalClassLoader 又作為 PathClassLoader 的parent皇帮。

DelegateClassLoader 繼承 BaseDexClassLoader 可以加載自定義路徑下的 dexjar 包中的 class 。PS:AndroidDexClassLoader相關(guān)文章可以參考:Android類加載之PathClassLoader和DexClassLoader

classloader.png

如上圖所示:整個(gè)類加載機(jī)制建造起來(lái)之后 DelegateClassLoader 為加載應(yīng)用程序的 dex 的類加載器蛋辈。

創(chuàng)建真正的Application

Instant-runAppinfo.java 文件為對(duì)Application的配置信息属拾。

public class AppInfo {
    public static String applicationClass = "com.tanzx.instantrun.MyAppication";
    public static String applicationId = BuildConfig.APPLICATION_ID;
    public static long token = -4151428381996933796L;
    public static boolean usingApkSplits = false;
}

再看看 BootstrapApplication 中真正的Application的創(chuàng)建:

 private void createRealApplication() {
    //是否有應(yīng)用自己的Application
    if (AppInfo.applicationClass != null) {
        if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
            Log.v(LOG_TAG, "About to create real application of class name = " +
                    AppInfo.applicationClass);
        }

        try {
            @SuppressWarnings("unchecked")
            Class<? extends Application> realClass =
                    (Class<? extends Application>) Class.forName(AppInfo.applicationClass);
            if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
                Log.v(LOG_TAG, "Created delegate app class successfully : " + realClass +
                        " with class loader " + realClass.getClassLoader());
            }
            Constructor<? extends Application> constructor = realClass.getConstructor();
            //自定的Application實(shí)例
            realApplication = constructor.newInstance();
            if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) {
                Log.v(LOG_TAG, "Created real app instance successfully :" + realApplication);
            }
        } catch (Exception e) {
            throw new IllegalStateException(e);
        }
    } else {
        //沒(méi)有自定義,則創(chuàng)建原生的Application
        realApplication = new Application();
    }
}
替換Application
public class MonkeyPatcher {
    public static void monkeyPatchApplication(Context context, Application bootstrap, Application realApplication, String externalResourceFile) {
        Class<?> activityThread;
        Class<?> loadedApkClass;
        try {
            //獲取ActivityThread實(shí)例
            activityThread = Class.forName("android.app.ActivityThread");
            Object currentActivityThread = getActivityThread(context, activityThread);
            Field mInitialApplication = activityThread.getDeclaredField("mInitialApplication");
            mInitialApplication.setAccessible(true);
            //替換ActivityThread的mInitialApplication成員變量
            Application initialApplication = (Application) mInitialApplication.get(currentActivityThread);
            if (realApplication != null && initialApplication == bootstrap) {
                mInitialApplication.set(currentActivityThread, realApplication);
            }
            //替換ActivityThread的mAllApplications隊(duì)列中的BootstrapApplication為realApplication
            if (realApplication != null) {
                Field mAllApplications = activityThread.getDeclaredField("mAllApplications");
                mAllApplications.setAccessible(true);
                List<Application> allApplications = (List) mAllApplications.get(currentActivityThread);
                for (int i = 0; i < allApplications.size(); i++) {
                    if (allApplications.get(i) == bootstrap) {
                        allApplications.set(i, realApplication);
                    }
                }
            }
            loadedApkClass = Class.forName("android.app.LoadedApk");
        } catch (ClassNotFoundException e) {
            loadedApkClass = Class.forName("android.app.ActivityThread$PackageInfo");
        } catch (Throwable e2) {
            IllegalStateException illegalStateException = new IllegalStateException(e2);
        }
        Field mApplication = loadedApkClass.getDeclaredField("mApplication");
        mApplication.setAccessible(true);
        Field mResDir = loadedApkClass.getDeclaredField("mResDir");
        mResDir.setAccessible(true);
        Field mLoadedApk = null;
        try {
            mLoadedApk = Application.class.getDeclaredField("mLoadedApk");
        } catch (NoSuchFieldException e3) {
        }
        //final ArrayMap<String, WeakReference<LoadedApk>> mPackages
        //final ArrayMap<String, WeakReference<LoadedApk>> mResourcePackages
        for (String fieldName : new String[]{"mPackages", "mResourcePackages"}) {
            Field field = activityThread.getDeclaredField(fieldName);
            field.setAccessible(true);
            for (Entry<String, WeakReference<?>> entry : ((Map) field.get(currentActivityThread)).entrySet()) {
                Object loadedApk = ((WeakReference) entry.getValue()).get();
                if (loadedApk != null && mApplication.get(loadedApk) == bootstrap) {
                    //將ActivityThread的mPackages|mResourcePackages的LoadedApk
                    //LoadedApk的成員變量mApplication的BootstrapApplication替換為realApplication
                    if (realApplication != null) {
                        mApplication.set(loadedApk, realApplication);
                    }
                    //LoadedApk的成員變量mResDir替換為externalResourceFile
                    if (externalResourceFile != null) {
                        mResDir.set(loadedApk, externalResourceFile);
                    }
                    //將realApplication的mLoadedApk替換為BootstrapApplication的mLoadedApk
                    if (!(realApplication == null || mLoadedApk == null)) {
                        mLoadedApk.set(realApplication, loadedApk);
                    }
                }
            }
        }
    }

    public static Object getActivityThread(Context context, Class<?> activityThread) {
        if (activityThread == null) {
            try {
                activityThread = Class.forName("android.app.ActivityThread");
            } catch (Throwable th) {
                return null;
            }
        }
        //獲取ActivityThread的靜態(tài)變量sCurrentActivityThread
        Method m = activityThread.getMethod("currentActivityThread", new Class[0]);
        m.setAccessible(true);
        Object currentActivityThread = m.invoke(null, new Object[0]);
        if (currentActivityThread != null || context == null) {
            return currentActivityThread;
        }
        //獲取BootstrapApplication的mLoadedApk
        Field mLoadedApk = context.getClass().getField("mLoadedApk");
        mLoadedApk.setAccessible(true);
        Object apk = mLoadedApk.get(context);
        Field mActivityThreadField = apk.getClass().getDeclaredField("mActivityThread");
        mActivityThreadField.setAccessible(true);
        //返回mLoadedApk的成員變量mActivityThread
        return mActivityThreadField.get(apk);
    }
    /***部分代碼省略***/
}
  • 獲取ActivityThread實(shí)例
    • 先獲取ActivityThread的靜態(tài)變量sCurrentActivityThread冷溶;
    • 否則獲取Application對(duì)象的成員變mLoadedApk的成員對(duì)象mActivityThread渐白;
  • 替換ActivityThread的mInitialApplication為realApplication
  • 替換ActivityThread的mAllApplications中的所有的BootstrapApplication為realApplication
  • 替換ActivityThread的mPackages,mResourcePackages中的mLoaderApk中的application為realApplication。
  • 替換realApplication中的mLoadedApk為BootstrapApplication的MLoadedApk
替換資源文件
public static void monkeyPatchExistingResources(Context context, String externalResourceFile, Collection<Activity> activities) {
    Field mResourcesImpl;
    Object resourceImpl;
    Field implAssets;
    if (externalResourceFile != null) {
        //構(gòu)造自己的AssetManager
        AssetManager newAssetManager = (AssetManager) AssetManager.class.getConstructor(new Class[0]).newInstance(new Object[0]);
        Method mAddAssetPath = AssetManager.class.getDeclaredMethod("addAssetPath", new Class[]{String.class});
        mAddAssetPath.setAccessible(true);
        //將當(dāng)前的資源文件路徑添加到AssetManager中
        if (((Integer) mAddAssetPath.invoke(newAssetManager, new Object[]{externalResourceFile})).intValue() == 0) {
            throw new IllegalStateException("Could not create new AssetManager");
        }
        Resources resources;
        Field mAssets;
        Method mEnsureStringBlocks = AssetManager.class.getDeclaredMethod("ensureStringBlocks", new Class[0]);
        mEnsureStringBlocks.setAccessible(true);
        //進(jìn)行資源初始化StringBlock對(duì)象
        mEnsureStringBlocks.invoke(newAssetManager, new Object[0]);
        if (activities != null) {
            /***部分代碼省略***/
        }
        Field fMActiveResources;
        Collection<WeakReference<Resources>> references;
        //獲取當(dāng)前JVM中的ResourcesManager的final ArrayMap<ResourcesKey, WeakReference<Resources> > mActiveResources
        if (VERSION.SDK_INT >= 19) {
            Class<?> resourcesManagerClass = Class.forName("android.app.ResourcesManager");
            Method mGetInstance = resourcesManagerClass.getDeclaredMethod("getInstance", new Class[0]);
            mGetInstance.setAccessible(true);
            Object resourcesManager = mGetInstance.invoke(null, new Object[0]);
            try {
                fMActiveResources = resourcesManagerClass.getDeclaredField("mActiveResources");
                fMActiveResources.setAccessible(true);
                references = ((ArrayMap) fMActiveResources.get(resourcesManager)).values();
            } catch (NoSuchFieldException e4) {
                Field mResourceReferences = resourcesManagerClass.getDeclaredField("mResourceReferences");
                mResourceReferences.setAccessible(true);
                references = (Collection) mResourceReferences.get(resourcesManager);
            }
        } else {
            Class<?> activityThread = Class.forName("android.app.ActivityThread");
            fMActiveResources = activityThread.getDeclaredField("mActiveResources");
            fMActiveResources.setAccessible(true);
            references = ((HashMap) fMActiveResources.get(getActivityThread(context, activityThread))).values();
        }
        //循環(huán)便利當(dāng)前Resources逞频,將其成員變量mAssets指向自定義的newAssetManager
        for (WeakReference<Resources> wr : references) {
            resources = (Resources) wr.get();
            if (resources != null) {
                try {
                    mAssets = Resources.class.getDeclaredField("mAssets");
                    mAssets.setAccessible(true);
                    mAssets.set(resources, newAssetManager);
                } catch (Throwable th) {
                    mResourcesImpl = Resources.class.getDeclaredField("mResourcesImpl");
                    mResourcesImpl.setAccessible(true);
                    resourceImpl = mResourcesImpl.get(resources);
                    implAssets = resourceImpl.getClass().getDeclaredField("mAssets");
                    implAssets.setAccessible(true);
                    implAssets.set(resourceImpl, newAssetManager);
                }
                //更新資源
                resources.updateConfiguration(resources.getConfiguration(), resources.getDisplayMetrics());
            }
        }
    }
}

Socket監(jiān)聽(tīng)

執(zhí)行完 Application.onCreate 纯衍,開啟Socket監(jiān)聽(tīng)。

public static void create(String packageName, Application application) {
    Server server = new Server(packageName, application);
}
run

我們分析Socket線程的主要的run方法:

private class SocketServerReplyThread extends Thread {
    private final LocalSocket mSocket;

    SocketServerReplyThread(LocalSocket socket) {
        this.mSocket = socket;
    }

    public void run() {
        DataInputStream input;
        DataOutputStream output;
        try {
            input = new DataInputStream(this.mSocket.getInputStream());
            output = new DataOutputStream(this.mSocket.getOutputStream());
            //分發(fā)消息
            handle(input, output);
            try {
                input.close();
            } catch (IOException e) {
            }
            try {
                output.close();
            } catch (IOException e2) {
            }
        } catch (IOException e3) {
            if (Log.isLoggable(BootstrapApplication.LOG_TAG, 2)) {
                Log.v(BootstrapApplication.LOG_TAG, "Fatal error receiving messages", e3);
            }
        } catch (Throwable th) {
            try {
                input.close();
            } catch (IOException e4) {
            }
            try {
                output.close();
            } catch (IOException e5) {
            }
        }
    }
    /***部分代碼省略***/
}
handle

根據(jù)不同的version苗胀,進(jìn)行不同類型的消息處理:

private void handle(DataInputStream input, DataOutputStream output) throws IOException {
    long magic = input.readLong();
    if (magic != ProtocolConstants.PROTOCOL_IDENTIFIER) {
        Log.w(BootstrapApplication.LOG_TAG, "Unrecognized header format " + Long.toHexString(magic));
        return;
    }
    //讀取消息類型
    int version = input.readInt();
    output.writeInt(4);
    if (version != 4) {
        Log.w(BootstrapApplication.LOG_TAG, "Mismatched protocol versions; app is using version 4 and tool is using version " + version);
        return;
    }
    while (true) {
        int message = input.readInt();
        String path;
        switch (message) {
            case 1:
                if (authenticate(input)) {
                    List<ApplicationPatch> changes = ApplicationPatch.read(input);
                    if (changes == null) {
                        break;
                    }
                    boolean hasResources = Server.hasResources(changes);
                    int updateMode = Server.this.handlePatches(changes, hasResources, input.readInt());
                    boolean showToast = input.readBoolean();
                    output.writeBoolean(true);
                    Server.this.restart(updateMode, hasResources, showToast);
                    break;
                }
                return;
            /***部分代碼省略***/
            case 7://消息結(jié)束發(fā)送
                if (Log.isLoggable(BootstrapApplication.LOG_TAG, 2)) {
                    Log.v(BootstrapApplication.LOG_TAG, "Received EOF from the IDE");
                    return;
                }
                return;
            default:
                if (Log.isLoggable(BootstrapApplication.LOG_TAG, 6)) {
                    Log.e(BootstrapApplication.LOG_TAG, "Unexpected message type: " + message);
                    return;
                }
                return;
        }
    }
}
//判斷是否為當(dāng)前Application發(fā)送的信息
private boolean authenticate(DataInputStream input) throws IOException {
    long token = input.readLong();
    if (token == AppInfo.token) {
        return true;
    }
    Log.w(BootstrapApplication.LOG_TAG, "Mismatched identity token from client; received " + token + " and expected " + AppInfo.token);
    Server.access$208();
    return false;
}

處理服務(wù)端(AndroidStudio-IDE)消息

handlePatches

根據(jù)發(fā)送過(guò)來(lái)的文件進(jìn)行不同的操作:

private int handlePatches(List<ApplicationPatch> changes, boolean hasResources, int updateMode) {
    if (hasResources) {
        FileManager.startUpdate();
    }
    for (ApplicationPatch change : changes) {
        String path = change.getPath();
        //文件以“.dex”結(jié)尾
        if (path.endsWith(FileManager.CLASSES_DEX_SUFFIX)) {
            //冷交換
            handleColdSwapPatch(change);
            boolean canHotSwap = false;
            for (ApplicationPatch c : changes) {
                if (c.getPath().equals(Paths.RELOAD_DEX_FILE_NAME)) {
                    canHotSwap = true;
                    break;
                }
            }
            if (!canHotSwap) {
                updateMode = 3;
            }
        } else if (path.equals(Paths.RELOAD_DEX_FILE_NAME)) {//文件名為“classes.dex.3”
            //熱交換
            updateMode = handleHotSwapPatch(updateMode, change);
        } else if (isResourcePath(path)) {
            //資源交換(溫部署)
            updateMode = handleResourcePatch(updateMode, change, path);
        }
    }
    if (hasResources) {
        FileManager.finishUpdate(true);
    }
    return updateMode;
}
private static boolean isResourcePath(String path) {
    //判斷文件是否為“resources.ap_”襟诸,或者文件以“res/"開頭
    return path.equals(Paths.RESOURCE_FILE_NAME) || path.startsWith("res/");
}

handleColdSwapPatch

private static void handleColdSwapPatch(ApplicationPatch patch) {
    //如果文件地址以”slice-“開頭
    //那么將其寫入到”data/data/applicationid/files/instant-run/dex“目錄下
    if (patch.path.startsWith(Paths.DEX_SLICE_PREFIX)) {
        File file = FileManager.writeDexShard(patch.getBytes(), patch.path);
        if (Log.isLoggable(BootstrapApplication.LOG_TAG, 2)) {
            Log.v(BootstrapApplication.LOG_TAG, "Received dex shard " + file);
        }
    }
}
public class FileManager {
    public static final String CLASSES_DEX_SUFFIX = ".dex";
    private static final String FILE_NAME_ACTIVE = "active";
    private static final String FOLDER_NAME_LEFT = "left";
    private static final String FOLDER_NAME_RIGHT = "right";
    private static final String RELOAD_DEX_PREFIX = "reload";
    private static final String RESOURCE_FILE_NAME = "resources.ap_";
    private static final String RESOURCE_FOLDER_NAME = "resources";
    /***部分代碼省略***/
    public static File writeDexShard(byte[] bytes, String name) {
        //創(chuàng)建或獲取“data/data/applicationid/files/instant-run/dex”文件
        File dexFolder = getDexFileFolder(getDataFolder(), true);
        if (dexFolder == null) {
            return null;
        }
        File file = new File(dexFolder, name);
        writeRawBytes(file, bytes);
        return file;
    }
    /***部分代碼省略***/
    private static File getDexFileFolder(File base, boolean createIfNecessary) {
        File file = new File(base, Paths.DEX_DIRECTORY_NAME);
        if (!createIfNecessary || file.isDirectory() || file.mkdirs()) {
            return file;
        }
        Log.e(BootstrapApplication.LOG_TAG, "Failed to create directory " + file);
        return null;
    }
    private static File getDataFolder() {
        //“data/data/applicationid/files/instant-run”
        return new File(Paths.getDataDirectory(AppInfo.applicationId));
    }
}
slice-.png

當(dāng)然在替換完dex之后,應(yīng)用就會(huì)在重啟后加載新的dex基协。

handleHotSwapPatch

private int handleHotSwapPatch(int updateMode, ApplicationPatch patch) {
    if (Log.isLoggable(BootstrapApplication.LOG_TAG, 2)) {
        Log.v(BootstrapApplication.LOG_TAG, "Received incremental code patch");
    }
    try {
        //創(chuàng)建data/data/applicationid/files/instant-run/dex-temp/reloadxxxx.dex文件
        String dexFile = FileManager.writeTempDexFile(patch.getBytes());
        if (dexFile == null) {
            Log.e(BootstrapApplication.LOG_TAG, "No file to write the code to");
            return updateMode;
        }
        if (Log.isLoggable(BootstrapApplication.LOG_TAG, 2)) {
            Log.v(BootstrapApplication.LOG_TAG, "Reading live code from " + dexFile);
        }
        //反射構(gòu)造AppPatchesLoaderImpl實(shí)例
        Class<?> aClass = Class.forName("com.android.tools.fd.runtime.AppPatchesLoaderImpl", true, new DexClassLoader(dexFile, this.mApplication.getCacheDir().getPath(), FileManager.getNativeLibraryFolder().getPath(), getClass().getClassLoader()));
        if (Log.isLoggable(BootstrapApplication.LOG_TAG, 2)) {
            Log.v(BootstrapApplication.LOG_TAG, "Got the patcher class " + aClass);
        }
        PatchesLoader loader = (PatchesLoader) aClass.newInstance();
        if (Log.isLoggable(BootstrapApplication.LOG_TAG, 2)) {
            Log.v(BootstrapApplication.LOG_TAG, "Got the patcher instance " + loader);
        }
        //獲取熱修復(fù)所要替換的類的classname
        String[] getPatchedClasses = (String[]) aClass.getDeclaredMethod("getPatchedClasses", new Class[0]).invoke(loader, new Object[0]);
        if (Log.isLoggable(BootstrapApplication.LOG_TAG, 2)) {
            Log.v(BootstrapApplication.LOG_TAG, "Got the list of classes ");
            for (String getPatchedClass : getPatchedClasses) {
                Log.v(BootstrapApplication.LOG_TAG, "class " + getPatchedClass);
            }
        }
        //執(zhí)行AppPatchesLoaderImpl的load方法進(jìn)行類修復(fù)
        if (!loader.load()) {
            updateMode = 3;
        }
        return updateMode;
    } catch (Exception e) {
        Log.e(BootstrapApplication.LOG_TAG, "Couldn't apply code changes", e);
        e.printStackTrace();
        updateMode = 3;
    } catch (Throwable e2) {
        Log.e(BootstrapApplication.LOG_TAG, "Couldn't apply code changes", e2);
        updateMode = 3;
    }
}

public class FileManager {
    public static final String CLASSES_DEX_SUFFIX = ".dex";
    private static final String FILE_NAME_ACTIVE = "active";
    private static final String FOLDER_NAME_LEFT = "left";
    private static final String FOLDER_NAME_RIGHT = "right";
    private static final String RELOAD_DEX_PREFIX = "reload";
    private static final String RESOURCE_FILE_NAME = "resources.ap_";
    private static final String RESOURCE_FOLDER_NAME = "resources";
    /***部分代碼省略***/
    public static String writeTempDexFile(byte[] bytes) {
        File file = getTempDexFile();
        if (file != null) {
            writeRawBytes(file, bytes);
            return file.getPath();
        }
        Log.e(BootstrapApplication.LOG_TAG, "No file to write temp dex content to");
        return null;
    }
    /***部分代碼省略***/
    public static File getTempDexFile() {
        //“data/data/applicationid/files/instant-run”
        File dataFolder = getDataFolder();
        //“data/data/applicationid/files/instant-run/dex-temp”
        File dexFolder = getTempDexFileFolder(dataFolder);
        if (dexFolder.exists()) {
            if (!sHavePurgedTempDexFolder) {
                //刪除之前的
                purgeTempDexFiles(dataFolder);
            }
        } else if (dexFolder.mkdirs()) {
            sHavePurgedTempDexFolder = true;
        } else {
            Log.e(BootstrapApplication.LOG_TAG, "Failed to create directory " + dexFolder);
            return null;
        }
        File[] files = dexFolder.listFiles();
        int max = -1;
        if (files != null) {
            for (File file : files) {
                String name = file.getName();
                //文件以“reload"開頭歌亲,以".dex"結(jié)尾
                if (name.startsWith(RELOAD_DEX_PREFIX) && name.endsWith(CLASSES_DEX_SUFFIX)) {
                    try {
                        //獲取中間版本,取最高版本
                        int version = Integer.decode(name.substring(RELOAD_DEX_PREFIX.length(), name.length() - CLASSES_DEX_SUFFIX.length())).intValue();
                        if (version > max) {
                            max = version;
                        }
                    } catch (NumberFormatException e) {
                    }
                }
            }
        }
        //創(chuàng)建版本號(hào)+1的reloadxxx.dex文件
        File file2 = new File(dexFolder, String.format("%s0x%04x%s", new Object[]{RELOAD_DEX_PREFIX, Integer.valueOf(max + 1), CLASSES_DEX_SUFFIX}));
        if (!Log.isLoggable(BootstrapApplication.LOG_TAG, 2)) {
            return file2;
        }
        Log.v(BootstrapApplication.LOG_TAG, "Writing new dex file: " + file2);
        return file2;
    }
}
reloadxxx.dex.png
reload0x0000-dex2jar.png

我們?cè)趯懭胪?reload0x0000.dex 文件后澜驮,執(zhí)行 AppPatchesLoaderImplload 方法陷揪。

public abstract class AbstractPatchesLoaderImpl implements PatchesLoader {
    public abstract String[] getPatchedClasses();

    public boolean load() {
        try {
            //調(diào)用getPatchedClasses,獲取需要修復(fù)的classname
            for (String className : getPatchedClasses()) {
                //反射機(jī)制,構(gòu)造出用來(lái)修復(fù)的類悍缠,類為classname+$override
                ClassLoader cl = getClass().getClassLoader();
                Object o = cl.loadClass(className + "$override").newInstance();
                //讀取需要修復(fù)的類的$change字段
                Field changeField = cl.loadClass(className).getDeclaredField("$change");
                changeField.setAccessible(true);
                Object previous = changeField.get(null);
                if (previous != null) {
                    Field isObsolete = previous.getClass().getDeclaredField("$obsolete");
                    if (isObsolete != null) {
                        isObsolete.set(null, Boolean.valueOf(true));
                    }
                }
                //將構(gòu)造出用來(lái)修復(fù)的類(classname$override),賦值給需要修復(fù)類的$change成員變量
                changeField.set(null, o);
                if (Log.logging != null && Log.logging.isLoggable(Level.FINE)) {
                    Log.logging.log(Level.FINE, String.format("patched %s", new Object[]{className}));
                }
            }
            return true;
        } catch (Exception e) {
            if (Log.logging != null) {
                Log.logging.log(Level.SEVERE, String.format("Exception while patching %s", new Object[]{"foo.bar"}), e);
            }
            return false;
        }
    }
}

在前面 <u>反編譯結(jié)果-java類的修改</u> 部分中我們講到了每個(gè)類中都有的 IncrementalChange 類型的 $change 字段卦绣。

在第一次運(yùn)行的時(shí)候 $change 的值都是 null

先用 InstantRunActivity$1$override 來(lái)舉個(gè)例子飞蚓, InstantRunActivity$1$overrideActivity 中的一個(gè) Button 的點(diǎn)擊事件處理類滤港,即:View.OnClickListener

public class InstantRunActivity$1$override
  implements IncrementalChange{
  public static Object init$args(InstantRunActivity.1[] paramArrayOf1, InstantRunActivity paramInstantRunActivity, Object[] paramArrayOfObject){
    return new Object[] { { paramArrayOf1, paramInstantRunActivity, new Object[0] }, "java/lang/Object.()V" };
  }
  
  public static void init$body(InstantRunActivity.1 param1, InstantRunActivity paramInstantRunActivity, Object[] paramArrayOfObject) {}
  //第一個(gè)參數(shù)為OnClickListern持有的Activity的成員變量
  public static void onClick(InstantRunActivity.1 param1, View paramView){
    Log.d("xxxxxx", "" + InstantRunActivity.access$008((InstantRunActivity)AndroidInstantRuntime.getPrivateField(param1, InstantRunActivity.1.class, "this$0")) * 2);
    InstantRunActivity.access$100((InstantRunActivity)AndroidInstantRuntime.getPrivateField(param1, InstantRunActivity.1.class, "this$0"));
  }
  //分發(fā)事件
  public Object access$dispatch(String paramString, Object... paramVarArgs){
    switch (paramString.hashCode()){
    default: 
      throw new InstantReloadException(String.format("String switch could not find '%s' with hashcode %s in %s", new Object[] { paramString, Integer.valueOf(paramString.hashCode()), "com/example/tzx/changeskin/InstantRunActivity$1" }));
    case -1912803358: //執(zhí)行修改后的onClick方法
      onClick((InstantRunActivity.1)paramVarArgs[0], (View)paramVarArgs[1]);
      return null;
    case -451173209: 
      init$body((InstantRunActivity.1)paramVarArgs[0], (InstantRunActivity)paramVarArgs[1], (Object[])paramVarArgs[2]);
      return null;
    }
    return init$args((InstantRunActivity.1[])paramVarArgs[0], (InstantRunActivity)paramVarArgs[1], (Object[])paramVarArgs[2]);
  }
}

就這樣通過(guò)動(dòng)態(tài)的修改 $change 的值玷坠,在運(yùn)行的過(guò)程改變函數(shù)的調(diào)用邏輯蜗搔,實(shí)現(xiàn)內(nèi)容的修復(fù)。

handleResourcePatch

private static int handleResourcePatch(int updateMode, ApplicationPatch patch, String path) {
    if (Log.isLoggable(BootstrapApplication.LOG_TAG, 2)) {
        Log.v(BootstrapApplication.LOG_TAG, "Received resource changes (" + path + ")");
    }
    FileManager.writeAaptResources(path, patch.getBytes());
    return Math.max(updateMode, 2);
}

Restart

一般通過(guò)現(xiàn)象看本質(zhì)八堡,而通過(guò)程序的運(yùn)行判斷修復(fù)的類型卻不一定是正確的樟凄。

on-code-change.png

比如在 AndroidStudioInstant Run 選項(xiàng)中勾選了 Restart activity on code changes 的話,無(wú)論什么樣的類型修復(fù)都至少會(huì)重啟當(dāng)前的 Activity 兄渺。

下邊列舉我實(shí)際測(cè)試的結(jié)果:

  • 修改java方法的代碼
I/Toast: Show toast from OpPackageName:com.tanzx.instantrun, PackageName:com.tanzx.instantrun, content:Applied code changes without activity restart
V/RenderScript: 0xa0f7d000 Launching thread(s), CPUs 4
Hot swapped changes, activity not restarted
  • 資源文件的修改
Hot swapped changes, activity restarted
I/Toast: Show toast from OpPackageName:com.tanzx.instantrun, PackageName:com.tanzx.instantrun, content:Applied changes, restarted activity

AndroidStudio 有更友好的提示:

add_class_quote.png
class_add.png
code_changes.png
field_added.png
method_add.png
R_class_changes.png
static_init_change.png

小結(jié)

我們將以上的所有內(nèi)容梳理一下:

  • 修改源代碼缝龄,每個(gè)類增加 $change 字段;
  • 替換 Application 挂谍;
  • 創(chuàng)建自己的類加載器叔壤,修改正常的類加載器的加載順序;
  • 開啟 Socket 監(jiān)聽(tīng) AndroidStudio 推送的消息口叙;
  • 處理消息(熱炼绘、溫、冷)
    • 熱:給類的 $change 字段賦值妄田,改變運(yùn)行邏輯俺亮;
    • 溫:替換加載新的資源,重啟當(dāng)前 Activity 生效疟呐;
    • 冷:寫入新的 dex 文件脚曾,重新加載新的 dex;

Gradle2.3.0版本

相同點(diǎn):

我們?cè)?gradle2.2.3 小結(jié)中發(fā)現(xiàn)其實(shí)我們只是需要做到以下幾點(diǎn)就可以進(jìn)行熱修復(fù):

  • 開啟 Socket 監(jiān)聽(tīng) AndroidStudio 推送的消息;
  • 處理消息:給類的 $change 字段賦值启具,改變運(yùn)行邏輯本讥;

不同點(diǎn)

Gradle2.3.0Gradle2.2.3 的基礎(chǔ)之上進(jìn)行了一定的修改,那么我們接下來(lái)只對(duì)比兩個(gè)版本之間的不同點(diǎn):

  • 去掉了 BootstrapApplication 替換鲁冯,直接啟動(dòng)一個(gè) InstantRunService 用來(lái)啟動(dòng) SocketAndroid Studio 進(jìn)行信息傳遞拷沸;

AndroidManifest.xml

<application android:theme="@style/AppTheme" 
             android:label="@string/app_name" 
             android:icon="@mipmap/ic_launcher" 
             android:name="com.example.instantrun.MyApplication" 
             android:debuggable="true" 
             android:allowBackup="true" 
             android:supportsRtl="true">
    <activity android:name="com.example.instantrun.MainActivity">
        <intent-filter>
            <action android:name="android.intent.action.MAIN"/>
            <category android:name="android.intent.category.LAUNCHER"/>
        </intent-filter>
    </activity>
    <service android:name="com.android.tools.fd.runtime.InstantRunService"                          android:exported="true"/>
</application>

InstantRunService:

public class InstantRunService extends Service {
    private Server server;
    /***部分代碼省略***/
    public void onCreate() {
        Log.i(Logging.LOG_TAG, "Starting Instant Run Server for " + getPackageName());
        super.onCreate();
        if (AppInfo.applicationId != null) {
            boolean foundPackage = false;
            try {
                boolean startServer;
                int pid = Process.myPid();
                List<RunningAppProcessInfo> processes = ((ActivityManager) getSystemService("activity")).getRunningAppProcesses();
                if (processes == null || processes.size() <= 1) {
                    startServer = true;
                } else {
                    startServer = false;
                    for (RunningAppProcessInfo processInfo : processes) {
                        if (AppInfo.applicationId.equals(processInfo.processName)) {
                            foundPackage = true;
                            if (processInfo.pid == pid) {
                                startServer = true;
                                break;
                            }
                        }
                    }
                    if (!(startServer || foundPackage)) {
                        startServer = true;
                        if (Log.isLoggable(Logging.LOG_TAG, 2)) {
                            Log.v(Logging.LOG_TAG, "Multiprocess but didn't find process with package: starting server anyway");
                        }
                    }
                }
                if (startServer) {
                    this.server = Server.create(this);
                    return;
                } else if (Log.isLoggable(Logging.LOG_TAG, 2)) {
                    Log.v(Logging.LOG_TAG, "In secondary process: Not starting server");
                    return;
                } else {
                    return;
                }
            } catch (Throwable t) {
                if (Log.isLoggable(Logging.LOG_TAG, 2)) {
                    Log.v(Logging.LOG_TAG, "Failed during multi process check", t);
                }
                this.server = Server.create(this);
                return;
            }
        }
        this.server = Server.create(this);
    }

    public void onDestroy() {
        if (this.server != null) {
            Log.i(Logging.LOG_TAG, "Stopping Instant Run Server for " + getPackageName());
            this.server.shutdown();
        }
        super.onDestroy();
    }
}
  • 去掉了所謂的冷啟動(dòng)(handleColdSwapPatch),需要冷啟動(dòng)的時(shí)候直接進(jìn)行碎片安裝重啟不就好了薯演;

    PS:分裂a(bǔ)pk以及安裝參考:Android Studio 配置構(gòu)建變體

private int handlePatches(List<ApplicationPatch> changes, boolean hasResources, int updateMode) {
    if (hasResources) {
        FileManager.startUpdate();
    }
    for (ApplicationPatch change : changes) {
        String path = change.getPath();
        if (path.equals(Paths.RELOAD_DEX_FILE_NAME)) {
            updateMode = handleHotSwapPatch(updateMode, change);
        } else if (isResourcePath(path)) {
            updateMode = handleResourcePatch(updateMode, change, path);
        }
    }
    if (hasResources) {
        FileManager.finishUpdate(true);
    }
    return updateMode;
}

結(jié)果

Gradle2.3.0 版本上進(jìn)行試驗(yàn)的時(shí)候撞芍,我一直沒(méi)有找到 InstantRunService 是怎么啟動(dòng)的。結(jié)果:每一次都是執(zhí)行的是 install-multiple 涣仿,然后重新啟動(dòng)勤庐。

在網(wǎng)上找了些資料發(fā)現(xiàn)好多用 Gradle2.3.0 的朋友有相關(guān)的報(bào)錯(cuò):

Launching app
$ adb shell am startservice kang.yi.zhi.tan.tanzhiyikang/com.android.tools.fd.runtime.InstantRunService
Error while executing: am startservice kang.yi.zhi.tan.tanzhiyikang/com.android.tools.fd.runtime.InstantRunService
Starting service: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=kang.yi.zhi.tan.tanzhiyikang/com.android.tools.fd.runtime.InstantRunService }
Error: Not found; no service started.

從這個(gè)錯(cuò)誤可以看處理,是 AndroidStudio 執(zhí)行了 adb 命令啟動(dòng)了 InstantRunService 好港。

Gralde3.0.0版本

相同點(diǎn):

我們?cè)?gradle2.2.3gralde2.3.0 中發(fā)現(xiàn)其實(shí)我們只是需要做到以下幾點(diǎn)就可以進(jìn)行熱修復(fù):

  • 開啟 Socket 監(jiān)聽(tīng) AndroidStudio 推送的消息愉镰;
  • 處理消息:給類的 $change 字段賦值,改變運(yùn)行邏輯钧汹;

不同點(diǎn)

Gradle3.0.0Gradle2.3.0 的基礎(chǔ)之上進(jìn)行了一定的修改丈探,那么我們接下來(lái)只對(duì)比兩個(gè)版本之間的不同點(diǎn):

  • 去掉了 InstantRunService ,而通過(guò)用 ContentProvider 來(lái)啟動(dòng) SocketAndroid Studio 進(jìn)行信息傳遞拔莱,因?yàn)?ContentProvider 在應(yīng)用啟動(dòng)的時(shí)候就行創(chuàng)建碗降;

AndroidManifest.xml

<application android:theme="@style/AppTheme" android:label="@string/app_name" android:icon="@mipmap/ic_launcher" android:name="com.example.tzx.changeskin.MyApplication" android:debuggable="true" android:testOnly="true" android:allowBackup="true" android:supportsRtl="true">
        <activity android:name="com.example.tzx.changeskin.InstantRunActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
        <provider android:name="com.android.tools.ir.server.InstantRunContentProvider" android:multiprocess="true" android:authorities="com.example.tzx.changeskin.com.android.tools.ir.server.InstantRunContentProvider"/>
    </application>

InstantRunContentProvider:

public final class InstantRunContentProvider extends ContentProvider {
    public boolean onCreate() {
        if (isMainProcess()) {
            Log.i(Logging.LOG_TAG, "starting instant run server: is main process");
            Server.create(getContext());
        } else {
            Log.i(Logging.LOG_TAG, "not starting instant run server: not main process");
        }
        return true;
    }

    private boolean isMainProcess() {
        boolean isMainProcess = false;
        if (AppInfo.applicationId == null) {
            return false;
        }
        boolean foundPackage = false;
        int pid = Process.myPid();
        for (RunningAppProcessInfo processInfo : ((ActivityManager) getContext().getSystemService("activity")).getRunningAppProcesses()) {
            if (AppInfo.applicationId.equals(processInfo.processName)) {
                foundPackage = true;
                if (processInfo.pid == pid) {
                    isMainProcess = true;
                    break;
                }
            }
        }
        if (isMainProcess || foundPackage) {
            return isMainProcess;
        }
        Log.w(Logging.LOG_TAG, "considering this process main process:no process with this package found?!");
        return true;
    }
    /***部分代碼省略***/
}

結(jié)果:

和預(yù)期的相同,進(jìn)行部分代碼修改的時(shí)候只需要重新啟動(dòng)當(dāng)前的 Activity 塘秦,進(jìn)行資源替換或類的結(jié)構(gòu)修改的時(shí)候需要重新啟動(dòng) Application 讼渊;否則進(jìn)行新的 install-multiple 安裝增量apk。

總結(jié):

  • InstantRun 相關(guān)的所有內(nèi)容都講述完了尊剔,其核心的就是在于編譯時(shí)給每個(gè)類進(jìn)行 $change 的插入爪幻,修復(fù)的時(shí)候再給類的 $change 賦值;
  • 不同 Gradle 版本之間主要是觸發(fā) Socket 啟動(dòng)的姿勢(shì)不同须误;
  • 中間穿插的講述了 Application 的替換挨稿、ClassLoader 的創(chuàng)建和類加載器順序的修改。

除過(guò)以上內(nèi)容外還有 gradle 在編譯打包的時(shí)候?qū)︻愇募?br> AndroidManifest.xml 的修改京痢,以及每次使用 instant-run 時(shí)差異包的生產(chǎn)奶甘、下發(fā)和處理都是我們做插件化或者熱修復(fù)可以借鑒和學(xué)習(xí)的。

文章到這里就全部講述完啦祭椰,若有其他需要交流的可以留言哦~臭家!~!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末吭产,一起剝皮案震驚了整個(gè)濱河市侣监,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌臣淤,老刑警劉巖橄霉,帶你破解...
    沈念sama閱讀 221,888評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異邑蒋,居然都是意外死亡姓蜂,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,677評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門医吊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)钱慢,“玉大人,你說(shuō)我怎么就攤上這事卿堂∈” “怎么了懒棉?”我有些...
    開封第一講書人閱讀 168,386評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)览绿。 經(jīng)常有香客問(wèn)我策严,道長(zhǎng),這世上最難降的妖魔是什么饿敲? 我笑而不...
    開封第一講書人閱讀 59,726評(píng)論 1 297
  • 正文 為了忘掉前任妻导,我火速辦了婚禮,結(jié)果婚禮上怀各,老公的妹妹穿的比我還像新娘倔韭。我一直安慰自己,他們只是感情好瓢对,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,729評(píng)論 6 397
  • 文/花漫 我一把揭開白布寿酌。 她就那樣靜靜地躺著,像睡著了一般硕蛹。 火紅的嫁衣襯著肌膚如雪份名。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,337評(píng)論 1 310
  • 那天妓美,我揣著相機(jī)與錄音僵腺,去河邊找鬼。 笑死壶栋,一個(gè)胖子當(dāng)著我的面吹牛辰如,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播贵试,決...
    沈念sama閱讀 40,902評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼琉兜,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了毙玻?” 一聲冷哼從身側(cè)響起豌蟋,我...
    開封第一講書人閱讀 39,807評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎桑滩,沒(méi)想到半個(gè)月后梧疲,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,349評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡运准,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,439評(píng)論 3 340
  • 正文 我和宋清朗相戀三年幌氮,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片胁澳。...
    茶點(diǎn)故事閱讀 40,567評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡该互,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出韭畸,到底是詐尸還是另有隱情宇智,我是刑警寧澤蔓搞,帶...
    沈念sama閱讀 36,242評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站随橘,受9級(jí)特大地震影響败明,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜太防,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,933評(píng)論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望酸员。 院中可真熱鬧蜒车,春花似錦、人聲如沸幔嗦。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,420評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)邀泉。三九已至嬉挡,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間汇恤,已是汗流浹背庞钢。 一陣腳步聲響...
    開封第一講書人閱讀 33,531評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留因谎,地道東北人基括。 一個(gè)月前我還...
    沈念sama閱讀 48,995評(píng)論 3 377
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像财岔,于是被迫代替她去往敵國(guó)和親风皿。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,585評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容